Vista previa de la cámara de Android estirada

136

He estado trabajando para hacer mi actividad de cámara personalizada en Android, pero al girar la cámara, la relación de aspecto de la vista de la superficie se confunde.

En mi creación de la actividad, configuré el framelayout que contiene la vista de superficie que muestra los parámetros de la cámara.

//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);

Luego, en la vista de superficie, configuro los parámetros de la cámara para que se muestren

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

    }

ingrese la descripción de la imagen aquí

Puedes ver que el hombre LEGO se hace más alto y más delgado cuando se gira el teléfono:

¿Cómo puedo asegurarme de que la relación de aspecto para la vista de mi cámara sea correcta?

Scientiffic
fuente
@científico, ¿pueden ayudarme, por favor, dónde debo escribir el método dado que también enfrento el mismo problema?
user3233280
Parecía tener un problema similar, pero lo que solucionó fue llamar a lo siguiente: mCamera.setDisplayOrientation (90) en el método surfaceCreated () de mi SurfaceView para la cámara
Greg

Respuestas:

163

Estoy usando este método -> basado en Demos API para obtener mi Tamaño de vista previa:

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

Como puede ver, debe ingresar el ancho y la altura de su pantalla. Este método calculará la relación de pantalla en función de esos valores y luego, de la lista de tamaños de vista previa admitidos, elegirá el mejor para usted de los disponibles. Obtenga su lista SupportPreviewSize en el lugar donde el objeto Camera no es nulo utilizando

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

Y luego en onMeasure puede obtener su vista previa óptima Tamaño así:

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

Y luego (en mi código en el método SurfaceChanged, como dije que estoy usando la estructura API Demos del código CameraActivity, puede generarlo en Eclipse):

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

Y una pista para ti, porque hice casi la misma aplicación que tú. Una buena práctica para Camera Activity es ocultar StatusBar. Aplicaciones como Instagram lo están haciendo. Reduce el valor de altura de la pantalla y cambia el valor de la relación. Es posible obtener tamaños de vista previa extraños en algunos dispositivos (su SurfaceView se reducirá un poco)


Y para responder a su pregunta, ¿cómo verificar si su relación de vista previa es correcta? Luego obtenga el alto y el ancho de los parámetros que configuró:

mCamera.setParameters(parameters);

su relación establecida es igual a la altura / ancho. Si desea que la cámara se vea bien en su pantalla, la relación altura / ancho de los parámetros que configure en la cámara debe ser la misma que la relación altura (menos la barra de estado) / ancho de la pantalla.

F1sher
fuente
2
Solo una pregunta: veo que estás usando 2 razones calculadas diferentes: doble targetRatio = (doble) h / w y double ratio = (doble) size.width / size.height . El primero es la altura dividida por el ancho y el otro es el opuesto, el ancho dividido por la altura. ¿No deberían ser el mismo cálculo?
phyzalis
No importa porque la lista de tamaños tiene en cuenta todas las posibilidades, por ejemplo: encontrará algo como 480x680, pero tarde o temprano habrá 680x480 (para el paisaje), por lo que solo tiene que revisar esa lista y encontrar una que coincide con tus necesidades Lo encontrarás tarde o temprano.
F1sher
Además, si desea que sus imágenes también tengan este tamaño (como probablemente deberían ser), ha configurado el tamaño de la imagen con parameters.setPictureSize(mPreviewSize.width, mPreviewSize.height).
Matt Logan
2
@ F1sher, siempre obtengo parámetros de excepción de puntero nulo.setPreviewSize (mPreviewSize.width, mPreviewSize.height); No puedo encontrar ninguna solución.
FIXI
3
@phyzalis Estoy de acuerdo contigo. De hecho, tuve que hacer esa corrección para que el código funcionara como se esperaba.
fernio
149

La solución de F1Sher es buena pero a veces no funciona. Particularmente, cuando su SurfaceView no cubre toda la pantalla. En este caso, debe anular el método onMeasure (). He copiado mi código aquí para su referencia.

Como medí SurfaceView en función del ancho, tengo un pequeño espacio en blanco al final de la pantalla que lo llené por diseño. Puede solucionar este problema si mantiene la altura y aumenta el ancho multiplicándolo por la relación. Sin embargo, aplastará ligeramente 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
fuente
11
esta debería ser la respuesta correcta, maneja el caso cuando la vista previa de la cámara no cubre toda la pantalla. +1.
Houcine
1
cuando inflo en mi actividad, la aplicación se cerró <com.app.camera.CameraPreview android: id = "@ + id / camera_preview" android: layout_width = "match_parent" android: layout_height = "match_paight" />
fish40
Sin embargo, debe realizar algunas modificaciones, pero acepto esto como una solución. Votado a favor.
JaydeepW
2
Hola @ adnan9011, este tutorial podría ser útil probablemente. airpair.com/android/android-camera-surface-view-fragment
Hesam
44
la altura y el ancho debían cambiarse getOptimalPreviewSizepara que esto funcionara para mí. ¿Es esto porque la orientación de la pantalla se estableció en 90? De lo contrario, los cálculos de la relación se ni siquiera cerca (objetivo era 1,7 y la relación de la cámara eran menos de 1)
ono
23

NOTA: MI SOLUCIÓN ES UNA CONTINUACIÓN DE LA SOLUCIÓN DE HESAM: https://stackoverflow.com/a/22758359/1718734

Lo que abordo: Hesam dijo que hay un pequeño espacio en blanco que puede aparecer en algunos teléfonos, como este:

NOTA: Aunque la relación de aspecto es correcta, la cámara no llena toda la pantalla.

Hesam sugirió una segunda solución, pero eso aplasta la vista previa. Y en algunos dispositivos, distorsiona mucho.

Entonces, ¿cómo solucionamos este problema? Es simple ... multiplicando las relaciones de aspecto hasta que llene la pantalla. He notado que varias aplicaciones populares como Snapchat, WhatsApp, etc. funcionan de la misma manera.

Todo lo que tiene que hacer es agregar esto al método 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);
    }

Esto calculará la altura de la pantalla y obtendrá la relación entre la altura de la pantalla y la altura mPreviewSize. Luego, multiplica el ancho y la altura de la cámara por la nueva relación de altura y establece la dimensión medida en consecuencia.

ingrese la descripción de la imagen aquí

Y lo siguiente que sabes es que terminas con esto: D

ingrese la descripción de la imagen aquí

Esto también funciona bien con la cámara frontal. Creo que esta es la mejor manera de hacerlo. Ahora lo único que queda para mi aplicación es guardar la vista previa al hacer clic en "Capturar". Pero ya, esto es todo.

Yoosuf
fuente
¡Esto funciona muy bien! pero no funciona correctamente en Paisaje: /
Matan Dahan
su código resolvió mi problema de espacio negro vacío de 2 meses :)
karthik kolanji
1
Funciona, pero en mi caso ... Tengo una barra de acción en la parte superior, por lo que la barra blanca era más pequeña, sin embargo, después de agregar su código, mi cámara se ve ampliada en 40-50%. Al cambiar a la cámara frontal, básicamente no puedo ver mi cara mirando recta (solo un poco de pelo), debido a ese alto zoom.
Makalele
@Yoosuf Me aparece una pantalla en blanco cuando seguí tu código. En el onMeasure(int widthMeasureSpec, int heightMeasureSpec)método siempre obtengo ancho = 0 y altura = 0;
Kush Patel
10

De acuerdo, creo que no hay una respuesta suficiente para el problema general de estiramiento de la vista previa de la cámara. O al menos no encontré uno. Mi aplicación también sufrió este síndrome de estiramiento y me tomó un tiempo encontrar una solución de todas las respuestas de los usuarios en este portal e Internet.

Intenté la solución de @ Hesam pero no funcionó y dejé la vista previa de mi cámara muy distorsionada.

Primero muestro el código de mi solución (las partes importantes del código) y luego explico por qué tomé esos pasos. Hay espacio para modificaciones de rendimiento.

Diseño xml de actividad principal:

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

Vista previa de la cámara:

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

Actividad principal:

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

Mi comentario:

El punto de todo esto es, que si bien se calcula el tamaño de la cámara óptima en getOptimalPreviewSize()sólo recoger la relación más cercana para adaptarse a su pantalla. Entonces, a menos que la relación sea exactamente la misma, la vista previa se extenderá.

¿Por qué se estirará? Porque la vista previa de su cámara FrameLayout está configurada en layout.xml para emparejar_parent en ancho y alto. Por eso, la vista previa se extenderá a pantalla completa.

Lo que debe hacerse es establecer el ancho y la altura del diseño de la vista previa de la cámara para que coincida con la relación de tamaño de la cámara elegida , de modo que la vista previa mantenga su relación de aspecto y no se distorsione.

Traté de usar la CameraPreviewclase para hacer todos los cálculos y cambios de diseño, pero no pude resolverlo. Traté de aplicar esta solución , pero SurfaceViewno reconoce getChildCount ()o getChildAt (int index). Creo que finalmente lo hice funcionar con una referencia a maLayoutPreview, pero se estaba comportando mal y apliqué la proporción establecida a toda mi aplicación y lo hizo después de tomar la primera fotografía. Así que lo dejé pasar y moví las modificaciones de diseño al MainActivity.

En CameraPreviewCambié prSupportedPreviewSizesy getOptimalPreviewSize()en público para poder usarlo MainActivity. Luego necesitaba las dimensiones de la pantalla (menos la barra de navegación / estado si hay una) y elegí el tamaño óptimo de la cámara . Intenté obtener el tamaño RelativeLayout (o FrameLayout) en lugar del tamaño de visualización, pero estaba devolviendo un valor cero. Esta solución no me funcionó. El diseño obtuvo su valor después onWindowFocusChanged(marcado en el registro).

Así que tengo mis métodos para calcular las dimensiones del diseño para que coincidan con la relación de aspecto del tamaño de cámara elegido. Ahora solo necesita configurar LayoutParamsel diseño de vista previa de su cámara. Cambie el ancho, la altura y céntrelo en padre.

Hay dos opciones para calcular las dimensiones de la vista previa. O desea que se ajuste a la pantalla con barras negras (si windowBackground está configurado como nulo) en los lados o arriba / abajo. O desea que la vista previa se amplíe a pantalla completa . Dejé un comentario con más información en calcCamPrevDimensions().

Will Neithan
fuente
6

Hola, el getOptimalPreview () que está aquí no funcionó para mí, así que quiero compartir mi versión:

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
fuente
se convierte en una cámara cuadrada, la entrada dada es (1920x1080) después de optimizar el ancho y la altura que dio (1088x1088), mi dispositivo de prueba es Samsung S6. ¿Puedo saber cuál es la razón? por favor comparta si tiene alguna idea sobre este tema.
MohanRaj S
2

Solo para hacer este hilo más completo, estoy agregando mi versión de respuesta:

Lo que quería lograr: la vista de superficie no debería extenderse, y debería cubrir toda la pantalla. Además, solo había un modo horizontal en mi aplicación.

Solución:

La solución es una extensión extremadamente pequeña de la solución de F1sher:

=> El primer paso es integrar la solución de F1sher.

=> Ahora, puede surgir un escenario en la solución de F1sher cuando la vista de superficie no cubre toda la pantalla, la solución es hacer que la vista de superficie sea mayor que las dimensiones de la pantalla para que cubra toda la pantalla, para eso:

    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
fuente
1

Descubrí cuál es el problema: es con los cambios de orientación . Si cambia la orientación de la cámara a 90 o 270 grados, debe cambiar el ancho y la altura de los tamaños admitidos y todo estará bien.

También la vista de la superficie debe estar en un diseño de marco y tener un centro de gravedad.

Aquí hay un ejemplo en 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;
    }
}

Tenga en cuenta que los parámetros de la cámara deben establecerse como tamaño original (no intercambiados) y el tamaño de la vista de superficie debe intercambiarse.

Alexander Danilov
fuente
1

Intenté toda la solución anterior pero ninguna de ellas funciona para mí. Finalmente lo resolví yo mismo, y encuentro que en realidad es bastante fácil. Hay dos puntos que debes tener cuidado.

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

Este tamaño de vista previa debe ser una de las resoluciones compatibles con la cámara, que se puede obtener de la siguiente manera:

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

generalmente uno de rawSupportedSize equivale a la resolución del dispositivo.

En segundo lugar, coloque su SurfaceView en FrameLayout y configure la altura y el ancho del diseño de la superficie en el método SurfaceChanged como se indicó anteriormente

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

Ok, ya está hecho, espero que esto pueda ayudarte.

SalutonMondo
fuente
0

Mis requisitos son que la vista previa de la cámara debe ser de pantalla completa y mantener la relación de aspecto. La solución de Hesam y Yoosuf fue excelente, pero veo un problema de zoom alto por alguna razón.

La idea es la misma, tener el centro del contenedor de vista previa en padre y aumentar el ancho o la altura dependen de las relaciones de aspecto hasta que pueda cubrir toda la pantalla.

Una cosa a tener en cuenta es que el tamaño de la vista previa es horizontal porque configuramos la orientación de la pantalla.

camera.setDisplayOrientation (90);

El contenedor al que agregaremos la vista SurfaceView:

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

Agregue la vista previa a su contenedor con el centro en padre en su actividad.

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

Dentro de la clase 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
fuente
-1

Debe establecer cameraView.getLayoutParams (). Height y cameraView.getLayoutParams (). Width según la relación de aspecto que desee.

usuario2919210
fuente
¿Es esto diferente de configurar el parámetro de la vista de mi cámara de esta manera (que estoy haciendo actualmente): parameters.setPreviewSize (opticalSize.width, opticalSize.height); mCamera.setParameters (parámetros);
scientiffic
-2

Renuncié a los cálculos y simplemente obtuve el tamaño de la vista donde quiero que se muestre la vista previa de la cámara y configuré el mismo tamaño de vista previa de la cámara (solo volteo / ancho debido a la rotación) en mi implementación personalizada de SurfaceView:

@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
fuente
El documento de Android dice: Al configurar el tamaño de la vista previa, debe usar valores de getSupportedPreviewSizes (). No establezca valores arbitrarios en el método setPreviewSize ().
G. Steigert