即使设置了正确的尺寸,相机预览也会被拉伸

Posted

技术标签:

【中文标题】即使设置了正确的尺寸,相机预览也会被拉伸【英文标题】:Camera preview stretched even after setting the correct dimensions 【发布时间】:2013-04-10 03:35:30 【问题描述】:

我正在创建一个相机应用程序,它实现了自己的相机预览来拍照。 该应用目前被强制进入纵向模式。

我的问题是相机的预览略微拉伸(纵横比有点偏)。 有趣的是,我将 SurfaceView 的大小设置为始终与预览大小匹配。这确保了纵横比应该始终保持不变......但它不是......

这是我用来显示我的相机预览的布局:

public class Cp extends ViewGroup implements SurfaceHolder.Callback 
    private final String TAG = "CameraPreview";

    private boolean mPreviewRunning = false;

    private SurfaceView mSurfaceView;
    private SurfaceHolder mHolder;
    private Size mPreviewSize;
    private List<Size> mSupportedPreviewSizes;
    private Camera mCamera;

    public boolean IsPreviewRunning() 
        return mPreviewRunning;
    

    public Cp(Context context) 
        this(context, null, 0);
    

    public Cp(Context context, AttributeSet attrs) 
        this(context, attrs, 0);
    

    public Cp(Context context, AttributeSet attrs, int defStyle) 
        super(context, attrs, defStyle);

        mSurfaceView = new SurfaceView(context);
        addView(mSurfaceView);

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = mSurfaceView.getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    

    public void setCamera(Camera camera) 
        mCamera = camera;
        if (mCamera != null) 
            requestLayout();
        
    

    public void switchCamera(Camera camera) 
        setCamera(camera);
        try 
            camera.setPreviewDisplay(mHolder);
         catch (IOException exception) 
            Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
        
        Camera.Parameters parameters = camera.getParameters();
        parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
        requestLayout();

        camera.setParameters(parameters);
    

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
        // We purposely disregard child measurements because act as a wrapper to
        // a SurfaceView that
        // centers the camera preview instead of stretching it.
        final int width = resolveSize(getSuggestedMinimumWidth(),
                widthMeasureSpec);
        final int height = resolveSize(getSuggestedMinimumHeight(),
                heightMeasureSpec);
        setMeasuredDimension(width, height);

        if (mSupportedPreviewSizes == null && mCamera != null) 
            mSupportedPreviewSizes = mCamera.getParameters()
                    .getSupportedPreviewSizes();
        
        if (mSupportedPreviewSizes != null) 
            mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width,
                    height);
        
    

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) 
        if (changed && getChildCount() > 0) 
            final View child = getChildAt(0);

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

            int previewWidth = width;
            int previewHeight = height;
            if (mPreviewSize != null) 
                previewWidth = mPreviewSize.height;
                previewHeight = mPreviewSize.width;
            
            if (previewWidth == 0) 
                previewWidth = 1;
            
            if (previewHeight == 0) 
                previewHeight = 1;
            

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

    @Override
    public void surfaceCreated(SurfaceHolder holder) 
        // The Surface has been created, acquire the camera and tell it where to
        // draw.
        try 
            if (mCamera != null) 
                Parameters params = mCamera.getParameters();
                mSupportedPreviewSizes = params.getSupportedPreviewSizes();
                mCamera.setPreviewDisplay(holder);
            
         catch (IOException exception) 
            Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
        
    

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) 
        // Surface will be destroyed when we return, so stop the preview.
        stop();
    

    private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) 
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) w / h;
        if (sizes == null)
            return null;

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

        int targetHeight = h;

        // Try to find an size match aspect ratio and size
        for (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);
            
        

        // Cannot find the one match the aspect ratio, ignore the requirement
        if (optimalSize == null) 
            minDiff = Double.MAX_VALUE;
            for (Size size : sizes) 
                if (Math.abs(size.height - targetHeight) < minDiff) 
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                
            
        
        return optimalSize;
    

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) 
        if (mCamera != null) 
            // Now that the size is known, set up the camera parameters and
            // begin the preview.
            Camera.Parameters parameters = mCamera.getParameters();
            if (mPreviewSize != null) 
                parameters.setPreviewSize(mPreviewSize.width,
                        mPreviewSize.height);
            
            requestLayout();

            mCamera.setParameters(parameters);
            mCamera.startPreview();
            mPreviewRunning = true;
        
    

    public void stop() 
        if (mCamera != null) 
            mCamera.stopPreview();
            mPreviewRunning = false;
            mCamera = null;
        
    

请注意,在onLayout 中,宽度和高度是交换的,因为应用程序始终以纵向运行。

我添加了一些图片来向您展示问题的样子:

我已经调试了代码。预览大小设置为 1280x720,并且布局大小也正在正确调整以匹配该大小。

我也尝试过不同的布局,但结果总是一样...

【问题讨论】:

相关:Camera Preview Stretched on Few android Devices 【参考方案1】:

我遇到了同样的问题,在这个谜题中玩了几天之后,我的 Java 类最终出现在以下代码上:

所以,问题出现了,因为相机显示屏的尺寸(高 x 宽)为 576x720,而我的显示屏为 1184x720。因此,相机预览(我的表面视图类)被拉伸以填充父级。

因此,有效的方法是让这个视图比我的屏幕大,并且只显示我的屏幕区域。所以,我不得不使用这种奇怪的框架组合(线性布局内的相对布局),这样相对的大小就会像我想要的一样大 - 表面视图将填充它 - 而线性将只占用我想展示的部分。

package com.example.frame.camera;

import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;

import com.example.frame.MainActivity;

public class NativeCamera extends SurfaceView implements SurfaceHolder.Callback 

    static private NativeCamera instance;

    private LinearLayout        frame               = null;
    private RelativeLayout      innerFrame          = null;

    private Camera              camera;
    private final SurfaceHolder previewHolder;
    private static final String TAG                 = "NativeCamera.java";

    private boolean             inPreview           = false;
    private boolean             cameraConfigured    = false;
    private boolean             frontCamera         = false;

    private Camera.Size         size;

    static public NativeCamera getInstance() 
        if (NativeCamera.instance == null) 
            if (MainActivity.debug) 
                Log.d(TAG, "Creating Camera Singleton");
            
            NativeCamera.instance = new NativeCamera(MainActivity.instance);
        
        return NativeCamera.instance;
    

    public void onResume() 
        if (MainActivity.debug) 
            Log.d(TAG, "onResume");
        
        camera = Camera.open();
        if (size != null) 
            initPreview(size.width, size.height);
        
        startPreview();
    

    public void onPause() 
        if (MainActivity.debug) 
            Log.d(TAG, "onPause");
        
        if (inPreview) 
            camera.stopPreview();
        
        camera.release();
        camera = null;
        inPreview = false;
    

    public void onDestroy() 
        if (MainActivity.debug) 
            Log.d(TAG, "onDestroy");
        
        NativeCamera.instance = null;
    

    public void onSwitch() 
        frontCamera = !frontCamera;
        if (inPreview) 
            camera.stopPreview();
        
        camera.release();

        int cam = frontCamera ? 1 : 0;

        camera = Camera.open(cam);
        if (size != null) 
            initPreview(size.width, size.height);
        
        startPreview();
    

    private NativeCamera(Context context) 
        super(context);
        // TODO Auto-generated constructor stub

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        previewHolder = getHolder();
        previewHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        // previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        innerFrame = new RelativeLayout(MainActivity.instance);
        innerFrame.addView(this);
        frame = new LinearLayout(MainActivity.instance);
        frame.addView(innerFrame);
    

    public LinearLayout getFrame() 
        return frame;
    

    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) 
        // TODO Auto-generated method stub
        if (MainActivity.debug) 
            Log.d(TAG, "surfaceChanged");
        
        initPreview(width, height);
        startPreview();
    

    public void surfaceCreated(SurfaceHolder holder) 
        // TODO Auto-generated method stub
        // no-op -- wait until surfaceChanged()

    

    public void surfaceDestroyed(SurfaceHolder holder) 
        // TODO Auto-generated method stub
        // no-op
    

    private Camera.Size getBestPreviewSize(int width, int height,
            Camera.Parameters parameters) 
        Camera.Size result = null;

        for (Camera.Size size : parameters.getSupportedPreviewSizes()) 
            if (size.width <= width && size.height <= height) 
                if (result == null) 
                    result = size;
                 else 
                    int resultArea = result.width * result.height;
                    int newArea = size.width * size.height;

                    if (newArea > resultArea) 
                        result = size;
                    
                
            
        

        this.size = result;
        return (result);
    

    private void initPreview(int width, int height) 
        if (camera != null && previewHolder.getSurface() != null) 

            if (!cameraConfigured) 
                Camera.Parameters parameters = camera.getParameters();
                Camera.Size size = getBestPreviewSize(width, height, parameters);

                if (size != null) 
                    parameters.setPreviewSize(size.width, size.height);
                    camera.setParameters(parameters);
                    cameraConfigured = true;
                    // Setting up correctly the view
                    double ratio = size.height / (double) size.width;
                    LayoutParams params = innerFrame.getLayoutParams();
                    params.height = MainActivity.size.y;
                    params.width = (int) (MainActivity.size.y * ratio);
                    innerFrame.setLayoutParams(params);
                    int deslocationX = (int) (params.width / 2.0 - MainActivity.size.x / 2.0);
                    innerFrame.animate().translationX(-deslocationX);
                
            

            try 
                camera.setPreviewDisplay(previewHolder);
                camera.setDisplayOrientation(90);

             catch (Throwable t) 
                Log.e(TAG, "Exception in setPreviewDisplay()", t);
                Toast.makeText(MainActivity.instance, t.getMessage(), Toast.LENGTH_LONG).show();
            

        
    

    private void startPreview() 
        if (MainActivity.debug) 
            Log.d(TAG, "startPreview");
        
        if (cameraConfigured && camera != null) 
            camera.startPreview();
            inPreview = true;
        
    


【讨论】:

尚未对其进行全面测试,但这似乎确实有效。不错。 这个解决方案对我不起作用。请参考我的问题***.com/questions/21107208/… getBestPreviewSize() 总是给我较小尺寸的相机,同样的问题会发生视图拉伸。 @Eduardo Reis:我正在尝试像 instagram 一样实现相机预览,但相机 streatch。预览不正确。你能帮我吗 ?提前致谢。等待您的重播。【参考方案2】:

我一直在尝试解决同样的问题...下面的代码对我有用:

private void setMyPreviewSize(int width, int height) 
    // Get the set dimensions
    float newProportion = (float) width / (float) height;

    // Get the width of the screen
    int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
    int screenHeight = getWindowManager().getDefaultDisplay().getHeight();
    float screenProportion = (float) screenWidth / (float) screenHeight;

    // Get the SurfaceView layout parameters
    android.view.ViewGroup.LayoutParams lp = surfaceView.getLayoutParams();
    if (newProportion > screenProportion) 
        lp.width = screenWidth;
        lp.height = (int) ((float) screenWidth / newProportion );
     else 
        lp.width = (int) (newProportion * (float) screenHeight);
        lp.height = screenHeight;
    
    // Commit the layout parameters
    surfaceView.setLayoutParams(lp);

可以看帖:Resizing surface view for aspect ratio change in video display in android

【讨论】:

【参考方案3】:

解决办法

surfaceChanged 只是为了获得最佳预览尺寸

@Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) 
                Log.e("MYTAG", "surfaceChanged " );

                Camera.Parameters myParameters = camera.getParameters();
                Camera.Size myBestSize = getBestPreviewSize(width, height, myParameters);

                if(myBestSize != null)
                    myParameters.setPreviewSize(myBestSize.width, myBestSize.height);
                    camera.setParameters(myParameters);
                    camera.startPreview();


                
            



@Override
    public void onPictureTaken(byte[] data, Camera camera) 

        Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
       imageReview.setImageBitmap(bitmap);
    

这会将捕获的图像设置在imageReview上,但是您需要一个方法来获取位图的旋转

您需要为拉伸图像问题设置 imageView scaleType 属性

<ImageView
        android:id="@+id/imageView"
        android:layout_
        android:layout_
        android:scaleType="centerCrop"
        />

【讨论】:

以上是关于即使设置了正确的尺寸,相机预览也会被拉伸的主要内容,如果未能解决你的问题,请参考以下文章

相机表面视图图像看起来拉伸

2960x1440 拉伸 Android 相机预览

计算最佳相机预览尺寸的正确方法保持纵横比

设置缩小的相机预览以相对于宽度具有正确的图像纵横比

在android中将相机预览拉伸到全屏

相机预览被拉伸和倾斜