web-dev-qa-db-fra.com

Aperçu de la caméra étiré même après avoir défini les dimensions correctes

Je crée une application photo qui utilise son propre aperçu pour prendre des photos. L'application est actuellement forcée en mode portrait.

Mon problème est que l'aperçu de la caméra est légèrement étiré (le rapport d'aspect est légèrement désactivé). Ce qui est amusant, c'est que je règle ma taille SurfaceView pour qu'elle corresponde toujours à la taille de l'aperçu. Cela garantit que le rapport d'aspect doit toujours être conservé ... mais ce n'est pas ...

Voici la mise en page que j'utilise pour afficher l'aperçu de ma caméra:

public class Cp extends ViewGroup implements SurfaceHolder.Callback {
    private final String TAG = "CameraPreview";

    private boolean mPreviewRunning = false;

    private SurfaceView mSurfaceView;
    private SurfaceHolder mHolder;
    private Size mPreviewSize;
    private List<Size> mSupportedPreviewSizes;
    private Camera mCamera;

    public boolean IsPreviewRunning() {
        return mPreviewRunning;
    }

    public Cp(Context context) {
        this(context, null, 0);
    }

    public Cp(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public Cp(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        mSurfaceView = new SurfaceView(context);
        addView(mSurfaceView);

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = mSurfaceView.getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_Push_BUFFERS);
    }

    public void setCamera(Camera camera) {
        mCamera = camera;
        if (mCamera != null) {
            requestLayout();
        }
    }

    public void switchCamera(Camera camera) {
        setCamera(camera);
        try {
            camera.setPreviewDisplay(mHolder);
        } catch (IOException exception) {
            Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
        }
        Camera.Parameters parameters = camera.getParameters();
        parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
        requestLayout();

        camera.setParameters(parameters);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // We purposely disregard child measurements because act as a wrapper to
        // a SurfaceView that
        // centers the camera preview instead of stretching it.
        final int width = resolveSize(getSuggestedMinimumWidth(),
                widthMeasureSpec);
        final int height = resolveSize(getSuggestedMinimumHeight(),
                heightMeasureSpec);
        setMeasuredDimension(width, height);

        if (mSupportedPreviewSizes == null && mCamera != null) {
            mSupportedPreviewSizes = mCamera.getParameters()
                    .getSupportedPreviewSizes();
        }
        if (mSupportedPreviewSizes != null) {
            mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width,
                    height);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed && getChildCount() > 0) {
            final View child = getChildAt(0);

            final int width = r - l;
            final int height = b - t;

            int previewWidth = width;
            int previewHeight = height;
            if (mPreviewSize != null) {
                previewWidth = mPreviewSize.height;
                previewHeight = mPreviewSize.width;
            }
            if (previewWidth == 0) {
                previewWidth = 1;
            }
            if (previewHeight == 0) {
                previewHeight = 1;
            }

            // Center the child SurfaceView within the parent.
            if (width * previewHeight > height * previewWidth) {
                final int scaledChildWidth = previewWidth * height
                        / previewHeight;
                child.layout((width - scaledChildWidth) / 2, 0,
                        (width + scaledChildWidth) / 2, height);
            } else {
                final int scaledChildHeight = previewHeight * width
                        / previewWidth;
                child.layout(0, (height - scaledChildHeight) / 2, width,
                        (height + scaledChildHeight) / 2);
            }
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, acquire the camera and tell it where to
        // draw.
        try {
            if (mCamera != null) {
                Parameters params = mCamera.getParameters();
                mSupportedPreviewSizes = params.getSupportedPreviewSizes();
                mCamera.setPreviewDisplay(holder);
            }
        } catch (IOException exception) {
            Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // Surface will be destroyed when we return, so stop the preview.
        stop();
    }

    private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) w / h;
        if (sizes == null)
            return null;

        Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        // Try to find an size match aspect ratio and size
        for (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);
            }
        }

        // Cannot find the one match the aspect ratio, ignore the requirement
        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        if (mCamera != null) {
            // Now that the size is known, set up the camera parameters and
            // begin the preview.
            Camera.Parameters parameters = mCamera.getParameters();
            if (mPreviewSize != null) {
                parameters.setPreviewSize(mPreviewSize.width,
                        mPreviewSize.height);
            }
            requestLayout();

            mCamera.setParameters(parameters);
            mCamera.startPreview();
            mPreviewRunning = true;
        }
    }

    public void stop() {
        if (mCamera != null) {
            mCamera.stopPreview();
            mPreviewRunning = false;
            mCamera = null;
        }
    }
}

Veuillez noter que dans onLayout la largeur et la hauteur sont permutées, car l'application est toujours exécutée en mode portrait.

J'inclus quelques images pour vous montrer à quoi ressemble le problème: enter image description here

enter image description here

J'ai débogué le code. La taille de l'aperçu est définie sur 1280x720 et la taille de la mise en page est également correctement ajustée pour correspondre à cette taille.

J'ai aussi essayé différentes dispositions mais le résultat était toujours le même ...

18
Paul

J'avais le même problème, après quelques jours dans ce puzzle, ma classe Java aboutit à ce code:

Le problème se posait donc parce que l’affichage de la caméra avait une taille (hauteur x largeur) 576x720 et que mon affichage était de 1184x720. Ainsi, l'aperçu de la caméra (ma classe de vue de surface) s'est étiré pour remplir le parent. 

L’approche qui a fonctionné consiste donc à rendre cette vue plus grande que mon écran et à n’afficher que la zone de mon écran. J'ai donc dû utiliser cette étrange combinaison de cadres (une disposition relative dans une structure linéaire), de sorte que la taille du relatif soit aussi grande que je le souhaite - la vue de la surface le remplira - et le linéaire prendra uniquement la partie que je veux afficher.

package com.example.frame.camera;

import Android.content.Context;
import Android.hardware.Camera;
import Android.util.Log;
import Android.view.SurfaceHolder;
import Android.view.SurfaceView;
import Android.view.ViewGroup.LayoutParams;
import Android.widget.LinearLayout;
import Android.widget.RelativeLayout;
import Android.widget.Toast;

import com.example.frame.MainActivity;

public class NativeCamera extends SurfaceView implements SurfaceHolder.Callback {

    static private NativeCamera instance;

    private LinearLayout        frame               = null;
    private RelativeLayout      innerFrame          = null;

    private Camera              camera;
    private final SurfaceHolder previewHolder;
    private static final String TAG                 = "NativeCamera.Java";

    private boolean             inPreview           = false;
    private boolean             cameraConfigured    = false;
    private boolean             frontCamera         = false;

    private Camera.Size         size;

    static public NativeCamera getInstance() {
        if (NativeCamera.instance == null) {
            if (MainActivity.debug) {
                Log.d(TAG, "Creating Camera Singleton");
            }
            NativeCamera.instance = new NativeCamera(MainActivity.instance);
        }
        return NativeCamera.instance;
    }

    public void onResume() {
        if (MainActivity.debug) {
            Log.d(TAG, "onResume");
        }
        camera = Camera.open();
        if (size != null) {
            initPreview(size.width, size.height);
        }
        startPreview();
    }

    public void onPause() {
        if (MainActivity.debug) {
            Log.d(TAG, "onPause");
        }
        if (inPreview) {
            camera.stopPreview();
        }
        camera.release();
        camera = null;
        inPreview = false;
    }

    public void onDestroy() {
        if (MainActivity.debug) {
            Log.d(TAG, "onDestroy");
        }
        NativeCamera.instance = null;
    }

    public void onSwitch() {
        frontCamera = !frontCamera;
        if (inPreview) {
            camera.stopPreview();
        }
        camera.release();

        int cam = frontCamera ? 1 : 0;

        camera = Camera.open(cam);
        if (size != null) {
            initPreview(size.width, size.height);
        }
        startPreview();
    }

    private NativeCamera(Context context) {
        super(context);
        // TODO Auto-generated constructor stub

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        previewHolder = getHolder();
        previewHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        // previewHolder.setType(SurfaceHolder.SURFACE_TYPE_Push_BUFFERS);
        innerFrame = new RelativeLayout(MainActivity.instance);
        innerFrame.addView(this);
        frame = new LinearLayout(MainActivity.instance);
        frame.addView(innerFrame);
    }

    public LinearLayout getFrame() {
        return frame;
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        // TODO Auto-generated method stub
        if (MainActivity.debug) {
            Log.d(TAG, "surfaceChanged");
        }
        initPreview(width, height);
        startPreview();
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        // no-op -- wait until surfaceChanged()

    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        // no-op
    }

    private Camera.Size getBestPreviewSize(int width, int height,
            Camera.Parameters parameters) {
        Camera.Size result = null;

        for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
            if (size.width <= width && size.height <= height) {
                if (result == null) {
                    result = size;
                } else {
                    int resultArea = result.width * result.height;
                    int newArea = size.width * size.height;

                    if (newArea > resultArea) {
                        result = size;
                    }
                }
            }
        }

        this.size = result;
        return (result);
    }

    private void initPreview(int width, int height) {
        if (camera != null && previewHolder.getSurface() != null) {

            if (!cameraConfigured) {
                Camera.Parameters parameters = camera.getParameters();
                Camera.Size size = getBestPreviewSize(width, height, parameters);

                if (size != null) {
                    parameters.setPreviewSize(size.width, size.height);
                    camera.setParameters(parameters);
                    cameraConfigured = true;
                    // Setting up correctly the view
                    double ratio = size.height / (double) size.width;
                    LayoutParams params = innerFrame.getLayoutParams();
                    params.height = MainActivity.size.y;
                    params.width = (int) (MainActivity.size.y * ratio);
                    innerFrame.setLayoutParams(params);
                    int deslocationX = (int) (params.width / 2.0 - MainActivity.size.x / 2.0);
                    innerFrame.animate().translationX(-deslocationX);
                }
            }

            try {
                camera.setPreviewDisplay(previewHolder);
                camera.setDisplayOrientation(90);

            } catch (Throwable t) {
                Log.e(TAG, "Exception in setPreviewDisplay()", t);
                Toast.makeText(MainActivity.instance, t.getMessage(), Toast.LENGTH_LONG).show();
            }

        }
    }

    private void startPreview() {
        if (MainActivity.debug) {
            Log.d(TAG, "startPreview");
        }
        if (cameraConfigured && camera != null) {
            camera.startPreview();
            inPreview = true;
        }
    }

}
5
Eduardo Reis

J'ai essayé de résoudre le même problème ... Le code ci-dessous a fonctionné pour moi:

private void setMyPreviewSize(int width, int height) {
    // Get the set dimensions
    float newProportion = (float) width / (float) height;

    // Get the width of the screen
    int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
    int screenHeight = getWindowManager().getDefaultDisplay().getHeight();
    float screenProportion = (float) screenWidth / (float) screenHeight;

    // Get the SurfaceView layout parameters
    Android.view.ViewGroup.LayoutParams lp = surfaceView.getLayoutParams();
    if (newProportion > screenProportion) {
        lp.width = screenWidth;
        lp.height = (int) ((float) screenWidth / newProportion );
    } else {
        lp.width = (int) (newProportion * (float) screenHeight);
        lp.height = screenHeight;
    }
    // Commit the layout parameters
    surfaceView.setLayoutParams(lp);
}

Vous pouvez voir le fil conducteur: Redimensionnement de la vue de surface pour le changement du rapport de format dans l'affichage vidéo sous Android

2
Anju

Voici une solution

surfaceChanged est juste pour obtenir la meilleure taille de prévisualisation

@Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                Log.e("MYTAG", "surfaceChanged " );

                Camera.Parameters myParameters = camera.getParameters();
                Camera.Size myBestSize = getBestPreviewSize(width, height, myParameters);

                if(myBestSize != null){
                    myParameters.setPreviewSize(myBestSize.width, myBestSize.height);
                    camera.setParameters(myParameters);
                    camera.startPreview();


                }
            }



@Override
    public void onPictureTaken(byte[] data, Camera camera) {

        Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
       imageReview.setImageBitmap(bitmap);
    }

Cela va définir l'image capturée sur imageReview, mais vous avez besoin d'une méthode pour obtenir la rotation du bitmap

vous devez définir l'attribut imageView scaleType pour un problème d'image étendue

<ImageView
        Android:id="@+id/imageView"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:scaleType="centerCrop"
        />
0
Mohamed Embaby