Aperçu de la caméra Android étiré

136

J'ai travaillé sur la création de mon activité de caméra personnalisée sur Android, mais lors de la rotation de la caméra, le rapport hauteur / largeur de la vue de surface est perturbé.

Dans mon oncreate pour l'activité, j'ai défini la disposition du cadre qui contient la vue de surface qui affiche les paramètres de la caméra.

//FrameLayout that will hold the camera preview
        FrameLayout previewHolder = (FrameLayout) findViewById(R.id.camerapreview);

        //Setting camera's preview size to the best preview size
        Size optimalSize = null;
        camera = getCameraInstance();
        double aspectRatio = 0;
        if(camera != null){
            //Setting the camera's aspect ratio
            Camera.Parameters parameters = camera.getParameters();
            List<Size> sizes = parameters.getSupportedPreviewSizes();
            optimalSize = CameraPreview.getOptimalPreviewSize(sizes, getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);
            aspectRatio = (float)optimalSize.width/optimalSize.height;
        }

        if(optimalSize!= null){
            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, (int)(getResources().getDisplayMetrics().widthPixels*aspectRatio));
            previewHolder.setLayoutParams(params);
            LayoutParams surfaceParams = new LayoutParams(LayoutParams.MATCH_PARENT, (int)(getResources().getDisplayMetrics().widthPixels*aspectRatio));
            cameraPreview.setLayoutParams(surfaceParams);

        }

        cameraPreview.setCamera(camera);

        //Adding the preview to the holder
        previewHolder.addView(cameraPreview);

Ensuite, dans la vue Surface, j'ai défini les paramètres de la caméra à afficher

public void setCamera(Camera camera) {
        if (mCamera == camera) { return; }

        mCamera = camera;

        if (mCamera != null) {
            requestLayout();

            try {
                mCamera.setPreviewDisplay(mHolder);
            } catch (IOException e) {
                e.printStackTrace();
            }


            if(mCamera != null){
                //Setting the camera's aspect ratio
                Camera.Parameters parameters = mCamera.getParameters();
                List<Size> sizes = parameters.getSupportedPreviewSizes();
                Size optimalSize = getOptimalPreviewSize(sizes, getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);

                parameters.setPreviewSize(optimalSize.width, optimalSize.height);
                mCamera.setParameters(parameters);
            }

            /*
              Important: Call startPreview() to start updating the preview surface. Preview must 
              be started before you can take a picture.
              */
            mCamera.startPreview();
        }

    }

entrez la description de l'image ici

Vous pouvez voir que l'homme LEGO grandit et maigre lorsque le téléphone tourne:

Comment puis-je m'assurer que le rapport hauteur / largeur de la vue de ma caméra est correct?

scientifique
la source
@scientific pouvez-vous m'aider où devrais-je écrire la méthode donnée, je suis également confronté au même problème?
user3233280
J'ai semblé avoir un problème similaire, mais ce qui a résolu l'appelait ce qui suit: mCamera.setDisplayOrientation (90) dans la méthode surfaceCreated () de mon SurfaceView pour la caméra
Greg

Réponses:

163

J'utilise cette méthode -> basée sur les démos d'API pour obtenir ma taille d'aperçu:

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

Comme vous pouvez le voir, vous devez saisir la largeur et la hauteur de votre écran. Cette méthode calculera le rapport d'écran en fonction de ces valeurs, puis dans la liste des tailles de prévisualisation prises en charge, elle choisira le meilleur pour vous parmi celles disponibles. Obtenez votre liste pris en chargePreviewSize en place là où l'objet Camera n'est pas nul en utilisant

mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();

Et puis dans onMeasure, vous pouvez obtenir votre aperçu optimal

@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(mSupportedPreviewSizes, width, height);
        }
    }

Et puis (dans mon code dans la méthode surfaceChanged, comme je l'ai dit, j'utilise la structure API Demos du code CameraActivity, vous pouvez le générer dans Eclipse):

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

Et un indice pour vous, car j'ai fait presque la même application que vous. La bonne pratique pour l'activité de la caméra consiste à masquer la barre d'état. Des applications comme Instagram le font. Il réduit la valeur de la hauteur de votre écran et change la valeur de votre ratio. Il est possible d'obtenir des tailles d'aperçu étranges sur certains appareils (votre SurfaceView sera un peu coupée)


Et pour répondre à votre question, comment vérifier si votre ratio de prévisualisation est correct? Ensuite, obtenez la hauteur et la largeur des paramètres que vous avez définis:

mCamera.setParameters(parameters);

votre rapport défini est égal à hauteur / largeur. Si vous voulez que la caméra apparaisse bien sur votre écran, le rapport hauteur / largeur des paramètres que vous définissez sur la caméra doit être le même que le rapport hauteur (moins la barre d'état) / largeur de votre écran.

F1sher
la source
2
Juste une question: je vois que vous utilisez 2 rapports calculés différents: double targetRatio = (double) h / w et double ratio = (double) size.width / size.height . Le premier est la hauteur divisée par la largeur et l'autre est l'opposé, la largeur divisée par la hauteur. Ne devraient-ils pas être le même calcul?
phyzalis
Cela n'a pas d'importance car la liste des tailles prend en compte toutes les possibilités par exemple: vous trouverez quelque chose comme 480x680, mais tôt ou tard il y aura 680x480 (pour le paysage), il vous suffit donc de parcourir cette liste et d'en trouver une qui correspond à vos besoins. Vous le trouverez tôt ou tard.
F1sher
De plus, si vous souhaitez que vos images aient également cette taille (comme elles devraient probablement l'être), vous avez défini la taille de l'image avec parameters.setPictureSize(mPreviewSize.width, mPreviewSize.height).
Matt Logan
2
@ F1sher, j'obtiens toujours des paramètres d'exception de pointeur nul .setPreviewSize (mPreviewSize.width, mPreviewSize.height); Je ne trouve aucune solution.
FIXI
3
@phyzalis Je suis d'accord avec vous. En fait, j'ai dû faire cette correction pour que le code fonctionne comme prévu.
fernio
149

La solution de F1Sher est agréable mais parfois ne fonctionne pas. En particulier, lorsque votre surfaceView ne couvre pas tout l'écran. Dans ce cas, vous devez remplacer la méthode onMeasure (). J'ai copié mon code ici pour votre référence.

Depuis que j'ai mesuré surfaceView en fonction de la largeur, il y a un petit espace blanc à la fin de mon écran que je l'ai rempli par conception. Vous pouvez résoudre ce problème si vous conservez la hauteur et augmentez la largeur en la multipliant par rapport. Cependant, cela écrasera légèrement surfaceView.

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

    private static final String TAG = "CameraPreview";

    private Context mContext;
    private SurfaceHolder mHolder;
    private Camera mCamera;
    private List<Camera.Size> mSupportedPreviewSizes;
    private Camera.Size mPreviewSize;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mContext = context;
        mCamera = camera;

        // supported preview sizes
        mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
        for(Camera.Size str: mSupportedPreviewSizes)
                Log.e(TAG, str.width + "/" + str.height);

        // 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) {
        // empty. surfaceChanged will take care of stuff
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        Log.e(TAG, "surfaceChanged => w=" + w + ", h=" + 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
        // start preview with new settings
        try {
            Camera.Parameters parameters = mCamera.getParameters();
            parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
            mCamera.setParameters(parameters);
            mCamera.setDisplayOrientation(90);
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);

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

        if (mPreviewSize!=null) {
            float ratio;
            if(mPreviewSize.height >= mPreviewSize.width)
                ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
            else
                ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;

            // One of these methods should be used, second method squishes preview slightly
            setMeasuredDimension(width, (int) (width * ratio));
  //        setMeasuredDimension((int) (width * ratio), height);
        }
    }

    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.height / size.width;
            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;
    }
}
Hesam
la source
11
cela devrait être la bonne réponse, elle gère le cas où l'aperçu de la caméra ne couvre pas tout l'écran. +1.
Houcine
1
lorsque je gonfle dans mon application d'activité a été fermée <com.app.camera.CameraPreview android: id = "@ + id / camera_preview" android: layout_width = "match_parent" android: layout_height = "match_parent" />
fish40
Vous devez cependant apporter quelques modifications, mais j'accepte cela comme une solution. Voté.
JaydeepW
2
Salut @ adnan9011, ce tutoriel pourrait être utile probablement. airpair.com/android/android-camera-surface-view-fragment
Hesam
4
la hauteur et la largeur devaient être changées getOptimalPreviewSizepour que cela fonctionne pour moi. est-ce parce que l'orientation d'affichage a été réglée sur 90? Sinon, les calculs de rapport ne sont pas même à proximité ( la cible était de 1,7 et le ratio de la caméra était inférieur à 1)
ono
23

REMARQUE: MA SOLUTION EST UNE SUITE DE LA SOLUTION HESAM: https://stackoverflow.com/a/22758359/1718734

Ce que j'adresse: Hesam a dit qu'il y avait un petit espace blanc qui pouvait apparaître sur certains téléphones, comme ceci:

REMARQUE: bien que le format d'image soit correct, l'appareil photo ne remplit pas tout l'écran.

Hesam a suggéré une deuxième solution, mais cela écrase l'aperçu. Et sur certains appareils, il déforme fortement.

Alors, comment pouvons-nous résoudre ce problème. C'est simple ... en multipliant les proportions jusqu'à ce qu'il remplisse l'écran. J'ai remarqué que plusieurs applications populaires telles que Snapchat, WhatsApp, etc. fonctionnent de la même manière.

Tout ce que vous avez à faire est d'ajouter ceci à la méthode onMeasure:

float camHeight = (int) (width * ratio);
    float newCamHeight;
    float newHeightRatio;

    if (camHeight < height) {
        newHeightRatio = (float) height / (float) mPreviewSize.height;
        newCamHeight = (newHeightRatio * camHeight);
        Log.e(TAG, camHeight + " " + height + " " + mPreviewSize.height + " " + newHeightRatio + " " + newCamHeight);
        setMeasuredDimension((int) (width * newHeightRatio), (int) newCamHeight);
        Log.e(TAG, mPreviewSize.width + " | " + mPreviewSize.height + " | ratio - " + ratio + " | H_ratio - " + newHeightRatio + " | A_width - " + (width * newHeightRatio) + " | A_height - " + newCamHeight);
    } else {
        newCamHeight = camHeight;
        setMeasuredDimension(width, (int) newCamHeight);
        Log.e(TAG, mPreviewSize.width + " | " + mPreviewSize.height + " | ratio - " + ratio + " | A_width - " + (width) + " | A_height - " + newCamHeight);
    }

Cela calculera la hauteur de l'écran et obtiendra le rapport entre la hauteur de l'écran et la hauteur mPreviewSize. Ensuite, il multiplie la largeur et la hauteur de la caméra par le nouveau rapport de hauteur et définit la dimension mesurée en conséquence.

entrez la description de l'image ici

Et la prochaine chose que vous savez, vous vous retrouvez avec ceci: D

entrez la description de l'image ici

Cela fonctionne également bien avec la caméra frontale. Je pense que c'est la meilleure façon de procéder. Maintenant, la seule chose qui reste pour mon application est d'enregistrer l'aperçu lui-même en cliquant sur «Capturer». Mais toi, c'est ça.

Yoosuf
la source
Cela fonctionne très bien !! mais cela ne fonctionne pas correctement dans Landscape: /
Matan Dahan
votre code a résolu mon problème d'espace noir vide de 2 mois :)
karthik kolanji
1
Cela fonctionne, mais dans mon cas ... J'ai une barre d'action sur le dessus, donc la barre blanche était plus petite, mais après avoir ajouté votre code, mon appareil photo a zoomé de 40 à 50%. Lorsque je passe à la caméra frontale, je ne peux pas voir mon visage droit (juste un peu de cheveux), à cause de ce zoom élevé.
Makalele
@Yoosuf J'obtiens un écran blanc lorsque j'ai suivi votre code. Dans la onMeasure(int widthMeasureSpec, int heightMeasureSpec)méthode, j'obtiens toujours width = 0 et height = 0;
Kush Patel
10

OK, donc je pense qu'il n'y a pas de réponse suffisante pour le problème général d'étirement de l'aperçu de la caméra. Ou du moins je n'en ai pas trouvé. Mon application a également souffert de ce syndrome d'étirement et il m'a fallu un certain temps pour trouver une solution à partir de toutes les réponses des utilisateurs sur ce portail et sur Internet.

J'ai essayé la solution de @ Hesam mais cela n'a pas fonctionné et j'ai laissé l'aperçu de ma caméra très déformé.

Je montre d'abord le code de ma solution (les parties importantes du code) puis j'explique pourquoi j'ai pris ces mesures. Il y a place pour des modifications de performance.

Mise en page XML de l'activité principale:

<RelativeLayout 
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <FrameLayout
        android:id="@+id/camera_preview"
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
    />
</RelativeLayout>

Aperçu de la caméra:

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

private SurfaceHolder prHolder;
private Camera prCamera;
public List<Camera.Size> prSupportedPreviewSizes;
private Camera.Size prPreviewSize;

@SuppressWarnings("deprecation")
public YoCameraPreview(Context context, Camera camera) {
    super(context);
    prCamera = camera;

    prSupportedPreviewSizes = prCamera.getParameters().getSupportedPreviewSizes();

    prHolder = getHolder();
    prHolder.addCallback(this);
    prHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

public void surfaceCreated(SurfaceHolder holder) {
    try {
        prCamera.setPreviewDisplay(holder);
        prCamera.startPreview();
    } catch (IOException e) {
        Log.d("Yologram", "Error setting camera preview: " + e.getMessage());
    }
}

public void surfaceDestroyed(SurfaceHolder holder) {
}

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    if (prHolder.getSurface() == null){
      return;
    }

    try {
        prCamera.stopPreview();
    } catch (Exception e){
    }

    try {
        Camera.Parameters parameters = prCamera.getParameters();
        List<String> focusModes = parameters.getSupportedFocusModes();
        if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        }
        parameters.setPreviewSize(prPreviewSize.width, prPreviewSize.height);

        prCamera.setParameters(parameters);
        prCamera.setPreviewDisplay(prHolder);
        prCamera.startPreview();

    } catch (Exception e){
        Log.d("Yologram", "Error starting camera preview: " + e.getMessage());
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);

    setMeasuredDimension(width, height);

    if (prSupportedPreviewSizes != null) {
        prPreviewSize = 
            getOptimalPreviewSize(prSupportedPreviewSizes, width, height);
    }    
}

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

Activité principale:

public class MainActivity extends Activity {

...

@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);     
    setContentView(R.layout.activity_main);

        maCamera = getCameraInstance();

        maLayoutPreview = (FrameLayout) findViewById(R.id.camera_preview);

        maPreview = new CameraPreview(this, maCamera);

        Point displayDim = getDisplayWH();
        Point layoutPreviewDim = calcCamPrevDimensions(displayDim, 
                maPreview.getOptimalPreviewSize(maPreview.prSupportedPreviewSizes, 
                    displayDim.x, displayDim.y));
        if (layoutPreviewDim != null) {
            RelativeLayout.LayoutParams layoutPreviewParams = 
                (RelativeLayout.LayoutParams) maLayoutPreview.getLayoutParams();
            layoutPreviewParams.width = layoutPreviewDim.x;
            layoutPreviewParams.height = layoutPreviewDim.y;
            layoutPreviewParams.addRule(RelativeLayout.CENTER_IN_PARENT);
            maLayoutPreview.setLayoutParams(layoutPreviewParams);
        }
        maLayoutPreview.addView(maPreview);
}

@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
private Point getDisplayWH() {

    Display display = this.getWindowManager().getDefaultDisplay();
    Point displayWH = new Point();

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
        display.getSize(displayWH);
        return displayWH;
    }
    displayWH.set(display.getWidth(), display.getHeight());
    return displayWH;
}

private Point calcCamPrevDimensions(Point disDim, Camera.Size camDim) {

    Point displayDim = disDim;
    Camera.Size cameraDim = camDim;

    double widthRatio = (double) displayDim.x / cameraDim.width;
    double heightRatio = (double) displayDim.y / cameraDim.height;

    // use ">" to zoom preview full screen
    if (widthRatio < heightRatio) {
        Point calcDimensions = new Point();
        calcDimensions.x = displayDim.x;
        calcDimensions.y = (displayDim.x * cameraDim.height) / cameraDim.width;
        return calcDimensions;
    }
    // use "<" to zoom preview full screen
    if (widthRatio > heightRatio) { 
        Point calcDimensions = new Point();
        calcDimensions.x = (displayDim.y * cameraDim.width) / cameraDim.height;
        calcDimensions.y = displayDim.y;
        return calcDimensions;
    }
    return null;
}   
}

Mon commentaire:

Le point de tout cela est que, même si vous calculez la taille de la caméra optimale en getOptimalPreviewSize()vous ne choisissez le rapport le plus proche de correspondre à votre écran. Ainsi, à moins que le rapport ne soit exactement le même, l'aperçu s'étirera.

Pourquoi cela va-t-il s'étirer? Parce que l'aperçu de votre caméra FrameLayout est défini dans layout.xml sur match_parent en largeur et en hauteur. C'est pourquoi l'aperçu s'étendra en plein écran.

Ce qu'il faut faire est de définir la largeur et la hauteur de la mise en page de l'aperçu de la caméra pour qu'elles correspondent au rapport de taille de caméra choisi , afin que l'aperçu conserve son rapport hauteur / largeur et ne se déforme pas.

J'ai essayé d'utiliser la CameraPreviewclasse pour faire tous les calculs et les changements de mise en page, mais je ne pouvais pas le comprendre. J'ai essayé d'appliquer cette solution , mais je SurfaceViewne reconnais pas getChildCount ()ou getChildAt (int index). Je pense que je l'ai finalement fait fonctionner avec une référence à maLayoutPreview, mais il se comportait mal et a appliqué le rapport défini à l'ensemble de mon application et cela a été fait après la première photo. J'ai donc laissé tomber et déplacé les modifications de mise en page vers le MainActivity.

Dans CameraPreviewj'ai changé prSupportedPreviewSizeset getOptimalPreviewSize()en public afin que je puisse l'utiliser dans MainActivity. Ensuite, j'avais besoin des dimensions de l' écran (moins la barre de navigation / d'état s'il y en a une) et choisi la taille optimale de la caméra . J'ai essayé d'obtenir la taille RelativeLayout (ou FrameLayout) au lieu de la taille d'affichage, mais cela retournait une valeur nulle. Cette solution n'a pas fonctionné pour moi. La mise en page a sa valeur après onWindowFocusChanged(vérifiée dans le journal).

J'ai donc mes méthodes pour calculer les dimensions de la mise en page pour qu'elles correspondent au rapport hauteur / largeur de la taille de caméra choisie. Il vous suffit maintenant de définir LayoutParamsla disposition d'aperçu de votre appareil photo. Modifiez la largeur, la hauteur et centrez-la dans le parent.

Il existe deux choix pour calculer les dimensions de l'aperçu. Soit vous voulez qu'il s'adapte à l'écran avec des barres noires (si windowBackground est défini sur null) sur les côtés ou en haut / en bas. Ou vous voulez que l'aperçu soit agrandi en plein écran . J'ai laissé un commentaire avec plus d'informations calcCamPrevDimensions().

Will Neithan
la source
6

Salut le getOptimalPreview () qui est ici n'a pas fonctionné pour moi donc je veux partager ma version:

private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {

    if (sizes==null) return null;

    Camera.Size optimalSize = null;
    double ratio = (double)h/w;
    double minDiff = Double.MAX_VALUE;
    double newDiff;
    for (Camera.Size size : sizes) {
        newDiff = Math.abs((double)size.width/size.height - ratio);
        if (newDiff < minDiff) {
            optimalSize = size;
            minDiff = newDiff;
        }
    }
    return optimalSize;
}
EstebanA
la source
il se convertit en une caméra carrée, étant donné que l'entrée est (1920x1080) après avoir optimisé la largeur et la hauteur qu'il a donné (1088x1088), mon appareil de test est Samsung S6. Puis-je savoir quelle est la raison. veuillez partager si vous avez une idée concernant ce problème.
MohanRaj S
2

Juste pour rendre ce fil plus complet, j'ajoute ma version de réponse:

Ce que je voulais réaliser: La vue de la surface ne doit pas être étirée et elle doit couvrir tout l'écran.De plus, il n'y avait qu'un mode paysage dans mon application.

Solution:

La solution est une extension extrêmement petite à la solution de F1sher:

=> La première étape consiste à intégrer la solution de F1sher.

=> Maintenant, il peut se produire un scénario dans la solution de F1sher lorsque la vue de surface ne couvre pas tout l'écran, la solution est de rendre la vue de surface plus grande que les dimensions de l'écran afin qu'elle couvre tout l'écran, pour cela:

    size = getOptimalPreviewSize(mCamera.getParameters().getSupportedPreviewSizes(), screenWidth, screenHeight);

    Camera.Parameters parameters = mCamera.getParameters();
    parameters.setPreviewSize(size.width, size.height);


    mCamera.setParameters(parameters);      

    double screenRatio = (double) screenHeight / screenWidth;
    double previewRatio = (double) size.height / size.width;

    if (previewRatio > screenRatio)     /*if preview ratio is greater than screen ratio then we will have to recalculate the surface height while keeping the surface width equal to the screen width*/
    {
        RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams(screenWidth, (int) (screenWidth * previewRatio));
        params1.addRule(RelativeLayout.CENTER_IN_PARENT);
        flPreview.setLayoutParams(params1);

        flPreview.setClipChildren(false);

        LayoutParams surfaceParams = new LayoutParams(screenWidth, (int) (screenWidth * previewRatio));
        surfaceParams.gravity = Gravity.CENTER;
        mPreview.setLayoutParams(surfaceParams);        
    }
    else     /*if preview ratio is smaller than screen ratio then we will have to recalculate the surface width while keeping the surface height equal to the screen height*/
    {
        RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams((int) ((double) screenHeight / previewRatio), screenHeight);
        params1.addRule(RelativeLayout.CENTER_IN_PARENT);
        flPreview.setLayoutParams(params1);
        flPreview.setClipChildren(false);

        LayoutParams surfaceParams = new LayoutParams((int) ((double) screenHeight / previewRatio), screenHeight);
        surfaceParams.gravity = Gravity.CENTER;
        mPreview.setLayoutParams(surfaceParams);

    }       

    flPreview.addView(mPreview); 

  /*  The TopMost layout used is the RelativeLayout, flPreview is the FrameLayout in which Surface View is added, mPreview is an instance of a class which extends SurfaceView  */
Sarthak Mittal
la source
1

J'ai compris quel était le problème - c'est avec les changements d' orientation . Si vous changez l'orientation de la caméra à 90 ou 270 degrés, vous devez permuter la largeur et la hauteur des tailles prises en charge et tout ira bien.

La vue de surface doit également se trouver dans une disposition de cadre et avoir une gravité centrale.

Voici un exemple sur C # (Xamarin):

public void SurfaceChanged(ISurfaceHolder holder, Android.Graphics.Format format, int width, int height)
{
    _camera.StopPreview();

    // find best supported preview size

    var parameters = _camera.GetParameters();
    var supportedSizes = parameters.SupportedPreviewSizes;
    var bestPreviewSize = supportedSizes
        .Select(x => new { Width = x.Height, Height = x.Width, Original = x }) // HACK swap height and width because of changed orientation to 90 degrees
        .OrderBy(x => Math.Pow(Math.Abs(x.Width - width), 3) + Math.Pow(Math.Abs(x.Height - height), 2))
        .First();

    if (height == bestPreviewSize.Height && width == bestPreviewSize.Width)
    {
        // start preview if best supported preview size equals current surface view size

        parameters.SetPreviewSize(bestPreviewSize.Original.Width, bestPreviewSize.Original.Height);
        _camera.SetParameters(parameters);
        _camera.StartPreview();
    }
    else
    {
        // if not than change surface view size to best supported (SurfaceChanged will be called once again)

        var layoutParameters = _surfaceView.LayoutParameters;
        layoutParameters.Width = bestPreviewSize.Width;
        layoutParameters.Height = bestPreviewSize.Height;
        _surfaceView.LayoutParameters = layoutParameters;
    }
}

Faites attention que les paramètres de la caméra doivent être définis comme taille d'origine (non échangés) et que la taille de la vue de surface doit être permutée.

Alexandre Danilov
la source
1

J'ai essayé toutes les solutions ci-dessus mais aucune d'elles ne fonctionne pour moi. Finalement, je l'ai résolu moi-même, et je trouve en fait que c'est assez facile. il y a deux points dont vous devez faire attention.

parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);

cette previewSize doit être l'une des résolutions prises en charge par la caméra, qui peut être obtenue comme ci-dessous:

List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes(); 

généralement l'un des rawSupportedSize est égal à la résolution de l'appareil.

Deuxièmement, placez votre SurfaceView dans un FrameLayout et définissez la hauteur et la largeur de la disposition de la surface dans la méthode surfaceChanged comme ci-dessus.

FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) surfaceView.getLayoutParams();
layoutParams.height = cameraResolution.x;
layoutParams.width = cameraResolution.y;

Ok, les choses sont faites, j'espère que cela pourra vous aider.

SalutonMondo
la source
0

Mes exigences sont que l'aperçu de la caméra doit être plein écran et conserver le rapport hauteur / largeur. La solution de Hesam et Yoosuf était excellente, mais je vois un problème de zoom élevé pour une raison quelconque.

L'idée est la même, avoir le centre du conteneur de prévisualisation dans le parent et augmenter la largeur ou la hauteur en fonction des proportions jusqu'à ce qu'il puisse couvrir tout l'écran.

Une chose à noter est que la taille de l'aperçu est en paysage car nous définissons l'orientation de l'affichage.

camera.setDisplayOrientation (90);

Le conteneur auquel nous ajouterons la vue SurfaceView:

<RelativeLayout
    android:id="@+id/camera_preview_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerInParent="true"/>

Ajoutez l'aperçu à son conteneur avec le centre du parent dans votre activité.

this.cameraPreview = new CameraPreview(this, camera);
cameraPreviewContainer.removeAllViews();
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
cameraPreviewContainer.addView(cameraPreview, 0, params);

À l'intérieur de la classe CameraPreview:

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    // 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 (holder.getSurface() == null) {
        // preview surface does not exist
        return;
    }

    stopPreview();

    // set preview size and make any resize, rotate or
    // reformatting changes here
    try {
        Camera.Size nativePictureSize = CameraUtils.getNativeCameraPictureSize(camera);
        Camera.Parameters parameters = camera.getParameters();
        parameters.setPreviewSize(optimalSize.width, optimalSize.height);
        parameters.setPictureSize(nativePictureSize.width, nativePictureSize.height);
        camera.setParameters(parameters);
        camera.setDisplayOrientation(90);
        camera.setPreviewDisplay(holder);
        camera.startPreview();

    } catch (Exception e){
        Log.d(TAG, "Error starting camera preview: " + e.getMessage());
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);

    if (supportedPreviewSizes != null && optimalSize == null) {
        optimalSize = CameraUtils.getOptimalSize(supportedPreviewSizes, width, height);
        Log.i(TAG, "optimal size: " + optimalSize.width + "w, " + optimalSize.height + "h");
    }

    float previewRatio =  (float) optimalSize.height / (float) optimalSize.width;
    // previewRatio is height/width because camera preview size are in landscape.
    float measuredSizeRatio = (float) width / (float) height;

    if (previewRatio >= measuredSizeRatio) {
        measuredHeight = height;
        measuredWidth = (int) ((float)height * previewRatio);
    } else {
        measuredWidth = width;
        measuredHeight = (int) ((float)width / previewRatio);
    }
    Log.i(TAG, "Preview size: " + width + "w, " + height + "h");
    Log.i(TAG, "Preview size calculated: " + measuredWidth + "w, " + measuredHeight + "h");

    setMeasuredDimension(measuredWidth, measuredHeight);
}
Howard Lin
la source
-1

Vous devez définir cameraView.getLayoutParams (). Height et cameraView.getLayoutParams (). Width en fonction du rapport hauteur / largeur souhaité.

user2919210
la source
est-ce différent du paramétrage de la vue de ma caméra de cette façon (ce que je fais actuellement): parameters.setPreviewSize (optimalSize.width, optimalSize.height); mCamera.setParameters (paramètres);
scientiffic
-2

J'ai abandonné les calculs et obtenir simplement la taille de la vue où je veux que l'aperçu de la caméra soit affiché et définir la taille de l'aperçu de la caméra de la même manière (juste inversée en largeur / hauteur en raison de la rotation) dans ma mise en œuvre SurfaceView personnalisée:

@Override // CameraPreview extends SurfaceView implements SurfaceHolder.Callback { 
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    Display display = ((WindowManager) getContext().getSystemService(
            Context.WINDOW_SERVICE)).getDefaultDisplay();

    if (display.getRotation() == Surface.ROTATION_0) {
        final Camera.Parameters params = camera.getParameters();
        // viewParams is from the view where the preview is displayed
        params.setPreviewSize(viewParams.height, viewParams.width);
        camera.setDisplayOrientation(90);
        requestLayout();
        camera.setParameters(params);
    }
    // I do not enable rotation, so this can otherwise stay as is
}
Martin Šuráb
la source
Le document Android dit: Lors de la définition de la taille de l'aperçu, vous devez utiliser les valeurs de getSupportedPreviewSizes (). Ne définissez pas de valeurs arbitraires dans la méthode setPreviewSize ().
G.Steigert