如何在Android中修复相机预览(surfaceview)的正确纵横比?

Posted

技术标签:

【中文标题】如何在Android中修复相机预览(surfaceview)的正确纵横比?【英文标题】:How to fix the right aspect ratio of the camera preview (surfaceview) in Android? 【发布时间】:2017-05-03 21:21:45 【问题描述】:

我正在创建带有相机功能的 android 应用。 相机屏幕包含顶部的工具栏、工具栏下方的surfaceview(相机预览)以及屏幕底部的相机控制按钮。屏幕始终是纵向的。

[删除部分与问题无关的代码]

这是我的片段FragmentCamera

import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.dmc.R;
import com.dmc.entities.Preview;

public class CameraFragment implements View.OnClickListener, View.OnTouchListener 

    public static final String ARG_CAMERA_MODE = "camera.mode";
    public static final String TYPE_CAMERA_MODE_IMAGE = "image";
    public static final String TYPE_CAMERA_MODE_VIDEO = "video";
    public MediaRecorder mrec = new MediaRecorder();

    private Camera camera;
    private String mCameraMode = TYPE_CAMERA_MODE_IMAGE; //or video
    private com.dmc.entities.Preview preview;
    private ImageView btnStopRecording;
    private SurfaceView surfaceView;
    private View view;

    public static FrCamera getInstance(String cameraMode) 
        CameraFragment fragment = new CameraFragment();
        Bundle bundle = new Bundle(1);
        bundle.putString(ARG_CAMERA_MODE, cameraMode);
        return fragment.setArguments(bundle);
    

    @Override
    public void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        mCameraMode = getArguments().getString(ARG_CAMERA_MODE);
    

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
        view = inflater.inflate(R.layout.fragment_camera, container, false);
        view.setOnTouchListener(this);
        btnStopRecording = (ImageView) view.findViewById(R.id.btnStopRecording);
        if (!mCameraMode.equals(TYPE_CAMERA_MODE_IMAGE)) 
            btnStopRecording.setOnClickListener(this);
        
        surfaceView = (SurfaceView) view.findViewById(R.id.surfaceView);
        view.findViewById(R.id.imgCameraTakePicture).setOnClickListener(this);

        preview = new Preview(getActivity(), (SurfaceView) view.findViewById(R.id.surfaceView));
        preview.setKeepScreenOn(true);
        return view;
    

    @Override
    public void onStart() 
        super.onStart();

        int numCams = Camera.getNumberOfCameras();
        if (numCams > 0) 
            try 
                camera = Camera.open(0);
                preview.setCamera(camera);
                camera.startPreview();
             catch (RuntimeException ex) 
            
        
    

    @Override
    public void onPause() 
        if (camera != null) 
            camera.stopPreview();
            preview.setCamera(null);
            camera.release();
            camera = null;
        
        super.onPause();
    

    @Override
    public void onResume() 
        super.onResume();
        camera.startPreview();
    

    private void startVideoRecording() 
        try 
            mrec = new MediaRecorder();
            mrec.setCamera(camera);
            mrec.setVideoSource(MediaRecorder.VideoSource.CAMERA);
            mrec.setAudiosource(MediaRecorder.AudioSource.MIC);
            CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
            mrec.setProfile(profile);
            camera.lock();
            camera.unlock();
            mrec.setPreviewDisplay(preview.mHolder.getSurface());
            mrec.setOutputFile(outVideoFile.getPath());
            mrec.setOrientationHint(Preview.rotate);
            mrec.prepare();
            mrec.start();
         catch (Exception ex) 
            Log.e(getClass().getName(), ex.getMessage());
        
    

    protected void stopRecording() 
        if (mrec != null) 
            mrec.stop();
            mrec.release();
        
    

    @Override
    public void onClick(View v) 
        switch (v.getId()) 
            case R.id.imgCameraTakenPicture:
                // Save image
                break;
            case R.id.btnStopRecording:
                stopRecording();
                break;
            case R.id.imgCameraTakePicture:
                if (mCameraMode.equals(TYPE_CAMERA_MODE_IMAGE)) 
                    camera.takePicture(shutterCallback, rawCallback, jpegCallback);
                 else
                    startVideoRecording();
                break;
        
    

这是预览

import android.app.Activity;
import android.content.Context;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;

import java.io.IOException;
import java.util.List;

public class Preview extends ViewGroup implements SurfaceHolder.Callback 
    public static final float RATIO = 0.75f;
    public static int rotate;

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

    public Preview(Context context, SurfaceView sv) 
        super(context);
        mSurfaceView = sv;
        mHolder = mSurfaceView.getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    

    public void setCamera(Camera camera) 
        mCamera = camera;
        if (mCamera != null) 
            mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
            requestLayout();
            // get Camera parameters
            Camera.Parameters params = mCamera.getParameters();

            List<String> focusModes = params.getSupportedFocusModes();
            if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) 
                params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
             else 
                params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
            

            params.setJpegThumbnailQuality(100);
            params.setJpegQuality(100);

            // Configure image format. RGB_565 is the most common format.
            List<Integer> formats = params.getSupportedPictureFormats();
            if (formats.contains(PixelFormat.RGB_565))
                params.setPictureFormat(PixelFormat.RGB_565);
            else if (formats.contains(PixelFormat.JPEG))
                params.setPictureFormat(PixelFormat.JPEG);
            else params.setPictureFormat(formats.get(0));

            Camera.CameraInfo camInfo = new Camera.CameraInfo();
            Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, camInfo);
            int cameraRotationOffset = camInfo.orientation;

            Camera.Parameters parameters = mCamera.getParameters();

            int rotation = ((Activity) getContext()).getWindowManager().getDefaultDisplay().getRotation();
            int degrees = 0;
            switch (rotation) 
                case Surface.ROTATION_0:
                    degrees = 0;
                    break; // Natural orientation
                case Surface.ROTATION_90:
                    degrees = 90;
                    break; // Landscape left
                case Surface.ROTATION_180:
                    degrees = 180;
                    break;// Upside down
                case Surface.ROTATION_270:
                    degrees = 270;
                    break;// Landscape right
            
            int displayRotation;
            if (camInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) 
                displayRotation = (cameraRotationOffset + degrees) % 360;
                //displayRotation = (360 - displayRotation) % 360; // compensate the mirror
             else  // back-facing
                displayRotation = (cameraRotationOffset - degrees + 360) % 360;
            
            mCamera.setDisplayOrientation(displayRotation);

            if (camInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) 
                rotate = (360 + cameraRotationOffset + degrees) % 360;
             else 
                rotate = (360 + cameraRotationOffset - degrees) % 360;
            

            parameters.set("orientation", "portrait");
            parameters.setRotation(rotate);

            mCamera.setParameters(params);
        
    

    @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.width;
                previewHeight = mPreviewSize.height;
            

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

    public void surfaceCreated(SurfaceHolder holder) 
        try 
            if (mCamera != null) 
                mCamera.setPreviewDisplay(holder);
                mCamera.startPreview();
            
         catch (IOException exception) 
    

    public void surfaceDestroyed(SurfaceHolder holder) 
        if (mCamera != null) 
            mCamera.stopPreview();
            mCamera.release();
        
    

    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) 
        if (mHolder.getSurface() == null) 
            return;
        
        stopPreview();
        setCamera(mCamera);
        startPreview();
        mCamera.startPreview();
    

    public void startPreview() 
        try 
            if (mCamera != null) 
                mCamera.setPreviewDisplay(mHolder);
                mCamera.startPreview();
             
         catch (Exception e) 
    

    public void stopPreview() 
        try 
            if (mCamera != null)
                mCamera.stopPreview();
         catch (Exception e) 
    

预期结果在左侧的图像上。获得的结果在右侧的图像上。相机预览被拉伸。如何修复相机预览的正确纵横比?

【问题讨论】:

有什么解决办法吗? @迪伦 如果我理解正确,预览。 onLayout() 永远不会被调用,因为这个视图没有添加到内容视图中,以及其他问题。你的课程应该extend SurfaceView,并在运行时被夸大,因为在你开始之前你不知道相机的纵横比。 【参考方案1】:

Camera1 和 Android 4-5 的源代码:

@Override
protected void onResume() 
    super.onResume();
    camera = Camera.open(CAMERA_ID);
    setPreviewSize();


@Override
protected void onPause() 
    super.onPause();
    if (camera != null)
        camera.release();
    camera = null;

...
void setPreviewSize() 
    // получаем размеры экрана
    Display display = getWindowManager().getDefaultDisplay();
    int w1 = display.getWidth();
    int h1 = display.getHeight();
    boolean widthIsMax = display.getWidth() > display.getHeight();

    // определяем размеры превью камеры
    Camera.Size size = camera.getParameters().getPreviewSize();

    RectF rectDisplay = new RectF();
    RectF rectPreview = new RectF();

    // RectF экрана, соотвествует размерам экрана
    rectDisplay.set(0, 0, w1, h1);

    // подготовка матрицы преобразования
    Matrix matrix = new Matrix();
    // RectF первью
    if (widthIsMax) 
        // превью в горизонтальной ориентации
        rectPreview.set(0, 0, size.width, size.height);

        // если экран будет "втиснут" в превью (третий вариант из урока)
        matrix.setRectToRect(rectPreview, rectDisplay,
                Matrix.ScaleToFit.START);
     else 
        // превью в вертикальной ориентации
        rectPreview.set(0, 0, size.height, size.width);

        // если превью будет "втиснут" в экран (второй вариант из урока)
        matrix.setRectToRect(rectPreview, rectDisplay,
                Matrix.ScaleToFit.START);
    

    // преобразование
    matrix.mapRect(rectPreview);

    // установка размеров surface из получившегося преобразования
    h0 = (int) (rectPreview.bottom);
    w0 = (int) (rectPreview.right);

    surfaceView.getLayoutParams().height = h0;
    surfaceView.getLayoutParams().width = w0;

见http://startandroid.ru/ru/uroki/vse-uroki-spiskom/264-urok-132-kamera-vyvod-izobrazhenija-na-ekran-obrabotka-povorota.html

【讨论】:

【参考方案2】:

以下是Camera Preview Stretched的解决方案

    它可以制作我的自定义相机活动 我设置了 framelayout,它包含显示相机参数的表面视图。
 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;
            
        

参考链接: Android Camera Preview Stretched

【讨论】:

以上是关于如何在Android中修复相机预览(surfaceview)的正确纵横比?的主要内容,如果未能解决你的问题,请参考以下文章

android中没有Surface View的媒体管道中的地标扣除

如何将相机预览传递给 MediaCodec.createInputSurface() 创建的 Surface?

Android 相机预览应用中的 Nexus 5x 反向横向传感器修复

尝试在 SurfaceView 中设置时拉伸相机预览

如何在android中制作圆形的相机预览?

如何使用 Android 在相机预览中添加卡通脸