三星S9(18:9)相机预览拉长

时间:2018-11-07 05:24:28

标签: android performance android-layout android-camera

我正在使用Android Camera API,并且在纵向和横向模式下都可以使用16:9比例的设备。但是在三星S9 18:9比例设备中,横向模式下的预览看起来很拉伸。在Samsung S9上,我遵循以下受支持的预览尺寸:1920X1080,1440X1080, 1088X1088,1280X720,1056X704, 1024X768, 960X720,800X450,720X720,720X480,640X480,352X288,320X240,256X144,176X144,因此最佳的预览尺寸为1920X1080,但设备的实际分辨率为2,220 x 1,080。看起来很长。但是我需要全屏预览。默认的相机预览如何在全屏上显示?

@SuppressLint("ClickableViewAccessibility")
    @SuppressWarnings("deprecation")
    public CameraPreview(Context context, Camera.PreviewCallback previewCallback) {
        super(context);
        this.previewCallback = previewCallback;
        mContext = context;
        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void setCameraDisplayOrientation(Context activity,
                                            int cameraId, Camera camera) {
        Camera.CameraInfo info =
                new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        int rotation = ((AppCompatActivity) activity).getWindowManager().getDefaultDisplay()
                .getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }

        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;  // compensate the mirror
        } else {  // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }


        mDisplayOrientation = result;

        Log.d(TAG, "setCameraDisplayOrientation: "+mDisplayOrientation);

        camera.setDisplayOrientation(result);

    }



    public void takePhoto(final PictureCallback pCalback) {
        mCamera.takePicture(null, null, pCalback);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, acquire the camera and tell it where
        // to draw.
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.setPreviewCallback(null);
            mCamera.release();
            mCamera = null;
            //previewCount = 0;
        }

        try {
            mCamera = Camera.open();


            //setCameraDisplayOrientation(mContext, 0, mCamera);
            /*mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
            DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
            int screenWidth = displayMetrics.widthPixels;
            int screenHeight = displayMetrics.heightPixels;
            optimalPreviewSize = getBestAspectPreviewSize(mParameters.getSupportedPreviewSizes(), screenWidth, screenHeight);//Bug Fix for Samsung A8
            mParameters.setPreviewSize(optimalPreviewSize.width, optimalPreviewSize.height);
            mParameters.setPictureSize(optimalPreviewSize.width, optimalPreviewSize.height);
            mParameters.setPreviewFpsRange(30000, 30000);
            mCamera.setParameters(mParameters);*/
            /*mCamera.setPreviewDisplay(holder);
            mCamera.setPreviewCallback(previewCallback);*/


            mCamera.setPreviewDisplay(holder);
        } catch (IOException exception) {
            mCamera.release();
            mCamera = null;
            //previewCount = 0;
        } catch (Exception exception) {
            mCamera = null;
            //previewCount = 0;
        }

        if (mCameraPreviewListener != null) {
            mCameraPreviewListener.onCameraSurfaceCreated();
        }
    }



    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.setPreviewCallback(null);
            mCamera.release();
            mCamera = null;
            //previewCount = 0;
        }
        if (mCameraPreviewListener != null) {
            mCameraPreviewListener.onCameraSurfaceDestroyed();
        }
    }

    public void stopCamera() {
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.setPreviewCallback(null);
            mCamera.release();
            mCamera = null;
            //previewCount = 0;
        }
    }

    @SuppressWarnings("null")
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        try {
            // Now that the size is known, set up the camera parameters and begin
            // the preview.
            mParameters = mCamera.getParameters();

            Log.d("CameraFix", "parameters -> " + mParameters.flatten());

            setCameraDisplayOrientation(mContext, 0, mCamera);


            mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);

            //Size optimalPreviewSize = getOptimalPreviewSize(mParameters.getSupportedPreviewSizes(), getWidth(), getHeight());
            DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
            int screenWidth = displayMetrics.widthPixels;
            int screenHeight = displayMetrics.heightPixels;

            //Size optimalPreviewSize = getOptimalPreviewSize(mParameters.getSupportedPreviewSizes(), screenWidth, screenHeight, getHeight());
            mSupportedPreviewSizes = mParameters.getSupportedPreviewSizes();
            optimalPreviewSize = getBestAspectPreviewSize(mParameters.getSupportedPreviewSizes(), screenWidth, screenHeight);//Bug Fix for Samsung A8

            Log.d("CameraFix", "optimalPreviewSize.width -> " + optimalPreviewSize.width);
            Log.d("CameraFix", "optimalPreviewSize.height -> " + optimalPreviewSize.height);

            mParameters.setPreviewSize(optimalPreviewSize.width, optimalPreviewSize.height);
            mParameters.setPictureSize(optimalPreviewSize.width, optimalPreviewSize.height);
            mParameters.setPreviewFpsRange(30000, 30000);

            /*if (mDisplayOrientation == 0 || mDisplayOrientation == 180) {
                setLayoutParams(new FrameLayout.LayoutParams(optimalPreviewSize.width, optimalPreviewSize.height,Gravity.CENTER));
            }*/


            Log.d("CameraFix", "setPreviewFpsRange");

            mCamera.setParameters(mParameters);

            mCamera.setPreviewDisplay(holder);

            //SurfaceTexture st = new SurfaceTexture(10);
            //mCamera.setPreviewTexture(st);
            mCamera.setPreviewCallback(previewCallback);
            mCamera.startPreview();
            Log.d("CameraFix", "start preview");
            if (mCameraPreviewListener != null) {
                mCameraPreviewListener.onCameraSurfaceChanged();
            }

        } catch (Exception e) {
            e.printStackTrace();
            Log.d("CameraFix", e.toString());
        }

    }

public void toggleFlash(boolean flashModeOn) {
        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
            Parameters parameters = mCamera.getParameters();
            if (flashModeOn) {
                //parameters.setFlashMode(Parameters.FLASH_MODE_TORCH);
                parameters.setFlashMode(Parameters.FLASH_MODE_ON);
                mCamera.setParameters(parameters);
                mCamera.startPreview();
                //Toast.makeText(mContext, R.string.flash_mode_on, Toast.LENGTH_SHORT).show();
            } else {
                parameters.setFlashMode(Parameters.FLASH_MODE_OFF);
                mCamera.setParameters(parameters);
                //Toast.makeText(mContext, R.string.flash_mode_off, Toast.LENGTH_SHORT).show();
            }

        } else {
            Toast.makeText(mContext, R.string.flash_not_available, Toast.LENGTH_SHORT).show();
        }
    }

/**
     * Source for this solution - https://stackoverflow.com/questions/21354313/camera-preview-quality-in-android-is-poor/21354442#21354442
     *
     * @param supportedPreviewSizes
     * @param screenWidth
     * @param screenHeight
     * @return
     */
    private Size getBestAspectPreviewSize(List<Size> supportedPreviewSizes, int screenWidth, int screenHeight) {
        double targetRatio = (double) screenWidth / screenHeight;
        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        for (int i = 0; i < supportedPreviewSizes.size(); i++) {
            Size size = supportedPreviewSizes.get(i);
            Log.d(TAG, "getBestAspectPreviewSize: supportedPreviewSizes -> "+size.width +"X"+size.height);
        }

        Log.d(TAG, "getBestAspectPreviewSize: supportedPreviewSizes -> "+supportedPreviewSizes.toString());
        Log.d(TAG, "getBestAspectPreviewSize: mDisplayOrientation -> "+mDisplayOrientation);

        if (mDisplayOrientation == 90 || mDisplayOrientation == 270) {
            Log.d(TAG, "getBestAspectPreviewSize: inside 90 - 270 ");
            targetRatio = (double) screenHeight / screenWidth;
        }


        Log.d(TAG, "getBestAspectPreviewSize: targetRatio -> "+targetRatio);

        Collections.sort(supportedPreviewSizes,
                Collections.reverseOrder(new SizeComparator()));

        for (Size size : supportedPreviewSizes) {
            double ratio = (double) size.width / size.height;

            if (Math.abs(ratio - targetRatio) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(ratio - targetRatio);
            }

            if (minDiff < 0.0d) {
                break;
            }
        }
        return (optimalSize);

        /*if (mDisplayOrientation == 0 || mDisplayOrientation == 180) {
            if (optimalSize != null) {
                return mCamera.new Size(optimalSize.height, optimalSize.width);
            } else {
                return null;
            }
        }
        else{
            return (optimalSize);
        }*/

        //return mCamera.new Size(2220,1080);
    }

    public int getDisplayOrientation() {
        return mDisplayOrientation;
    }

    public void setDisplayOrientation(int displayOrientation) {
        this.mDisplayOrientation = displayOrientation;
    }

    public Parameters getCameraParameters() {
        return mCamera.getParameters();
    }

public void setCameraPreviewListener(CameraPreviewListener cameraPreviewListener) {
        mCameraPreviewListener = cameraPreviewListener;
    }

    public interface CameraPreviewListener {
        void onCameraSurfaceCreated();

        void onCameraSurfaceChanged();

        void onCameraSurfaceDestroyed();

        void onCameraPreviewStarted();
    }

    private static class SizeComparator implements
            Comparator<Size> {
        @Override
        public int compare(Size lhs, Size rhs) {
            int left = lhs.width * lhs.height;
            int right = rhs.width * rhs.height;

            if (left < right) {
                return (-1);
            } else if (left > right) {
                return (1);
            }

            return (0);
        }
    }

S9 Camera stretched in landscape mode

2 个答案:

答案 0 :(得分:2)

  1. 您应再次检查默认摄像机的可显示区域。我认为该分辨率无法完全显示。它可能有黑色区域,工具栏,状态栏...
  2. 您的实现没有错。我们必须找到与您要显示的表面视图比较的最佳支持预览尺寸。在这种情况下,您应该以中心视图(1920 x 1080)为中心,然后在顶部和底部添加黑色填充区域。

答案 1 :(得分:0)

请查看我的答案,希望对您有所帮助,我通过以下代码解决了拉伸问题,方法名称可能会更改。我会分享我的实现,因为我知道在Android中实现相机的难度,所以请不要犹豫,看看下面的部分。

在ButtonAction中调用loadCamera方法。

private void loadCamera() {
        if (CommonUtils.deviceHasCamera(getActivityContext)) {
            startBackgroundThread();
            mCameraTimeOut=(isPermissionGranted?2500:5000);
            if (mTextureView.isAvailable()) {
                openCamera(mTextureView.getWidth(), mTextureView.getHeight());
            } else {
                mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
            }
        }else{
            ShowToastUtils.INSTANCE.showCustomToast(getActivityContext, getString(R.string.msg_no_camera));
        }
    }

最初为相机调用的SurfaceListener

private TextureView.SurfaceTextureListener mSurfaceTextureListener
            = new TextureView.SurfaceTextureListener() {

        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
                                              int width, int height) {
            mCameraTimeOut=(isPermissionGranted?2500:5000);
            Log.e(TAG1, "chooseOptimalSize"+"-SurfaceTextureListener ---=>Width---=>"+width);
            Log.e(TAG1, "chooseOptimalSize"+"-SurfaceTextureListener ---=>Height---=>"+height);
            openCamera(width, height);
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
                                                int width, int height) {
            configureTransform(width, height);
        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
            return true;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {}

    };

为纹理选择最佳预览尺寸

//Samsung-S6-choices[0]
//Samsung-S7-edge-choices[6]
//OnePlus-5T-choices[15]
/*Following is used for Camera Preview in TextureView,  based on device camera resolution*/
    /*
     * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
     * width and height are at least as large as the respective requested values, and whose aspect
     * ratio matches with the specified value.
     *
     * @param choices     The list of sizes that the camera supports for the intended output class
     * @param width       The minimum desired width
     * @param height      The minimum desired height
     * @param aspectRatio The aspect ratio
     * @return The optimal {@code Size}, or an arbitrary one if none were big enough
     */

     private  Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
        // Collect the supported resolutions that are at least as big as the preview Surface

        int loopCounter=0;
        Log.e(TAG1, "Screen-->Width x Height="+screenWidth+" x "+screenHeight);
        for (Size size : choices) {
            Log.e(TAG1, "chooseOptimalSize:"+size);
        }
        for (Size size : choices) {
            int orientation = getActivityContext.getResources().getConfiguration().orientation;
            if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                if((size.getWidth()/16) == (size.getHeight()/9) && size.getWidth() <=7680 ) {//8K UHDTV Super Hi-Vision
                    Log.e(TAG1, "chooseOptimalSize:"+size.getWidth()+"x"+size.getHeight()+"--LoopPosition---==>"+loopCounter);
                    return size;
                }
            } else {
                Log.e(TAG1, "chooseOptimalSize:--given--"+size);
                if((size.getWidth()/16) == (size.getHeight()/9) && ((size.getWidth() <=1280)||(size.getHeight()<=1920))) {
                    mCameraRatio=RATIO_16_9;
                    Log.e(TAG1, "chooseOptimalSize:"+size.getWidth()+"x"+size.getHeight()+"-16:9"+"--LoopPosition---==>"+loopCounter);
                    return size;
                }else if((size.getWidth()/18) == (size.getHeight()/9) && ((size.getWidth() <=2160)||(size.getHeight()<=3840))) {
                    mCameraRatio=RATIO_18_9;
                    Log.e(TAG1, "chooseOptimalSize:"+size.getWidth()+"x"+size.getHeight()+"-18:9"+"--LoopPosition---==>"+loopCounter);
                    return size;
                }else if((size.getWidth()/18.5) == (size.getHeight()/9) && ((size.getWidth() <=2160)||(size.getHeight()<=3840))) {
                    mCameraRatio=RATIO_18_9;
                    Log.e(TAG1, "chooseOptimalSize:"+size.getWidth()+"x"+size.getHeight()+"-18.5:9"+"--LoopPosition---==>"+loopCounter);
                    return size;
                }else if((width/19) == (height/9) && ((width <=2208)||(height<=3216))) {
                    mCameraRatio=RATIO_19_9;
                    Log.e(TAG1, "chooseOptimalSize:"+size.getWidth()+"x"+size.getHeight()+"-19:9"+"--LoopPosition---==>"+loopCounter);
                    return size;
                }else if((size.getWidth()/19.5) == (size.getHeight()/9) && ((size.getWidth() <=3840)||(size.getHeight()<=2160))) {
                    mCameraRatio=RATIO_19_9;
                    Log.e(TAG1, "chooseOptimalSize:"+size.getWidth()+"x"+size.getHeight()+"-19.5:9"+"--LoopPosition---==>"+loopCounter);
                    return size;
                }else{
                    Log.e(TAG1, "chooseOptimalSize"+" not proper aspect resolution");
                }
            }
                  loopCounter++;
        }
}

要打开相机

    private void openCamera(int width, int height) {
                CameraManager manager = (CameraManager) getActivityContext.getSystemService(Context.CAMERA_SERVICE);
                try {
                    Log.e(TAG, "tryAcquire");
                    if (!mCameraOpenCloseLock.tryAcquire(mCameraTimeOut, TimeUnit.MILLISECONDS)) {
                        throw new RuntimeException("Time out waiting to lock camera opening.");
                    }
                    String mCameraId = manager.getCameraIdList()[cameraId];
                    // Choose the sizes for camera preview and video recording
                    characteristics = manager.getCameraCharacteristics(mCameraId);
                    StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                    try {
                        mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
                        maximumZoomLevel = characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
                        if (map == null) {
                            throw new RuntimeException("Cannot get available preview/video sizes");
                        }
                        mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
    /*This Line will configure the Texture size*/
                        mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height, mVideoSize);


int orientation = getResources().getConfiguration().orientation;
                    if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                        Log.e(TAG1, "Width" + mPreviewSize.getWidth() + "X Height" + mPreviewSize.getHeight());
                        mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
                    } else {
                        Log.e(TAG1, "Width" + mPreviewSize.getHeight() + "X Height" + mPreviewSize.getWidth());
                        mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
    //S10 preview Size
                        /* mTextureView.setAspectRatio(1080, 2280);*/
                        //mTextureView.setAspectRatio(2208, 2944);
                    }
                    if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                        configureTransform(width, height);
                    }

                    if (isPermissionGranted) {
                        manager.openCamera(mCameraId, mStateCallback, null);
                    }

                }catch (Exception ex){ex.printStackTrace();}finally {
                    map=null;
                    Runtime.getRuntime().gc();
                }
            } catch (CameraAccessException e) {
                Toast.makeText(getActivityContext, "Cannot access the camera.", Toast.LENGTH_SHORT).show();
                //getActivityContext.finish();
                e.printStackTrace();
            } catch (NullPointerException e) {
                e.printStackTrace();

            } catch (InterruptedException e) {
                e.printStackTrace();
                throw new RuntimeException("Interrupted while trying to lock camera opening.");
            }
        }

用于定向处理的ConfigureTransform方法

/*
     * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`.
     * This method should not to be called until the camera preview size is determined in
     * openCamera, or until the size of `mTextureView` is fixed.
     *
     * @param viewWidth  The width of `mTextureView`
     * @param viewHeight The height of `mTextureView`
     */

private void configureTransform(int viewWidth, int viewHeight) {
    if (null == mTextureView || null == mPreviewSize) {
        return;
    }
    int rotation = getActivityContext.getWindowManager().getDefaultDisplay().getRotation();
    Matrix matrix = new Matrix();
    RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
    RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
    float centerX = viewRect.centerX();
    float centerY = viewRect.centerY();
    if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
        bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
        matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
        float scale = Math.max((float) viewHeight / mPreviewSize.getHeight(),(float) viewWidth / mPreviewSize.getWidth());
        matrix.postScale(scale, scale, centerX, centerY);
        matrix.postRotate(90 * (rotation - 2), centerX, centerY);
    }else if (Surface.ROTATION_0 == rotation) {
        bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
        matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
        float scale=Math.max((float) viewWidth / mPreviewSize.getWidth(), (float) viewHeight / mPreviewSize.getHeight());
        matrix.postScale(scale, scale, centerX, centerY);
        matrix.postRotate(0, centerX, centerY);
    }else if(Surface.ROTATION_180== rotation){
        bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
        matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
        float scale=Math.max((float) viewWidth / mPreviewSize.getWidth(), (float) viewHeight / mPreviewSize.getHeight());
        matrix.postScale(scale, scale, centerX, centerY);
        matrix.postRotate(0, centerX, centerY);
    }
    try {
        mTextureView.setTransform(matrix);
    }catch (Exception ex){ex.printStackTrace();}finally {
        bufferRect=null;
        viewRect=null;
        matrix=null;
    }
}

最后开始预览

private void startPreview() {
        if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
            return;
        }
        try {
            closePreviewSession();
            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            assert texture != null;
            texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            //texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            Surface previewSurface = new Surface(texture);
            mPreviewBuilder.addTarget(previewSurface);
            mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface),
                    new CameraCaptureSession.StateCallback() {
                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession session) {
                            mPreviewSession = session;
                            updatePreview();
                        }
                        @Override
                        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                            Toast.makeText(getActivityContext, "Failed", Toast.LENGTH_SHORT).show();
                        }
                    }, mBackgroundHandler);
            //previewSurface=null;
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }