Fitting a camera preview to a SurfaceView larger than the display

Daniel Smith picture Daniel Smith · Jul 4, 2012 · Viewed 17.7k times · Source

I have an Android application that needs to fullscreen a camera preview regardless of the preview size given by getSupportedPreviewSizes() without distortion. I expect this to crop the preview by some amount in either the height or width, but this does not concern me. The reason I seek to do this is because certain android cameras (e.g. Samsung Galaxy SII front facing camera) do not return any previews with the same aspect ratio as the display, and therefore need to be stretched and clipped.

Is it possible to fully grow the camera preview to a view larger than the display size? It is possible to grow the SurfaceView larger than the preview pixel dimensions returned by simply setting the surface view's layout params to match_parent, but this results in distortion in some direction and only is possible up until the preview fills the display. This seems to indicate that previewing outside of the display is impossible, and the hardware (or OS) limits such behavior.

There is another question on the topic, but that question claims it is not possible to create a SurfaceView greater than the screen dimensions, which is false, as I have found with logging. However, what seems to be the case is that camera preview however cannot grow to the full size of an enormous SurfaceView.

My Attempt:

I have a relative layout, within it a FrameLayout, and within that I create a SurfaceView. The width of the entire relative layout is set below at 635dp, which is ~double the device's screen size. The FrameLayout matches this sizing and so does the SurfaceView (added programmatically later)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/camera_activity_layout"
    android:layout_width="635dp"
    android:layout_height="match_parent" >

    <FrameLayout
        android:id="@+id/camera_preview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </FrameLayout>

</RelativeLayout>

The following is the code that gets and sets preview size. Here I want to make the camera preview expand to fill the entire FrameLayout (double the screen width). The view shows up and destructs totally normally so I am removing code not related to this particular question because it distracts from the essential question, and my attempt to easily and simply grow the view to some larger-than-display size.

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

    ...

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        Camera.Parameters parameters = mCamera.getParameters();
        final DisplayMetrics dm = this.getResources().getDisplayMetrics();
            //getBestPreviewSize returns the best preview size, don't worry
            //but some devices do not return one for all camera matching the aspect ratio
        cameraSize = getBestPreviewSize(parameters, dm.heightPixels, dm.widthPixels);

        if (cameraSize!=null) {
            parameters.setPreviewSize(cameraSize.width, cameraSize.height);
            mCamera.setParameters(parameters);
        }

        FrameLayout.LayoutParams frameParams = (FrameLayout.LayoutParams) this.getLayoutParams();

        frameParams.width = 800; //For argument's sake making this ~double the width of the display. Same result occurs if I use MATCH_PARENT
        frameParams.height = LayoutParams.MATCH_PARENT;
        this.setLayoutParams(frameParams);

        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

            Log.d("surfChange","W/H of CamPreview (child) is "+this.getWidth()+"/"+this.getHeight()+" while parent is ");
            Log.d("surfChange","W/H of holder (child) is "+mHolder.getSurfaceFrame().width()+"/"+mHolder.getSurfaceFrame().height()+" while parent is ");
        } catch (Exception e){
        }
    }
};

I am concerned that the only way to succeed in this endeavor is to somehow render to an openGL surface, but this apparently might be too slow in full color. It is also a pain if the camera preview can simply be stretched and clipped.

Thanks in advance to any attempts at the solution.

Answer

jp36 picture jp36 · Jan 18, 2013

Ok, I know this is very late as an answer but it is possible. Below is code from the Android SDK sample. To see the rest of the code to implement this download the android sdk samples and go to Samples/android-10(one I used, could try whichever you want to target)/ApiDemos/src/com/example/android/apis/graphics/CameraPreview.java

class PreviewSurface extends ViewGroup implements SurfaceHolder.Callback {
...
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (changed && getChildCount() > 0) {
        final View child = getChildAt(0);

        final int width = r - l;
        final int height = b - t;

        int previewWidth = width;
        int previewHeight = height;
        if (mPreviewSize != null) {
            previewWidth = mPreviewSize.width;
            previewHeight = mPreviewSize.height;
        }

        // Center the child SurfaceView within the parent.
        if (width * previewHeight > height * previewWidth) {
            final int scaledChildWidth = previewWidth * height / previewHeight;
            child.layout((width - scaledChildWidth) / 2, 0,
                    (width + scaledChildWidth) / 2, height);
        } else {
            final int scaledChildHeight = previewHeight * width / previewWidth;
            child.layout(0, (height - scaledChildHeight) / 2,
                    width, (height + scaledChildHeight) / 2);
        }
    }
}
}

this code is intended to fit the preview to the smallest dimension (that doesn't mess up the aspcect ratio) and center to have black bars on the other dimension. But if you flip the > to be < instead you will achieve what you want. Ex:

if (width * previewHeight < height * previewWidth) {...