相机预览在少数 Android 设备上延伸

Posted

技术标签:

【中文标题】相机预览在少数 Android 设备上延伸【英文标题】:Camera Preview Stretched on Few Android Devices 【发布时间】:2015-07-14 23:16:42 【问题描述】:

我正在为 android 应用程序制作像“SnapChat”这样的自定义相机,但相机预览在少数设备(如(Moto g 第二代,一加一)上拉伸,但不是(三星 s3,三星 s4)。我使用了以下参考资料Camera display / preview in full screen does not maintain aspect ratio - image is skewed, stretched in order to fit on the screen。但这对我没有 100% 的帮助。我正在共享屏幕。

三星 Moto G 第二代的拉伸图像是。

未拉伸的三星S3图片在上方

private void setPreviewLayout() 
    if (null == mCamera) 
        return;
    
    Camera.Parameters parameters = null;
    Camera.Size size = null;
    try 
        int screenWidth = (int) getResources().getDisplayMetrics().widthPixels;
        int screenHeight = (int) getResources().getDisplayMetrics().heightPixels;
        parameters = mCamera.getParameters();
        size = getOptimalPreviewSize(mCamera.getParameters().getSupportedPreviewSizes(), screenWidth, screenHeight);
        if (size != null) 

            parameters.setPreviewSize(size.width, size.height);

        

        parameters.setPictureSize(screenHeight, screenWidth);
        ;
        mCamera.setParameters(parameters);
        if (on && currentCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) 
            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_ON);
         else 
            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
        
        parameters.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
        parameters.setExposureCompensation(0);
        parameters.setPictureFormat(ImageFormat.JPEG);
        parameters.setJpegQuality(100);
        List<String> focusModes = parameters.getSupportedFocusModes();
        if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) 
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
         else if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) 
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        
        mCamera.setParameters(parameters);
        /*
         * camera.setPreviewDisplay(surfaceHolder); camera.startPreview();
         */

     catch (Exception e) 
        e.printStackTrace();
    


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;

【问题讨论】:

谁能帮帮我? 【参考方案1】:

使用getOptimalPreviewSize() 很重要,但它并不能解决所有布局中所有设备上的所有拉伸问题。您必须准备好对预览进行一点裁剪,以便预览填满屏幕而不会失真。

有不同的技术可以强制使表面的大小与实际屏幕大小不同,但我认为这是最简单的一种:

我将CameraView 添加到我的布局中:

public class CameraView extends SurfaceView implements SurfaceHolder.Callback 

public CameraView(Context context, AttributeSet attr) 
    super(context, attr);

    // install a SurfaceHolder.Callback so we get notified when the
    // underlying surface is created and destroyed.
    getHolder().addCallback(this);


@Override public void surfaceCreated(SurfaceHolder holder) 
    openCamera();
    bCameraInitialized = false;


@Override public void surfaceDestroyed(SurfaceHolder holder) 
    camera.release();


@Override public void surfaceChanged(SurfaceHolder holder,
        int format, int w, int h) 
        if (bCameraInitialized) 
            // we will get here after we have resized the surface, see below 
            return;
        
        cameraSetup(w, h);
        bCameraInitialized = true;


private void cameraSetup(int w, int h) 
    // set the camera parameters, including the preview size

    FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
    double cameraAspectRatio = ((double)optimalSize.width)/optimalSize.height;

    if (((double)h)/w > cameraAspectRatio) 
        lp.width = (int)(h/cameraAspectRatio+0.5);
        lp.height = h;
    
    else 
        lp.height = (int)(w*cameraAspectRatio + 0.5);
        lp.width = w;
        lp.topMargin = (h - lp.height)/2;
    
    lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;

    setLayoutParams(lp);
    requestLayout();

为了强调主旨,我没有包括错误处理,这里也没有展示相机is actually started with a secondary Looper是怎么做的。

【讨论】:

我使用了您的代码,但这并不能解决我的问题。我正在使用 TextureView.SurfaceTextureListener 而不是 Surface 视图。你可以在Skype sachin.auxiliumit 上加我或给我你的Skype id。我真的会非常感谢你。 直到明天我才能在 Skype 上使用。我相信即使使用 TextureView 你仍然有相同的布局事件。您可以使用onMeasure() 如上所述,或OnLayoutListener - 无论是什么类型,您都需要为您的视图设置固定的宽度和高度 - 并可能在其容器中居中。 @SandeepVishwakarma 我面临同样的问题。我正在使用camera2和TextureView。您找到解决方案了吗?【参考方案2】:

需要重写SurfaceView类的onMeasure方法来获取surfaceview的宽高

@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(mCamera.getParameters().getSupportedPreviewSizes(), width, height);
        
    

然后将你得到的预览尺寸设置为相机参数预览尺寸。

cameraParameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
mCamera.setParameters(cameraParameters);

getSuggestedMinimumHeight()、getSuggestedMinimumWidth()是View类的方法

希望对你有帮助!!!

【讨论】:

嗨 Lakshay,我使用的是 TextureView.SurfaceTextureListener 而不是表面视图。它没有方法 onMeasure 。你能告诉我我该怎么做吗? 你能详细说明一下你的代码吗?将不得不看到,因为我从未使用过讨论的 TextureView 侦听器【参考方案3】:

试试这个,它正在工作......

 public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback 

private static final String TAG = "Camera Preview";
private static final double PREVIEW_SIZE_FACTOR = 3.00;
private SurfaceHolder mHolder = null;
private Camera mCamera = null;
Camera.Parameters _parameters=null;

@SuppressWarnings("deprecation")
public CameraPreview(Context context, Camera camera) 
    super(context);
    mCamera = camera;
    // 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) 
    // The Surface has been created, now tell the camera where to draw the preview.
    try 
        Camera.Parameters parameters = mCamera.getParameters();
        final Size mPreviewSize = getOptimalSize();
        parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
         requestLayout();
        mCamera.setParameters(parameters);
        mCamera.setPreviewDisplay(holder);
        mCamera.startPreview();
        //startContinuousAutoFocus();
     catch (Exception e) 
        Log.d(TAG, "Error setting camera preview: " + e.getMessage());
    

public void surfaceDestroyed(SurfaceHolder holder) 


@SuppressLint("NewApi")
public void surfaceChanged(SurfaceHolder holder, int format, int w, int 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
    // set Camera parameters

    // start preview with new settings
    try 
        mCamera.setPreviewDisplay(mHolder);
        mCamera.startPreview();

     catch (Exception e)
        Log.d(TAG, "Error starting camera preview: " + e.getMessage());
    
    
public void startPreview()
    try
        mCamera.startPreview();
    catch (Exception e) 
    
         
        private Size getOptimalSize() 
    Camera.Size result = null;
    final Camera.Parameters parameters = mCamera.getParameters();
    Log.i(CameraPreview.class.getSimpleName(), "window width: " + getWidth() + ", height: " + getHeight());
    for (final Camera.Size size : parameters.getSupportedPreviewSizes()) 
        if (size.width <= getWidth() * PREVIEW_SIZE_FACTOR && size.height <= getHeight() * PREVIEW_SIZE_FACTOR) 
            if (result == null) 
                result = size;
             else 
                final int resultArea = result.width * result.height;
                final int newArea = size.width * size.height;

                if (newArea > resultArea) 
                    result = size;
                
            
        
    
    if (result == null) 
        result = parameters.getSupportedPreviewSizes().get(0);
    
    Log.i(CameraPreview.class.getSimpleName(), "Using PreviewSize: " + result.width + " x " + result.height);
    return result;


【讨论】:

这不起作用。我正在使用 TextureView.SurfaceTextureListener 而不是表面视图,你能告诉我我该怎么做。

以上是关于相机预览在少数 Android 设备上延伸的主要内容,如果未能解决你的问题,请参考以下文章

Android相机预览人像比例

相机预览在 Android 上首次加载时不显示

Xamarin android Camera2 - 在 18:9 纵横比设备上拉伸图像预览

Android:如何通过回调显示相机预览?

在android中使用没有预览或表面的相机

使用 PreviewView 来展示相机预览