理想的 surfaceView 相机方向和比例(避免预览拉伸)- android

The ideal surfaceView Camera orientations and proportions (avoiding preview stretching) - android

我从一开始就开始了一个包含相机 surfaceView(用于在屏幕上实时预览图像)的项目,我认为实现它应该不会有太大问题,但现在我已经卡在有关预览方向及其比例的部分,我一直在搜索 Stack 关于表面视图方向和拉伸问题的主题,但我根本无法从中得到好的结果。

我想做的是无论phone旋转如何,都以相同的角度获得预览,这样用户就可以一直看到他的脸。

Example picture

如果您能建议我如何更改我的代码以避免预览在 portret 和横向视图中拉伸,我会很高兴。

我在上面附上我的代码

public class CameraView extends SurfaceView implements SurfaceHolder.Callback {


private SurfaceHolder mHolder;
private Camera mCamera;


public CameraView (Context context, Camera camera){
    super(context);
    mCamera = camera;
    mCamera.setDisplayOrientation(90);


    //get the holder and set this class as the callback, so we can get camera data here
    mHolder = getHolder();
    mHolder.addCallback(this);
    //mHolder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL); 
    mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
    try {
        Camera.Parameters parameters = mCamera.getParameters();
        if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            parameters.set("orientation", "portrait");
            mCamera.setDisplayOrientation(90);
            parameters.setRotation(90);
        }
        else {
            // This is an undocumented although widely known feature
            parameters.set("orientation", "landscape");
            // For Android 2.2 and above
            mCamera.setDisplayOrientation(0);
            // Uncomment for Android 2.0 and above
            parameters.setRotation(0);
        }
        mCamera.setPreviewDisplay(surfaceHolder);
        mCamera.startPreview();

    } catch (IOException e) {
        // left blank for now
    }
}

@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
    this.getHolder().removeCallback(this); 
    mCamera.stopPreview();
    mCamera.release();
}

@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format,
                           int width, int height) {

    try {
        Camera.Parameters parameters = mCamera.getParameters();
        if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            parameters.set("orientation", "portrait");
            mCamera.setDisplayOrientation(90);
            parameters.setRotation(90);
            setCamera(mCamera);
            mCamera.setPreviewDisplay(surfaceHolder);
            mCamera.startPreview();
        }
        else {

            parameters.set("orientation", "landscape");
            mCamera.setDisplayOrientation(0);
            parameters.setRotation(0);
        }
        setCamera(mCamera);
        mCamera.setPreviewDisplay(surfaceHolder);
        mCamera.startPreview();

    } catch (IOException e) {

    }
}

public void setCamera(Camera camera){ 
    mCamera = camera;  }               

在您的方法中,您必须选择要在预览中显示的特定比例,这将产生因屏幕尺寸而异的结果。

你可能想考虑一个不同的方法,你可以定义你自己的相机预览如下:

  • 创建相机预览class:

       public class CameraSourcePreview extends ViewGroup {
    
    
    
            private static final String TAG = "CameraSourcePreview";
            private Context mContext;
            private SurfaceView mSurfaceView;
            private boolean mStartRequested;
            private boolean mSurfaceAvailable;
            private CameraSource mCameraSource;
    
            private GraphicOverlay mOverlay;
    
            public CameraSourcePreview(Context context, AttributeSet attrs) {
                super(context, attrs);
                mContext = context;
                mStartRequested = false;
                mSurfaceAvailable = false;
    
                mSurfaceView = new SurfaceView(context);
                mSurfaceView.getHolder().addCallback(new SurfaceCallback());
                addView(mSurfaceView);
            }
    
            public void start(CameraSource cameraSource) throws IOException {
                if (cameraSource == null) {
                    stop();
                }
    
                mCameraSource = cameraSource;
    
                if (mCameraSource != null) {
                    mStartRequested = true;
                    startIfReady();
                }
            }
    
            public void start(CameraSource cameraSource, GraphicOverlay overlay) throws IOException {
                mOverlay = overlay;
                start(cameraSource);
            }
    
            public void stop() {
                if (mCameraSource != null) {
                    mCameraSource.stop();
                }
            }
    
            public void release() {
                if (mCameraSource != null) {
                    mCameraSource.release();
                    mCameraSource = null;
                }
            }
    
            private void startIfReady() throws IOException {
                if (mStartRequested && mSurfaceAvailable) {
                    mCameraSource.start(mSurfaceView.getHolder());
                    if (mOverlay != null) {
                        Size size = mCameraSource.getPreviewSize();
                        int min = Math.min(size.getWidth(), size.getHeight());
                        int max = Math.max(size.getWidth(), size.getHeight());
                        if (isPortraitMode()) {
                            // Swap width and height sizes when in portrait, since it will be rotated by
                            // 90 degrees
                            mOverlay.setCameraInfo(min, max, mCameraSource.getCameraFacing());
                        } else {
                            mOverlay.setCameraInfo(max, min, mCameraSource.getCameraFacing());
                        }
                        mOverlay.clear();
                    }
                    mStartRequested = false;
                }
            }
    
            private class SurfaceCallback implements SurfaceHolder.Callback {
                @Override
                public void surfaceCreated(SurfaceHolder surface) {
                    mSurfaceAvailable = true;
                    try {
                        startIfReady();
                    } catch (IOException e) {
                        Log.e(TAG, "Could not start camera source.", e);
                    }
                }
    
                @Override
                public void surfaceDestroyed(SurfaceHolder surface) {
                    mSurfaceAvailable = false;
                }
    
                @Override
                public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                }
            }
    
            @Override
            protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
                int previewWidth = 320;
                int previewHeight = 240;
                if (mCameraSource != null) {
                    Size size = mCameraSource.getPreviewSize();
                    if (size != null) {
                        previewWidth = size.getWidth();
                        previewHeight = size.getHeight();
                    }
                }
    
                // Swap width and height sizes when in portrait, since it will be rotated 90 degrees
                if (isPortraitMode()) {
                    int tmp = previewWidth;
                    previewWidth = previewHeight;
                    previewHeight = tmp;
                }
    
                final int viewWidth = right - left;
                final int viewHeight = bottom - top;
    
                int childWidth;
                int childHeight;
                int childXOffset = 0;
                int childYOffset = 0;
                float widthRatio = (float) viewWidth / (float) previewWidth;
                float heightRatio = (float) viewHeight / (float) previewHeight;
    
                // To fill the view with the camera preview, while also preserving the correct aspect ratio,
                // it is usually necessary to slightly oversize the child and to crop off portions along one
                // of the dimensions.  We scale up based on the dimension requiring the most correction, and
                // compute a crop offset for the other dimension.
                if (widthRatio > heightRatio) {
                    childWidth = viewWidth;
                    childHeight = (int) ((float) previewHeight * widthRatio);
                    childYOffset = (childHeight - viewHeight) / 2;
                } else {
                    childWidth = (int) ((float) previewWidth * heightRatio);
                    childHeight = viewHeight;
                    childXOffset = (childWidth - viewWidth) / 2;
                }
    
                for (int i = 0; i < getChildCount(); ++i) {
                    // One dimension will be cropped.  We shift child over or up by this offset and adjust
                    // the size to maintain the proper aspect ratio.
                    getChildAt(i).layout(
                            -1 * childXOffset, -1 * childYOffset,
                            childWidth - childXOffset, childHeight - childYOffset);
                }
    
                try {
                    startIfReady();
                } catch (IOException e) {
                    Log.e(TAG, "Could not start camera source.", e);
                }
            }
    
            private boolean isPortraitMode() {
                int orientation = mContext.getResources().getConfiguration().orientation;
                if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                    return false;
                }
                if (orientation == Configuration.ORIENTATION_PORTRAIT) {
                    return true;
                }
    
                Log.d(TAG, "isPortraitMode returning false by default");
                return false;
            }
    

    }

  • 在您的 layout.xml 中添加自定义预览:

    <yourpackagename.CameraSourcePreview
        android:id="@+id/preview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    

  • 在你的activityclass里面定义camera source的方法,然后就可以在onCreate()中调用这个方法了

/** * 启动或重新启动相机源(如果存在)。如果相机源还不存在 *(例如,因为在创建相机源之前调用了 onResume),这将被调用 * 再次创建相机源。 */

private void startCameraSource() {
    // check that the device has play services available.
    int code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(
            getApplicationContext());
    if (code != ConnectionResult.SUCCESS) {
        Dialog dlg =
                GoogleApiAvailability.getInstance().getErrorDialog(this, code, RC_HANDLE_GMS);
        dlg.show();
    }

    if (mCameraSource != null) {
        try {
            mPreview.start(mCameraSource, mGraphicOverlay);
        } catch (IOException e) {
            Log.e(TAG, "Unable to start camera source.", e);
            mCameraSource.release();
            mCameraSource = null;
        }
    }
}
  • 不要忘记添加以下内容:

/** * 重启相机。 */

@Override
protected void onResume() {
    super.onResume();

    startCameraSource();
}

/** * 停止相机。 */

@Override
protected void onPause() {
    super.onPause();
    mPreview.stop();
}

/**
 * Releases the resources associated with the camera source, the associated detector, and the
 * rest of the processing pipeline.
 */


 @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mCameraSource != null) {
            mCameraSource.release();
        }
    }