Android Camera Preview Stretched

sci*_*fic 129 android preview aspect-ratio surfaceview android-camera

我一直在努力在Android上制作自定义相机活动,但是当旋转相机时,表面视图的纵横比会变得混乱.

在我的oncreate活动中,我设置了framelayout,它保存了显示摄像机参数的表面视图.

//FrameLayout that will hold the camera preview
        FrameLayout previewHolder = (FrameLayout) findViewById(R.id.camerapreview);

        //Setting camera's preview size to the best preview size
        Size optimalSize = null;
        camera = getCameraInstance();
        double aspectRatio = 0;
        if(camera != null){
            //Setting the camera's aspect ratio
            Camera.Parameters parameters = camera.getParameters();
            List<Size> sizes = parameters.getSupportedPreviewSizes();
            optimalSize = CameraPreview.getOptimalPreviewSize(sizes, getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);
            aspectRatio = (float)optimalSize.width/optimalSize.height;
        }

        if(optimalSize!= null){
            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, (int)(getResources().getDisplayMetrics().widthPixels*aspectRatio));
            previewHolder.setLayoutParams(params);
            LayoutParams surfaceParams = new LayoutParams(LayoutParams.MATCH_PARENT, (int)(getResources().getDisplayMetrics().widthPixels*aspectRatio));
            cameraPreview.setLayoutParams(surfaceParams);

        }

        cameraPreview.setCamera(camera);

        //Adding the preview to the holder
        previewHolder.addView(cameraPreview);
Run Code Online (Sandbox Code Playgroud)

然后,在Surface视图中,我设置要显示的摄像机参数

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

        mCamera = camera;

        if (mCamera != null) {
            requestLayout();

            try {
                mCamera.setPreviewDisplay(mHolder);
            } catch (IOException e) {
                e.printStackTrace();
            }


            if(mCamera != null){
                //Setting the camera's aspect ratio
                Camera.Parameters parameters = mCamera.getParameters();
                List<Size> sizes = parameters.getSupportedPreviewSizes();
                Size optimalSize = getOptimalPreviewSize(sizes, getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);

                parameters.setPreviewSize(optimalSize.width, optimalSize.height);
                mCamera.setParameters(parameters);
            }

            /*
              Important: Call startPreview() to start updating the preview surface. Preview must 
              be started before you can take a picture.
              */
            mCamera.startPreview();
        }

    }
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

你可以看到,当手机旋转时,乐高男人会变得更高更瘦;

如何确保相机视图的宽高比正确?

F1s*_*her 159

我正在使用这种方法 - >基于API演示来获取我的预览大小:

private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio=(double)h / w;

        if (sizes == null) return null;

        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        for (Camera.Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }
Run Code Online (Sandbox Code Playgroud)

如您所见,您必须输入屏幕的宽度和高度.此方法将根据这些值计算屏幕比率,然后从supportedPreviewSizes列表中选择最适合您的屏幕比率.通过使用,获取您的supportedPreviewSize列表,其中Camera对象不为null

mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
Run Code Online (Sandbox Code Playgroud)

然后在onMeasure中,您可以获得最佳的previewSize:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        setMeasuredDimension(width, height);

        if (mSupportedPreviewSizes != null) {
           mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
        }
    }
Run Code Online (Sandbox Code Playgroud)

然后(在我的代码中使用了SurfaceChanged方法,就像我说我使用的是Camera Demos的API演示结构代码,你可以在Eclipse中生成它):

Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
mCamera.setParameters(parameters);
mCamera.startPreview();
Run Code Online (Sandbox Code Playgroud)

还有一个暗示,因为我做了几乎和你一样的应用程序.Camera Activity的好习惯是隐藏StatusBar.Instagram等应用程序正在这样做.它会降低屏幕高度值并更改比率值.某些设备上可能会出现奇怪的预览尺寸(SurfaceView会被切割一点)


要回答您的问题,如何检查您的预览比例是否正确?然后获取您在其中设置的参数的高度和宽度:

mCamera.setParameters(parameters);
Run Code Online (Sandbox Code Playgroud)

您的设定比率等于高度/宽度.如果您希望相机在屏幕上看起来很好,那么您设置为相机的参数的高宽比必须与屏幕的高度(减去状态栏)/宽度比相同.

  • @phyzalis我同意你的看法.实际上,我必须对代码进行校正才能按预期工作. (3认同)
  • 只有一个问题:我看到你正在使用2种不同的计算比率:**double targetRatio =(double)h/w**和**double ratio =(double)size.width/size.height**.第一个是高度除以宽度,另一个是相反的,宽度除以高度.它们应该是不一样的计算吗? (2认同)
  • @ F1sher,我总是得到空指针异常参数.setPreviewSize(mPreviewSize.width,mPreviewSize.height); 找不到任何解决方案. (2认同)

Hes*_*sam 143

F1Sher的解决方案很好但有时不起作用.特别是,当您的surfaceView不覆盖整个屏幕时.在这种情况下,您需要覆盖onMeasure()方法.我在这里复制了我的代码供您参考.

因为我根据宽度测量了SurfaceView,所以我在屏幕的末尾有一点点白色间隙,我按设计填充它.如果您保持高度并通过将其乘以比率来增加宽度,则可以解决此问题.但是,它会稍微挤压surfaceView.

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

    private static final String TAG = "CameraPreview";

    private Context mContext;
    private SurfaceHolder mHolder;
    private Camera mCamera;
    private List<Camera.Size> mSupportedPreviewSizes;
    private Camera.Size mPreviewSize;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mContext = context;
        mCamera = camera;

        // supported preview sizes
        mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
        for(Camera.Size str: mSupportedPreviewSizes)
                Log.e(TAG, str.width + "/" + str.height);

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // empty. surfaceChanged will take care of stuff
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        Log.e(TAG, "surfaceChanged => w=" + w + ", h=" + h);
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.
        if (mHolder.getSurface() == null){
            // preview surface does not exist
            return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e){
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or reformatting changes here
        // start preview with new settings
        try {
            Camera.Parameters parameters = mCamera.getParameters();
            parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
            mCamera.setParameters(parameters);
            mCamera.setDisplayOrientation(90);
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);

        if (mSupportedPreviewSizes != null) {
            mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
        }

        if (mPreviewSize!=null) {
            float ratio;
            if(mPreviewSize.height >= mPreviewSize.width)
                ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
            else
                ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;

            // One of these methods should be used, second method squishes preview slightly
            setMeasuredDimension(width, (int) (width * ratio));
  //        setMeasuredDimension((int) (width * ratio), height);
        }
    }

    private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) h / w;

        if (sizes == null)
            return null;

        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        for (Camera.Size size : sizes) {
            double ratio = (double) size.height / size.width;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
                continue;

            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }

        return optimalSize;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这应该是正确的答案,它处理相机的预览没有覆盖整个屏幕的情况.+1. (9认同)
  • 需要在`getOptimalPreviewSize`中切换的高度和宽度才能为我工作.这是因为显示方向设置为90?否则,比率计算甚至不接近(目标是1.7,相机比率小于1) (4认同)
  • 嗨@ adnan9011,本教程可能会有所帮助.https://www.airpair.com/android/android-camera-surface-view-fragment (2认同)

小智 23

注意:我的解决方案是HESAM解决方案的持续性:https://stackoverflow.com/a/22758359/1718734

我所说的:Hesam说有些手机上可能会出现一些小空格,如下所示:

注意:虽然宽高比是正确的,但相机不会填满整个屏幕.

Hesam建议采用第二种解决方案,但是要求预览.在某些设备上,它会严重扭曲.

那么我们如何解决这个问题呢.它很简单......通过乘以纵横比直到它填满屏幕.我注意到,一些流行的应用程序,如Snapchat,WhatsApp等以同样的方式工作.

您所要做的就是将其添加到onMeasure方法:

float camHeight = (int) (width * ratio);
    float newCamHeight;
    float newHeightRatio;

    if (camHeight < height) {
        newHeightRatio = (float) height / (float) mPreviewSize.height;
        newCamHeight = (newHeightRatio * camHeight);
        Log.e(TAG, camHeight + " " + height + " " + mPreviewSize.height + " " + newHeightRatio + " " + newCamHeight);
        setMeasuredDimension((int) (width * newHeightRatio), (int) newCamHeight);
        Log.e(TAG, mPreviewSize.width + " | " + mPreviewSize.height + " | ratio - " + ratio + " | H_ratio - " + newHeightRatio + " | A_width - " + (width * newHeightRatio) + " | A_height - " + newCamHeight);
    } else {
        newCamHeight = camHeight;
        setMeasuredDimension(width, (int) newCamHeight);
        Log.e(TAG, mPreviewSize.width + " | " + mPreviewSize.height + " | ratio - " + ratio + " | A_width - " + (width) + " | A_height - " + newCamHeight);
    }
Run Code Online (Sandbox Code Playgroud)

这将计算屏幕高度并获得屏幕高度与mPreviewSize高度的比率.然后,它将相机的宽度和高度乘以新的高度比,并相应地设置测量的尺寸.

在此输入图像描述

接下来你知道了,你最终得到了这个:D

在此输入图像描述

这也适用于前置摄像头.我相信这是解决这个问题的最好方法.现在我的应用程序唯一剩下的就是在点击"Capture"时保存预览.但是,是的,就是这样.


Wil*_*han 10

好的,所以我认为对于一般的相机预览拉伸问题没有足够的答案.或者至少我找不到一个.我的应用程序也遭受了这种拉伸综合症,我花了一段时间才将这个门户和互联网上所有用户答案的​​解决方案拼凑起来.

我尝试了@ Hesa​​m的解决方案,但它没有工作,并使我的相机预览主要扭曲.

首先,我展示了我的解决方案的代码(代码的重要部分),然后我解释了为什么我采取了这些步骤.有改进性能的空间.

主要活动xml布局:

<RelativeLayout 
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <FrameLayout
        android:id="@+id/camera_preview"
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
    />
</RelativeLayout>
Run Code Online (Sandbox Code Playgroud)

相机预览:

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

private SurfaceHolder prHolder;
private Camera prCamera;
public List<Camera.Size> prSupportedPreviewSizes;
private Camera.Size prPreviewSize;

@SuppressWarnings("deprecation")
public YoCameraPreview(Context context, Camera camera) {
    super(context);
    prCamera = camera;

    prSupportedPreviewSizes = prCamera.getParameters().getSupportedPreviewSizes();

    prHolder = getHolder();
    prHolder.addCallback(this);
    prHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

public void surfaceCreated(SurfaceHolder holder) {
    try {
        prCamera.setPreviewDisplay(holder);
        prCamera.startPreview();
    } catch (IOException e) {
        Log.d("Yologram", "Error setting camera preview: " + e.getMessage());
    }
}

public void surfaceDestroyed(SurfaceHolder holder) {
}

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    if (prHolder.getSurface() == null){
      return;
    }

    try {
        prCamera.stopPreview();
    } catch (Exception e){
    }

    try {
        Camera.Parameters parameters = prCamera.getParameters();
        List<String> focusModes = parameters.getSupportedFocusModes();
        if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        }
        parameters.setPreviewSize(prPreviewSize.width, prPreviewSize.height);

        prCamera.setParameters(parameters);
        prCamera.setPreviewDisplay(prHolder);
        prCamera.startPreview();

    } catch (Exception e){
        Log.d("Yologram", "Error starting camera preview: " + e.getMessage());
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);

    setMeasuredDimension(width, height);

    if (prSupportedPreviewSizes != null) {
        prPreviewSize = 
            getOptimalPreviewSize(prSupportedPreviewSizes, width, height);
    }    
}

public Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {

    final double ASPECT_TOLERANCE = 0.1;
    double targetRatio = (double) h / w;

    if (sizes == null)
        return null;

    Camera.Size optimalSize = null;
    double minDiff = Double.MAX_VALUE;

    int targetHeight = h;

    for (Camera.Size size : sizes) {
        double ratio = (double) size.width / size.height;
        if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
            continue;

        if (Math.abs(size.height - targetHeight) < minDiff) {
            optimalSize = size;
            minDiff = Math.abs(size.height - targetHeight);
        }
    }

    if (optimalSize == null) {
        minDiff = Double.MAX_VALUE;
        for (Camera.Size size : sizes) {
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }
    }

    return optimalSize;
}
}
Run Code Online (Sandbox Code Playgroud)

主要活动:

public class MainActivity extends Activity {

...

@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);     
    setContentView(R.layout.activity_main);

        maCamera = getCameraInstance();

        maLayoutPreview = (FrameLayout) findViewById(R.id.camera_preview);

        maPreview = new CameraPreview(this, maCamera);

        Point displayDim = getDisplayWH();
        Point layoutPreviewDim = calcCamPrevDimensions(displayDim, 
                maPreview.getOptimalPreviewSize(maPreview.prSupportedPreviewSizes, 
                    displayDim.x, displayDim.y));
        if (layoutPreviewDim != null) {
            RelativeLayout.LayoutParams layoutPreviewParams = 
                (RelativeLayout.LayoutParams) maLayoutPreview.getLayoutParams();
            layoutPreviewParams.width = layoutPreviewDim.x;
            layoutPreviewParams.height = layoutPreviewDim.y;
            layoutPreviewParams.addRule(RelativeLayout.CENTER_IN_PARENT);
            maLayoutPreview.setLayoutParams(layoutPreviewParams);
        }
        maLayoutPreview.addView(maPreview);
}

@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
private Point getDisplayWH() {

    Display display = this.getWindowManager().getDefaultDisplay();
    Point displayWH = new Point();

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
        display.getSize(displayWH);
        return displayWH;
    }
    displayWH.set(display.getWidth(), display.getHeight());
    return displayWH;
}

private Point calcCamPrevDimensions(Point disDim, Camera.Size camDim) {

    Point displayDim = disDim;
    Camera.Size cameraDim = camDim;

    double widthRatio = (double) displayDim.x / cameraDim.width;
    double heightRatio = (double) displayDim.y / cameraDim.height;

    // use ">" to zoom preview full screen
    if (widthRatio < heightRatio) {
        Point calcDimensions = new Point();
        calcDimensions.x = displayDim.x;
        calcDimensions.y = (displayDim.x * cameraDim.height) / cameraDim.width;
        return calcDimensions;
    }
    // use "<" to zoom preview full screen
    if (widthRatio > heightRatio) { 
        Point calcDimensions = new Point();
        calcDimensions.x = (displayDim.y * cameraDim.width) / cameraDim.height;
        calcDimensions.y = displayDim.y;
        return calcDimensions;
    }
    return null;
}   
}
Run Code Online (Sandbox Code Playgroud)

我的评论:

所有这一切的要点是,尽管你计算出最佳的相机尺寸,但getOptimalPreviewSize()你只选择最接近的比例来适应你的屏幕.因此,除非比率完全相同,否则预览将延伸.

为什么会伸展?因为您的FrameLayout相机预览在layout.xml中设置为match_parent的宽度和高度.这就是预览将延伸到全屏的原因.

需要做的是设置相机预览布局的宽度和高度以匹配所选的相机尺寸比,因此预览保持其纵横比并且不会扭曲.

我尝试使用CameraPreview该类来完成所有计算和布局更改,但我无法弄明白.我尝试应用此解决方案,但SurfaceView无法识别getChildCount ()getChildAt (int index).我想,我最终得到了它的参考maLayoutPreview,但它是行为不端,并将设定比率应用于我的整个应用程序,并在第一张照片拍摄后这样做.所以我放手将布局修改移到了MainActivity.

CameraPreview我改变prSupportedPreviewSizesgetOptimalPreviewSize()公众这样我就可以使用它MainActivity.然后我需要显示尺寸(减去导航/状态栏,如果有的话)并选择最佳的相机尺寸.我试图获取RelativeLayout(或FrameLayout)大小而不是显示大小,但它返回零值.这个解决方案对我不起作用.布局得到了它的值onWindowFocusChanged(在日志中检查).

所以我有计算布局尺寸的方法,以匹配所选摄像机尺寸的宽高比.现在您只需设置LayoutParams相机预览布局.更改宽度,高度并将其居中显示在父级中.

如何计算预览尺寸有两种选择.您希望它在侧面或顶部/底部与黑条(如果windowBackground设置为null)适合屏幕.或者您希望预览缩放到全屏.我在评论中留下了更多信息calcCamPrevDimensions().


小智 7

嗨这里的getOptimalPreview()不适合我,所以我想分享我的版本:

private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {

    if (sizes==null) return null;

    Camera.Size optimalSize = null;
    double ratio = (double)h/w;
    double minDiff = Double.MAX_VALUE;
    double newDiff;
    for (Camera.Size size : sizes) {
        newDiff = Math.abs((double)size.width/size.height - ratio);
        if (newDiff < minDiff) {
            optimalSize = size;
            minDiff = newDiff;
        }
    }
    return optimalSize;
}
Run Code Online (Sandbox Code Playgroud)