Camera2 with a SurfaceView

slott picture slott · Jul 14, 2015 · Viewed 16.9k times · Source

I'm trying to get the new Camera2 working with a simple SurfaceView and I'm having some problems with the live preview. On some devices the image is stretched out of proportions while looking fine on others.

I've setup a SurfaceView that I programatically adjust to fit the size of the preview stream size.

On Nexus 5 this looks fine but one Samsung devices its way off. Also the Samsung devices have a black border on the right part of the preview.

Is it really not possible to work with the SurfaceView or is this the time to switch to TextureView ?

Answer

rcsumner picture rcsumner · Jul 15, 2015

Yes, it is certainly possible. Note that the SurfaceView and its associated Surface are two different things, and each can/must be assigned a size.

The Surface is the actual memory buffer which will hold the output of the camera, and thus setting its size dictates the size of the actual image you will get from each frame. For each format available from the camera, there is a small set of possible (exact) sizes you can make this buffer.

The SurfaceView is what does the displaying of this image when it is available, and can basically be any size in your layout. It will stretch its underlying associated image data to fit whatever its layout size is, but note this display size is different from the data's size- Android will resize the image data for display automatically. This is what is probably causing your stretching.

For example, you can make a SurfaceView-based autofit View similar to the camera2basic's AutoFitTextureView as follows (this is what I use):

import android.content.Context;
import android.util.AttributeSet;
import android.view.SurfaceView;

public class AutoFitSurfaceView extends SurfaceView {

    private int mRatioWidth = 0;
    private int mRatioHeight = 0;

    public AutoFitSurfaceView(Context context) {
        this(context, null);
    }

    public AutoFitSurfaceView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AutoFitSurfaceView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    /**
     * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
     * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
     * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
     *
     * @param width  Relative horizontal size
     * @param height Relative vertical size
     */
    public void setAspectRatio(int width, int height) {
        if (width < 0 || height < 0) {
            throw new IllegalArgumentException("Size cannot be negative.");
        }
        mRatioWidth = width;
        mRatioHeight = height;
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (0 == mRatioWidth || 0 == mRatioHeight) {
            setMeasuredDimension(width, height);
        } else {
            if (width < height * mRatioWidth / mRatioHeight) {
                setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
            } else {
                setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
            }
        }
    }
}