简体   繁体   English

Android 相机预览拉伸

[英]Android Camera Preview Stretched

I've been working on making my custom camera activity on Android, but when rotating the camera, the aspect ratio of the surface view gets messed up.我一直致力于在 Android 上制作我的自定义相机活动,但是当旋转相机时,表面视图的纵横比变得混乱。

In my oncreate for the activity, I set the framelayout which holds the surface view that displays the camera's parameters.在我为活动创建的 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);

Then, in the Surface view I set the camera's parameters to be displayed然后,在 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();
        }

    }

在此处输入图像描述

You can see that the LEGO man grows taller and skinnier when the phone is rotated:可以看到手机旋转时乐高人变高变瘦了:

How can I ensure that the aspect ratio for my camera view is correct?如何确保相机视图的纵横比正确?

I'm using this method -> based on API Demos to get my Preview Size:我正在使用这种方法 -> 基于 API Demos 来获取我的预览尺寸:

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;
    }

As you can see you have to input width and height of your screen.如您所见,您必须输入屏幕的宽度和高度。 This method will calculate screen ratio based on those values and then from the list of supportedPreviewSizes it will choose the best for you from avaliable ones.此方法将根据这些值计算屏幕比例,然后从supportedPreviewSizes 列表中选择最适合您的可用值。 Get your supportedPreviewSize list in place where Camera object isn't null by using使用

mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();

And then on in onMeasure you can get your optimal previewSize like that:然后在 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);
        }
    }

And then (in my code in surfaceChanged method, like I said I'm using API Demos structure of CameraActivity code, you can generate it in Eclipse):然后(在我在surfaceChanged方法中的代码中,就像我说的,我正在使用CameraActivity代码的API Demos结构,你可以在Eclipse中生成它):

Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
mCamera.setParameters(parameters);
mCamera.startPreview();

And one hint for you, because I did almost the same app like you.给你一个提示,因为我和你做了几乎一样的应用程序。 Good practice for Camera Activity is to hide StatusBar.相机活动的良好做法是隐藏状态栏。 Applications like Instagram are doing it.像 Instagram 这样的应用程序正在这样做。 It reduces your screen height value and change your ratio value.它会降低您的屏幕高度值并更改您的比率值。 It is possible to get strange Preview Sizes on some devices (your SurfaceView will be cut a little)在某些设备上可能会出现奇怪的预览尺寸(您的 SurfaceView 会被削减一点)


And to answer your question, how to check if your preview ratio is correct?并回答您的问题,如何检查您的预览比例是否正确? Then get height and width of parameters that you set in:然后获取您设置的参数的高度和宽度:

mCamera.setParameters(parameters);

your set ratio is equal to height/width.您的设定比例等于高度/宽度。 If you want camera to look good on your screen then height/width ratio of parameters that you set to camera must be the same as height(minus status bar)/width ratio of your screen.如果您希望相机在屏幕上看起来不错,那么您设置为相机的参数的高度/宽度比率必须与屏幕的高度(减去状态栏)/宽度比率相同。

F1Sher's solution is nice but sometimes doesn't work. F1Sher 的解决方案很好,但有时不起作用。 Particularly, when your surfaceView doesn't cover whole screen.特别是当您的 SurfaceView 没有覆盖整个屏幕时。 In this case you need to override onMeasure() method.在这种情况下,您需要覆盖 onMeasure() 方法。 I have copied my code here for your reference.我在这里复制了我的代码供您参考。

Since I measured surfaceView based on width then I have little bit white gap at the end of my screen that I filled it by design.由于我根据宽度测量了surfaceView,因此我在屏幕末端有一点白色间隙,这是我按设计填充的。 You are able to fix this issue if you keep height and increase width by multiply it to ratio.如果您保持高度并通过乘以比率来增加宽度,则可以解决此问题。 However, it will squish surfaceView slightly.但是,它会稍微挤压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;
    }
}

NOTE: MY SOLUTION IS A CONTINUATION OF HESAM'S SOLUTION: https://stackoverflow.com/a/22758359/1718734注意:我的解决方案是 HESAM 解决方案的延续: https ://stackoverflow.com/a/22758359/1718734

What I address: Hesam's said there is a little white space that may appear on some phones, like this:我要解决的问题:Hesam 说某些手机上可能会出现一点空白,如下所示:

注意:虽然纵横比正确,但相机并没有填满整个屏幕。

Hesam suggested a second solution, but that squishes the preview. Hesam 提出了第二种解决方案,但这会影响预览。 And on some devices, it heavily distorts.在某些设备上,它会严重扭曲。

So how do we fix this problem.那么我们如何解决这个问题。 It is simple...by multiplying the aspect ratios till it fills in the screen.这很简单……通过乘以纵横比直到它填满屏幕。 I have noticed, several popular apps such as Snapchat, WhatsApp, etc works the same way.我注意到,Snapchat、WhatsApp 等几个流行的应用程序的工作方式相同。

All you have to do is add this to the onMeasure method:您所要做的就是将其添加到 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);
    }

This will calculate the screen height and gets the ratio of the screen height and the mPreviewSize height.这将计算屏幕高度并获取屏幕高度和 mPreviewSize 高度的比率。 Then it multiplies the camera's width and height by the new height ratio and the set the measured dimension accordingly.然后它将相机的宽度和高度乘以新的高度比率,并相应地设置测量尺寸。

在此处输入图片说明

And the next thing you know, you end up with this :D接下来你知道,你最终会得到这个:D

在此处输入图片说明

This also works well with he front camera.这也适用于前置摄像头。 I believe this is the best way to go about this.我相信这是解决这个问题的最好方法。 Now the only thing left for my app is to save the preview itself upon clicking on "Capture."现在我的应用程序唯一剩下的就是在单击“捕获”时保存预览本身。 But ya, this is it.但是,是的,就是这样。

OK, so I think there is no sufficient answer for general camera preview stretching problem.好的,所以我认为对于一般的相机预览拉伸问题没有足够的答案 Or at least I didn't find one.或者至少我没有找到。 My app also suffered this stretching syndrome and it took me a while to puzzle together a solution from all the user answers on this portal and internet.我的应用程序也遭受了这种拉伸综合症,我花了一段时间从这个门户网站和互联网上的所有用户答案中拼凑出一个解决方案。

I tried @Hesam's solution but it didn't work and left my camera preview majorly distorted.我尝试了@Hesam 的解决方案,但它没有用,并且使我的相机预览严重失真。

First I show the code of my solution (the important parts of the code) and then I explain why I took those steps.首先,我展示了我的解决方案的代码(代码的重要部分),然后我解释了我为什么要采取这些步骤。 There is room for performance modifications.有性能修改的空间。

Main activity xml layout:主要活动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>

Camera Preview:相机预览:

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;
}
}

Main activity:主要活动:

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;
}   
}

My commentary:我的评论:

The point of all this is, that although you calculate the optimal camera size in getOptimalPreviewSize() you only pick the closest ratio to fit your screen.所有这一切的重点是,尽管您在getOptimalPreviewSize()计算了最佳相机尺寸,但您只能选择最接近的比例以适合您的屏幕。 So unless the ratio is exactly the same the preview will stretch.因此,除非比率完全相同否则预览会拉伸。

Why will it stretch?为什么会拉伸? Because your FrameLayout camera preview is set in layout.xml to match_parent in width and height.因为您的 FrameLayout 相机预览在layout.xml 中设置为match_parent的宽度和高度。 So that is why the preview will stretch to full screen.这就是为什么预览会拉伸到全屏的原因。

What needs to be done is to set camera preview layout width and height to match the chosen camera size ratio , so the preview keeps its aspect ratio and won't distort.需要做的是设置相机预览布局的宽度和高度以匹配选择的相机尺寸比例,这样预览保持其纵横比不会失真。

I tried to use the CameraPreview class to do all the calculations and layout changes, but I couldn't figure it out.我尝试使用CameraPreview类来完成所有计算和布局更改,但我无法弄清楚。 I tried to apply this solution , but SurfaceView doesn't recognize getChildCount () or getChildAt (int index) .我尝试应用此解决方案,但SurfaceView无法识别getChildCount ()getChildAt (int index) I think, I got it working eventually with a reference to maLayoutPreview , but it was misbehaving and applied the set ratio to my whole app and it did so after first picture was taken.我想,我最终通过引用maLayoutPreview使其工作,但它行为不端并将设置的比率应用于我的整个应用程序,并且在拍摄第一张照片后就这样做了。 So I let it go and moved the layout modifications to the MainActivity .所以我放手并将布局修改移到MainActivity

In CameraPreview I changed prSupportedPreviewSizes and getOptimalPreviewSize() to public so I can use it in MainActivity .CameraPreview我将prSupportedPreviewSizesgetOptimalPreviewSize()更改为public以便我可以在MainActivity使用它。 Then I needed the display dimensions (minus the navigation/status bar if there is one) and chosen optimal camera size .然后我需要显示尺寸(减去导航/状态栏,如果有的话)并选择最佳相机尺寸 I tried to get the RelativeLayout (or FrameLayout) size instead of display size, but it was returning zero value.我试图获取 RelativeLayout(或 FrameLayout)大小而不是显示大小,但它返回零值。 This solution didn't work for me.这个解决方案对我不起作用。 The layout got it's value after onWindowFocusChanged (checked in the log).布局在onWindowFocusChanged之后得到了它的值(在日志中检查)。

So I have my methods for calculating the layout dimensions to match the aspect ratio of chosen camera size.所以我有我的方法来计算布局尺寸以匹配所选相机尺寸的纵横比。 Now you just need to set LayoutParams of your camera preview layout.现在您只需要设置相机预览布局的LayoutParams Change the width, height and center it in parent.更改宽度、高度并使其在父项中居中。

There are two choices how to calculate the preview dimensions.如何计算预览尺寸有两种选择。 Either you want it to fit the screen with black bars (if windowBackground is set to null) on the sides or top/bottom.您希望它在侧面或顶部/底部带有黑条(如果 windowBackground 设置为 null)适合屏幕。 Or you want the preview zoomed to full screen .或者您希望预览放大到全屏 I left comment with more information in calcCamPrevDimensions() .我在calcCamPrevDimensions()留下了更多信息的评论。

Hi the getOptimalPreview() that is here didn't worked for me so I want to share my version:嗨,这里的 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;
}

Just to make this thread more complete i am adding my version of answer:为了使这个线程更完整,我添加了我的答案版本:

What i wanted to achieve: The surface view shouldn't be stretched, and it should cover the whole screen, Moreover, there was only a landscape mode in my app.我想要实现的是:不应拉伸表面视图,它应该覆盖整个屏幕,此外,我的应用程序中只有横向模式。

Solution:解决方案:

The solution is an extremely small extension to F1sher's solution:该解决方案是 F1sher 解决方案的一个极小的扩展:

=> First step is to integrate F1sher's solution. => 第一步是集成 F1sher 的解决方案。

=> Now, there might arise a scenario in F1sher's solution when the surface view doesn't covers the whole screen, The solution is to make the surface view greater than the screen dimensions so that it covers the whole screen, for that: => 现在,F1sher 的解决方案中可能会出现表面视图没有覆盖整个屏幕的情况,解决方案是使表面视图大于屏幕尺寸,使其覆盖整个屏幕,为此:

    size = getOptimalPreviewSize(mCamera.getParameters().getSupportedPreviewSizes(), screenWidth, screenHeight);

    Camera.Parameters parameters = mCamera.getParameters();
    parameters.setPreviewSize(size.width, size.height);


    mCamera.setParameters(parameters);      

    double screenRatio = (double) screenHeight / screenWidth;
    double previewRatio = (double) size.height / size.width;

    if (previewRatio > screenRatio)     /*if preview ratio is greater than screen ratio then we will have to recalculate the surface height while keeping the surface width equal to the screen width*/
    {
        RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams(screenWidth, (int) (screenWidth * previewRatio));
        params1.addRule(RelativeLayout.CENTER_IN_PARENT);
        flPreview.setLayoutParams(params1);

        flPreview.setClipChildren(false);

        LayoutParams surfaceParams = new LayoutParams(screenWidth, (int) (screenWidth * previewRatio));
        surfaceParams.gravity = Gravity.CENTER;
        mPreview.setLayoutParams(surfaceParams);        
    }
    else     /*if preview ratio is smaller than screen ratio then we will have to recalculate the surface width while keeping the surface height equal to the screen height*/
    {
        RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams((int) ((double) screenHeight / previewRatio), screenHeight);
        params1.addRule(RelativeLayout.CENTER_IN_PARENT);
        flPreview.setLayoutParams(params1);
        flPreview.setClipChildren(false);

        LayoutParams surfaceParams = new LayoutParams((int) ((double) screenHeight / previewRatio), screenHeight);
        surfaceParams.gravity = Gravity.CENTER;
        mPreview.setLayoutParams(surfaceParams);

    }       

    flPreview.addView(mPreview); 

  /*  The TopMost layout used is the RelativeLayout, flPreview is the FrameLayout in which Surface View is added, mPreview is an instance of a class which extends SurfaceView  */

@Hesam 's answer is correct, CameraPreview will work in all portrait devices, but if the device is in landscape mode or in multi-window mode, this code is working perfect, just replace onMeasure() @Hesam 的回答是正确的, CameraPreview将在所有纵向设备中工作,但如果设备处于横向模式或多窗口模式,则此代码工作正常,只需替换onMeasure()

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

    int rotation = ((Activity) mContext).getWindowManager().getDefaultDisplay().getRotation();
    if ((Surface.ROTATION_0 == rotation || Surface.ROTATION_180 == rotation)) {//portrait
        mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, height, width);
    } else
        mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);//landscape

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


    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && ((Activity) mContext).isInMultiWindowMode()) {
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ||
                !(Surface.ROTATION_0 == rotation || Surface.ROTATION_180 == rotation)) {
            setMeasuredDimension(width, (int) (width / ratio));
        } else {
            setMeasuredDimension((int) (height / ratio), height);
        }
    } else {
        if ((Surface.ROTATION_0 == rotation || Surface.ROTATION_180 == rotation)) {
            Log.e("---", "onMeasure: " + height + " - " + width * ratio);
            //2264 - 2400.0 pix c -- yes
            //2240 - 2560.0 samsung -- yes
            //1582 - 1440.0 pix 2 -- no
            //1864 - 2048.0 sam tab -- yes
            //848 - 789.4737 iball -- no
            //1640 - 1600.0 nexus 7 -- no
            //1093 - 1066.6667 lenovo -- no
            //if width * ratio is > height, need to minus toolbar height
           if ((width * ratio) < height)
                setMeasuredDimension(width, (int) (width * ratio));
            else
                setMeasuredDimension(width, (int) (width * ratio) - toolbarHeight);
        } else {
            setMeasuredDimension((int) (height * ratio), height);
        }
    }
    requestLayout();
}

I figured out what's the problem - it is with orientation changes.我想出了什么问题 - 这是方向变化。 If you change camera orientation to 90 or 270 degrees than you need to swap width and height of supported sizes and all will be ok.如果您将相机方向更改为 90 或 270 度,那么您需要交换支持尺寸的宽度和高度,一切都会好起来的。

Also surface view should lie in a frame layout and have center gravity.此外,表面视图应位于框架布局中并具有中心重心。

Here is example on C# (Xamarin):这是 C# (Xamarin) 上的示例:

public void SurfaceChanged(ISurfaceHolder holder, Android.Graphics.Format format, int width, int height)
{
    _camera.StopPreview();

    // find best supported preview size

    var parameters = _camera.GetParameters();
    var supportedSizes = parameters.SupportedPreviewSizes;
    var bestPreviewSize = supportedSizes
        .Select(x => new { Width = x.Height, Height = x.Width, Original = x }) // HACK swap height and width because of changed orientation to 90 degrees
        .OrderBy(x => Math.Pow(Math.Abs(x.Width - width), 3) + Math.Pow(Math.Abs(x.Height - height), 2))
        .First();

    if (height == bestPreviewSize.Height && width == bestPreviewSize.Width)
    {
        // start preview if best supported preview size equals current surface view size

        parameters.SetPreviewSize(bestPreviewSize.Original.Width, bestPreviewSize.Original.Height);
        _camera.SetParameters(parameters);
        _camera.StartPreview();
    }
    else
    {
        // if not than change surface view size to best supported (SurfaceChanged will be called once again)

        var layoutParameters = _surfaceView.LayoutParameters;
        layoutParameters.Width = bestPreviewSize.Width;
        layoutParameters.Height = bestPreviewSize.Height;
        _surfaceView.LayoutParameters = layoutParameters;
    }
}

Pay attention that camera parameters should be set as original size (not swapped), and surface view size should be swapped.注意相机参数设置为原始大小(不交换),表面视图大小应交换。

i tried all the solution above but none of them works for me.我尝试了上述所有解决方案,但没有一个对我有用。 finaly i solved it myself, and find actually it's quite easy.最后我自己解决了,发现实际上很容易。 there are two points you need to be careful.有两点需要注意。

parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);

this previewSize must be one of the camera supported resolution, which can be get as below:这个 previewSize 必须是相机支持的分辨率之一,可以如下获取:

List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes(); 

usually one of the rawSupportedSize equals to the device resolution.通常 rawSupportedSize 之一等于设备分辨率。

Second, place your SurfaceView in a FrameLayout and set the surface layout height and width in surfaceChanged method as above其次,将你的 SurfaceView 放在一个 FrameLayout 中,并在上面的 SurfaceChanged 方法中设置表面布局的高度和宽度

FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) surfaceView.getLayoutParams();
layoutParams.height = cameraResolution.x;
layoutParams.width = cameraResolution.y;

Ok, things done, hope this could help you.好了,事情做完了,希望可以帮到你。

Very important point here to understand , the SurfaceView size must be the same as the camera parameters size , it means they have the same aspect ratio then the Stretch effect will go off .在这里理解很重要的一点,SurfaceView 的大小必须与相机参数的大小相同,这意味着它们具有相同的纵横比,那么 Stretch 效果就会消失。

You have to get the correct supported camera preview size using params.getSupportedPreviewSizes() choose one of them and then change your SurfaceView and its holders to this size.您必须使用 params.getSupportedPreviewSizes() 获得正确支持的相机预览尺寸,选择其中之一,然后将 SurfaceView 及其支架更改为该尺寸。

My requirements are the camera preview need to be fullscreen and keep the aspect ratio.我的要求是相机预览需要全屏并保持纵横比。 Hesam and Yoosuf's solution was great but I do see a high zoom problem for some reason. Hesam 和 Yoosuf 的解决方案很棒,但出于某种原因,我确实看到了高变焦问题。

The idea is the same, have the preview container center in parent and increase the width or height depend on the aspect ratios until it can cover the entire screen.想法是一样的,在父级中设置预览容器中心,并根据纵横比增加宽度或高度,直到它可以覆盖整个屏幕。

One thing to note is the preview size is in landscape because we set the display orientation.需要注意的一件事是预览尺寸是横向的,因为我们设置了显示方向。

camera.setDisplayOrientation(90);相机.setDisplayOrientation(90);

The container that we will add the SurfaceView view to:我们将 SurfaceView 视图添加到的容器:

<RelativeLayout
    android:id="@+id/camera_preview_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerInParent="true"/>

Add the preview to it's container with center in parent in your activity.将预览添加到它的容器中,在您的活动中以父级为中心。

this.cameraPreview = new CameraPreview(this, camera);
cameraPreviewContainer.removeAllViews();
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
cameraPreviewContainer.addView(cameraPreview, 0, params);

Inside the CameraPreview class:在 CameraPreview 类中:

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    // 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 (holder.getSurface() == null) {
        // preview surface does not exist
        return;
    }

    stopPreview();

    // set preview size and make any resize, rotate or
    // reformatting changes here
    try {
        Camera.Size nativePictureSize = CameraUtils.getNativeCameraPictureSize(camera);
        Camera.Parameters parameters = camera.getParameters();
        parameters.setPreviewSize(optimalSize.width, optimalSize.height);
        parameters.setPictureSize(nativePictureSize.width, nativePictureSize.height);
        camera.setParameters(parameters);
        camera.setDisplayOrientation(90);
        camera.setPreviewDisplay(holder);
        camera.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 (supportedPreviewSizes != null && optimalSize == null) {
        optimalSize = CameraUtils.getOptimalSize(supportedPreviewSizes, width, height);
        Log.i(TAG, "optimal size: " + optimalSize.width + "w, " + optimalSize.height + "h");
    }

    float previewRatio =  (float) optimalSize.height / (float) optimalSize.width;
    // previewRatio is height/width because camera preview size are in landscape.
    float measuredSizeRatio = (float) width / (float) height;

    if (previewRatio >= measuredSizeRatio) {
        measuredHeight = height;
        measuredWidth = (int) ((float)height * previewRatio);
    } else {
        measuredWidth = width;
        measuredHeight = (int) ((float)width / previewRatio);
    }
    Log.i(TAG, "Preview size: " + width + "w, " + height + "h");
    Log.i(TAG, "Preview size calculated: " + measuredWidth + "w, " + measuredHeight + "h");

    setMeasuredDimension(measuredWidth, measuredHeight);
}

a lil late for you but hopefully not for everybody.对你来说有点晚了,但希望不是对每个人来说。

What you can use to ensure a specific ration is the attribute:您可以用来确保特定口粮的是属性:

layout_constraintDimensionRatio="ration_a:ratio_b"

It solved my issues.它解决了我的问题。

您必须根据所需的纵横比设置 cameraView.getLayoutParams().height 和 cameraView.getLayoutParams().width。

I gave up the calculations and simply get the size of the view where I want the camera preview displayed and set the camera's preview size the same (just flipped width/height due to rotation) in my custom SurfaceView implementation:我放弃了计算,只是在我的自定义 SurfaceView 实现中获取我想要显示相机预览的视图的大小,并将相机的预览大小设置为相同(只是由于旋转而翻转了宽度/高度):

@Override // CameraPreview extends SurfaceView implements SurfaceHolder.Callback { 
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    Display display = ((WindowManager) getContext().getSystemService(
            Context.WINDOW_SERVICE)).getDefaultDisplay();

    if (display.getRotation() == Surface.ROTATION_0) {
        final Camera.Parameters params = camera.getParameters();
        // viewParams is from the view where the preview is displayed
        params.setPreviewSize(viewParams.height, viewParams.width);
        camera.setDisplayOrientation(90);
        requestLayout();
        camera.setParameters(params);
    }
    // I do not enable rotation, so this can otherwise stay as is
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM