在一些安卓设备上,相机预览画面被拉伸了。

28

我正在为一个安卓应用制作像“SnapChat”一样的自定义相机,但在某些设备上(如Moto g第二代,one+ one),相机预览会被拉伸,而在Samsung s3和Samsung s4上则不会。我已经使用了以下参考资料,但这对我没有百分之百的帮助。我分享了屏幕。

在Samsung Moto G第二代上被拉伸的图像如下: enter image description here enter image description here

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

有人可以帮我吗? - Sandeep Vishwakarma
3个回答

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

为了强调主要思想,我没有包含错误处理,并且我没有在这里展示相机如何使用辅助Looper来实际启动

我已经使用了你的代码,但是这并没有解决我的问题。我正在使用TextureView.SurfaceTextureListener而不是Surface view。你可以添加我的Skype账号sachin.auxiliumit或者告诉我你的Skype账号。非常感谢你。 - Sandeep Vishwakarma
我直到明天最后才能在Skype上可用。我相信即使使用TextureView,您仍然具有相同的布局事件。您可以像上面解释的那样使用 onMeasure()OnLayoutListener - 所有您需要做的就是为视图设置固定的宽度和高度,无论其类型如何 - 并且可能将其居中在其容器中。 - Alex Cohn
@SandeepVishwakarma 我也遇到了同样的问题。我正在使用camera2和TextureView。你找到解决方案了吗? - TOP

1

您需要覆盖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而不是Surface View。它没有onMeasure方法。你能告诉我该怎么做吗? - Sandeep Vishwakarma
你能否更详细地解释一下你的代码呢?因为我从未使用过所讨论的TextureView监听器,所以需要看一下。 - lakshay

0

试试这个,它有效的...

 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而不是SurfaceView,你能告诉我该怎么做吗? - Sandeep Vishwakarma

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接