Camera Preview Stretched on Few Android Devices

2019-01-07 11:06发布

问题:

I am making custom Camera like "SnapChat" for a Android app but camera preview is stretched on Few devices like(Moto g second generation , one+ one) but not on(Samsung s3, Samsung s4). I have use the following reference Camera display / preview in full screen does not maintain aspect ratio - image is skewed, stretched in order to fit on the screen. But this does not help me 100%. I am sharing the screen .

Stretched image on Samsung Moto G second generation is .

Samsung S3 images which does not stretched is Above

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:

Using getOptimalPreviewSize() is important, but it does not resolve all stretching on all devices in all layouts. You must be prepared to crop the preview a little bit so that the preview fills the screen without distortion.

There are different techniques to force the size of the surface different from the actual screen size, but this one I find the easiest:

I add the CameraView to my layout:

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

To emphasize the main idea, I did not include error handling, and I did not show here how the camera is actually started with a secondary Looper.



回答2:

You need to override the onMeasure method of SurfaceView class to get width and height of 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);
        }
    }

Then set the preview size you got to the camera parameters preview size.

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

getSuggestedMinimumHeight(), getSuggestedMinimumWidth() are the methods of View class

Hope this helps !!!



回答3:

try this it is working...

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