2960x1440 拉伸 Android 相机预览

Posted

技术标签:

【中文标题】2960x1440 拉伸 Android 相机预览【英文标题】:2960x1440 stretching Android Camera Preview 【发布时间】:2019-03-02 16:10:25 【问题描述】:

我的相机预览几乎可以在所有屏幕上完美运行,但是当我使用2960x1440 屏幕(例如在手机 S8 和 S9 上)测试我的相机时,预览确实被拉伸了。

我已经包含了为相机设置 preview size 的所有内容。

private static final int MAX_PREVIEW_WIDTH = 2960;
private static final int MAX_PREVIEW_HEIGHT = 1440;

/**
 * Given @code choices of @code Sizes supported by a camera, choose the smallest one that
 * is at least as large as the respective texture view size, and that is at most as large as the
 * respective max size, and whose aspect ratio matches with the specified value. If such size
 * doesn't exist, choose the largest one that is at most as large as the respective max size,
 * 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 textureViewWidth  The width of the texture view relative to sensor coordinate
 * @param textureViewHeight The height of the texture view relative to sensor coordinate
 * @param maxWidth          The maximum width that can be chosen
 * @param maxHeight         The maximum height that can be chosen
 * @param aspectRatio       The aspect ratio
 * @return The optimal @code Size, or an arbitrary one if none were big enough
 */
private static Size chooseOptimalSize(Size[] choices, int textureViewWidth, int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio)

    List<Size> bigEnough = new ArrayList<>();
    List<Size> notBigEnough = new ArrayList<>();
    int w = aspectRatio.getWidth();
    int h = aspectRatio.getHeight();
    for (Size option : choices) 
        if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
                option.getHeight() == option.getWidth() * h / w) 
            if (option.getWidth() >= textureViewWidth &&
                    option.getHeight() >= textureViewHeight) 
                bigEnough.add(option);
             else 
                notBigEnough.add(option);
            
        
    
    if (bigEnough.size() > 0)
    
        return Collections.min(bigEnough, new CompareSizesByArea());
     else if (notBigEnough.size() > 0)
    
        return Collections.max(notBigEnough, new CompareSizesByArea());
     else
    
        return choices[0];
    


/**
 * Given @code choices of @code Sizes 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 static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) 
    List<Size> bigEnough = new ArrayList<>();
    int w = aspectRatio.getWidth();
    int h = aspectRatio.getHeight();
    for (Size option : choices) 
        if (option.getHeight() == option.getWidth() * h / w &&
                option.getWidth() >= width && option.getHeight() >= height) 
            bigEnough.add(option);
        
    
    if (bigEnough.size() > 0) 
        return Collections.min(bigEnough, new CompareSizesByArea());
     else 
        return choices[0];
    



/**
 * Sets up member variables related to camera.
 * @param width  The width of available size for camera preview
 * @param height The height of available size for camera preview
 */
private void setUpCameraOutputs(int width, int height) 

    System.out.println("Setup camera outputs.");
    try 
        CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId);
        StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

        assert map != null;

        Size largest = Collections.max(
                Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
                new CompareSizesByArea());

        mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class)); //640x480

        mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
                width, height, mVideoSize);
        mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
                ImageFormat.JPEG, /*maxImages*/2);
        mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
        int displayRotation =parentactivity.getWindowManager().getDefaultDisplay().getRotation();
        mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        boolean swappedDimensions = false;

        switch (displayRotation) 
            case Surface.ROTATION_0:
            case Surface.ROTATION_180:
                if (mSensorOrientation == 90 || mSensorOrientation == 270)
                
                    swappedDimensions = true;
                
                break;
            case Surface.ROTATION_90:
            case Surface.ROTATION_270:
                if (mSensorOrientation == 0 || mSensorOrientation == 180)
                
                    swappedDimensions = true;
                
                break;
            default:
        

        Point displaySize = new Point();
        parentactivity.getWindowManager().getDefaultDisplay().getSize(displaySize);
        int rotatedPreviewWidth = width;
        int rotatedPreviewHeight = height;
        int maxPreviewWidth = displaySize.x;
        int maxPreviewHeight = displaySize.y;

        if (swappedDimensions) 
            rotatedPreviewWidth = height;
            rotatedPreviewHeight = width;
            maxPreviewWidth = displaySize.y;
            maxPreviewHeight = displaySize.x;
        

        if (maxPreviewWidth > MAX_PREVIEW_WIDTH) 
            maxPreviewWidth = MAX_PREVIEW_WIDTH;
        
        if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) 
            maxPreviewHeight = MAX_PREVIEW_HEIGHT;
        

        mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
                rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
                maxPreviewHeight, mVideoSize);

        int orientation = getResources().getConfiguration().orientation;
        if (orientation == Configuration.ORIENTATION_LANDSCAPE) 
            mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
         else 
            mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
        
        Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
        mFlashSupported = available == null ? false : available;

     catch (IllegalArgumentException e) 
        e.printStackTrace();
    
    catch (CameraAccessException | NullPointerException e) 
        e.printStackTrace();
    


 /**
 * Creates a new @link CameraCaptureSession for camera preview.
 */
private void createCameraPreviewSession()

    if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) 
        return;
    
    try
    
        closePreviewSession();
        SurfaceTexture texture = mTextureView.getSurfaceTexture();
        assert texture != null;
        texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        Surface surface = new Surface(texture);
        mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        mPreviewRequestBuilder.addTarget(surface);
        mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
                new CameraCaptureSession.StateCallback() 

                    @Override
                    public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession)
                    
                        if (null == mCameraDevice) 
                            return;
                        
                        mCaptureSession = cameraCaptureSession;

                        try 
                            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                            mPreviewRequest = mPreviewRequestBuilder.build();
                            mCaptureSession.setRepeatingRequest(mPreviewRequest,
                                    mCaptureCallback, mBackgroundHandler);
                         catch (Exception e) 
                            e.printStackTrace();
                        
                    

                    @Override
                    public void onConfigureFailed(
                            @NonNull CameraCaptureSession cameraCaptureSession)
                    
                        showToast("Failed to configure camera.");
                    
                , null
        );
     catch (Exception e) 
        e.printStackTrace();
    

/**
 * Configures the necessary @link Matrix transformation to `mTextureView`.
 * This method should be called after the camera preview size is determined in
 * setUpCameraOutputs and also 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 || null == parentactivity) 
        return;
    
    int rotation =parentactivity.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_180 == rotation) 
        matrix.postRotate(180, centerX, centerY);
    
    mTextureView.setTransform(matrix);

由于我不太明白将预览尺寸缩放到 2960x1440 时出现什么问题,如果有人能帮助我解决这个问题,我将不胜感激。

以防万一我添加了完整代码的链接Pastebin Link to Camera

【问题讨论】:

拉伸是什么意思。你能举例说明它在普通手机和高分辨率手机上的外观吗? @zipo13 没拿到手机截图,明天看看能不能弄到 @zipo13 但它基本上会垂直拉伸视频并使其看起来很难看 【参考方案1】:

我认为您需要获得支持预览尺寸并从中计算最佳宽度和高度。

例如

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;
    

然后在 onMeasure 方法中使用它来获得最佳预览尺寸

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
        final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        setMeasuredDimension(width, height);
        mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();

        if (mSupportedPreviewSizes != null) 
           mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
        
    

然后在surfaceChanged里面设置你的相机参数

Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
mCamera.setParameters(parameters);
mCamera.startPreview();

【讨论】:

我想说它有效,但由于我使用的是定制的Camera,我不能使用onMeasure(),因此我不能使用resolveSizegetSuggestedMinimumWidth()的功能

以上是关于2960x1440 拉伸 Android 相机预览的主要内容,如果未能解决你的问题,请参考以下文章

在android中将相机预览拉伸到全屏

Android 相机预览在预览中拉伸,而不是在拍照后

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

相机预览被拉伸和倾斜

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

Android:更改屏幕上的相机预览大小