扩展了Android相机预览


136

我一直在尝试在Android上进行自定义摄像头活动,但是在旋转摄像头时,表面视图的纵横比会变得混乱。

在活动的oncreate中,我设置了框架布局,该框架布局保留了显示相机参数的表面视图。

//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);

然后,在“曲面”视图中,设置要显示的相机参数

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

    }

在此处输入图片说明

您会看到,旋转手机时,乐高男人变得越来越高,越来越瘦:

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


@scientific您能帮我在哪里遇到同样问题的情况下编写给定方法的地方吗?
user3233280 2014年

我似乎遇到了类似的问题,但是修复该问题的原因是调用了以下内容:我的相机的SurfaceView的surfaceCreated()方法中的mCamera.setDisplayOrientation(90)
Greg

Answers:


163

我正在使用此方法->基于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;
    }

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

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

然后在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);
        }
    }

然后(在我的surfaceChanged方法的代码中,就像我说的那样,我正在使用CameraActivity代码的API Demos结构,您可以在Eclipse中生成它):

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

还有一个提示给您,因为我做过几乎和您一样的应用程序。相机活动的好习惯是隐藏StatusBar。Instagram之类的应用程序正在这样做。它会减小屏幕高度值并更改比率值。在某些设备上可能会获得奇怪的预览尺寸(SurfaceView会被剪掉一些)


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

mCamera.setParameters(parameters);

您设置的比例等于高度/宽度。如果要让摄像机在屏幕上看起来不错,则您设置为摄像机的参数的高度/宽度比必须与屏幕的高度(负状态栏)/宽度比相同。


2
只是一个问题:我看到您正在使用2种不同的计算比率:double targetRatio =(double)h / wdouble ratio =(double)size.width / size.height。第一个是高度除以宽度,另一个则相反,宽度除以高度。它们应该不是同一计算吗?
phyzalis 2014年

没关系,因为大小列表会考虑各种可能性,例如:您会找到480x680之类的东西,但是迟早会有680x480(用于风景),因此您只需要遍历该列表并找到一个符合您的需求。您迟早会发现它。
F1sher 2014年

此外,如果您希望图片也是如此(可能应该如此),则可以使用设置图片大小parameters.setPictureSize(mPreviewSize.width, mPreviewSize.height)
马特·洛根

2
@ F1sher,我总是得到空指针异常参数。setPreviewSize(mPreviewSize.width,mPreviewSize.height); 找不到任何解决方案。
FIXI

3
@phyzalis我同意你的看法。实际上,为了使代码按预期工作,我不得不进行了更正。
fernio

149

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

11
这应该是正确的答案,它可以处理相机预览未覆盖整个屏幕的情况。+1。
Houcine 2014年

1
当我在活动应用程序中充气时关闭<com.app.camera.CameraPreview android:id =“ @ + id / camera_preview” android:layout_width =“ match_parent” android:layout_height =“ match_parent” />
fish40 2014年

尽管您需要进行一些修改,但是我接受这作为解决方案。投票赞成。
JaydeepW 2014年

2
@ adnan9011,您好,本教程可能会有所帮助。airpair.com/android/android-camera-surface-view-fragment
Hesam

4
高度和宽度需要切换getOptimalPreviewSize才能对我有用。是因为显示方向设置为90?否则,比率计算甚至无法完成(目标是1.7,摄像机比率小于1)
2015年

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

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

在此处输入图片说明

接下来,您知道的是:D

在此处输入图片说明

这对于前置摄像头也很有效。我相信这是解决此问题的最佳方法。现在,留给我的应用程序的唯一一件事就是在单击“捕获”时保存预览本身。但是,是的。


这个效果很好!但在风景中无法正常使用:/
Matan Dahan

您的代码解决了我2个月的空白空间问题:)
karthik kolanji 2016年

1
它可以工作,但是以我为例。.我在顶部有操作栏,因此白色栏较小,但是在添加代码后,我的相机看起来放大了40-50%。切换到前置摄像头时,由于具有很高的变焦,我基本上看不到我的脸笔直(只是一小撮头发)。
Makalele '16

@Yoosuf当我遵循您的代码时,我正在白屏显示。在onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法中,我总是得到width = 0和height = 0;
库什·帕特尔

10

好的,所以我认为对于一般的相机预览拉伸问题没有足够的答案。或者至少我没有找到一个。我的应用程序也遭受了这种拉伸综合症,花了我一段时间才从该门户网站和互联网上的所有用户答案中解惑。

我尝试了@Hesam的解决方案,但没有成功,并且导致我的相机预览严重失真。

首先,我显示解决方案的代码(代码的重要部分),然后解释为什么要执行这些步骤。有性能修改的空间。

主要活动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>

相机预览:

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

主要活动:

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

我的评论:

所有这些的要点是,尽管您在计算最佳摄像机尺寸getOptimalPreviewSize()仅选择最接近的比例以适合您的屏幕。因此,除非比率完全相同否则预览将伸展。

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

需要做的是设置摄像机预览布局的宽度和高度,以匹配所选的摄像机尺寸比率,以便预览保持其长宽比并且不会变形。

我试图使用CameraPreview该类来进行所有计算和布局更改,但无法弄清楚。我尝试应用此解决方案,但SurfaceView无法识别getChildCount ()getChildAt (int index)。我认为,我最终通过参考来使它正常工作maLayoutPreview,但是它工作不正常,并且将设置比例应用于我的整个应用程序,并且在拍摄第一张照片后才这样做。因此,我放手了,并将布局修改移至MainActivity

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

因此,我有一些方法可以计算布局尺寸以匹配所选摄像机尺寸的纵横比。现在,您只需要设置LayoutParams摄像机预览布局即可。更改宽度,高度并将其居中放置在父级中。

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


6

嗨,这里的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;
}

将其转换为方形摄像头,在优化宽度和高度(1088x1088)后,输入为(1920x1080),我的测试设备为Samsung S6。请问是什么原因。如果您对这个问题有任何想法,请分享。
MohanRaj S

2

为了使这个线程更完整,我添加了我的答案版本:

我想要实现的目标:不应拉伸表面视图,它应该覆盖整个屏幕,而且,我的应用程序中只有横向模式。

解:

该解决方案是对F1sher解决方案的极小的扩展:

=>第一步是集成F1sher的解决方案。

=>现在,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  */

1

我发现了问题所在- 方向改变了。如果将相机方向更改为90或270度,则需要交换支持尺寸的宽度和高度,一切都可以。

另外,表面视图应位于框架布局中并具有重心。

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

请注意,相机参数应设置为原始尺寸(不交换),并且表面视图尺寸应交换。


1

我尝试了上面所有的解决方案,但没有一个适合我。最后我自己解决了,发现实际上很简单。您需要注意两点。

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

该PreviewSize必须是相机支持的分辨率之一,可以通过以下方式获得:

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

通常,rawSupportedSize之一等于设备分辨率。

其次,将SurfaceView放置在FrameLayout中,并按上述方法在surfaceChanged方法中设置表面布局的高度和宽度

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

好的,事情已经完成,希望对您有所帮助。


0

我的要求是相机预览需要全屏显示并保持宽高比。Hesam和Yoosuf的解决方案很棒,但由于某些原因,我确实看到了高变焦问题。

想法是相同的,将预览容器的中心置于父级,并根据宽高比增加宽度或高度,直到它可以覆盖整个屏幕为止。

要注意的一件事是预览大小是横向的,因为我们设置了显示方向。

camera.setDisplayOrientation(90);

我们将SurfaceView视图添加到的容器:

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

将预览添加到其容器中,并在活动中将其父级居中。

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

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

-1

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


这与通过这种方式(我当前正在执行)设置摄像机视图的参数不同:mCamera.setParameters(parameters);
科学

-2

我放弃了计算,只是简单地获取了要显示相机预览的视图的大小,并在我的自定义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
}

Android文档说:设置预览大小时,必须使用getSupportedPreviewSizes()中的值。不要在setPreviewSize()方法中设置任意值。
G. Steigert '17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.