Visualização da câmera do Android esticada

136

Eu tenho trabalhado na criação da minha atividade de câmera personalizada no Android, mas ao girar a câmera, a proporção da visualização da superfície fica confusa.

No meu trabalho para a atividade, defino o layout da moldura que mantém a visualização da superfície que exibe os parâmetros da câmera.

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

Em seguida, na visualização Surface, defino os parâmetros da câmera a serem exibidos

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

    }

insira a descrição da imagem aqui

Você pode ver que o homem LEGO cresce mais alto e mais fino quando o telefone é girado:

Como posso garantir que a proporção da visualização da minha câmera esteja correta?

cientifico
fonte
@scientific @ u pls pode me ajudar onde devo escrever o método dado também estou enfrentando o mesmo problema?
user3233280
Eu parecia ter um problema semelhante, mas o que fixo que estava chamando o seguinte: mCamera.setDisplayOrientation (90) no método surfaceCreated () do meu SurfaceView para a câmera
Greg

Respostas:

163

Estou usando esse método -> com base nas demonstrações da API para obter meu tamanho de visualização:

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 você pode ver, é necessário inserir a largura e a altura da tela. Este método calculará a proporção da tela com base nesses valores e, a partir da lista de supportedPreviewSizes, escolherá o melhor para você dentre os disponíveis. Obtenha sua lista supportedPreviewSize no local em que o objeto Câmera não é nulo usando

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

E, em seguida, no onMeasure, você pode obter sua visualização ideal

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

E então (no meu código no método surfaceChanged, como eu disse que estou usando a estrutura API Demos do código CameraActivity, você pode gerá-lo no Eclipse):

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

E uma dica para você, porque eu fiz quase o mesmo aplicativo como você. A boa prática para a atividade da câmera é ocultar a barra de status. Aplicativos como o Instagram estão fazendo isso. Reduz o valor da altura da tela e altera o valor da proporção. É possível obter tamanhos de visualização estranhos em alguns dispositivos (o SurfaceView será cortado um pouco)


E para responder sua pergunta, como verificar se a taxa de visualização está correta? Em seguida, obtenha a altura e a largura dos parâmetros definidos:

mCamera.setParameters(parameters);

sua proporção definida é igual a altura / largura. Se você deseja que a câmera tenha uma boa aparência na tela, a proporção de altura / largura dos parâmetros configurados para a câmera deve ser a mesma que a proporção de altura (menos barra de status) / largura da tela.

F1sher
fonte
2
Só uma pergunta: Eu vejo você está usando 2 relação diferente computadorizada: double targetRatio = / w (duplo) h e relação de dupla = (double) size.width / size.height . O primeiro é a altura dividida pela largura e o outro é o oposto, a largura dividida pela altura. Eles não devem ser a mesma computação?
Phyzalis
Não importa, porque a lista de tamanhos leva em consideração todas as possibilidades, por exemplo: você encontrará algo como 480x680, mas mais cedo ou mais tarde haverá 680x480 (para paisagem), então você só precisa examinar essa lista e encontrar uma que corresponde às suas necessidades. Você o encontrará mais cedo ou mais tarde.
F1sher
Além disso, se você quiser que suas fotos tenham esse tamanho também (como provavelmente deveriam), defina o tamanho da imagem parameters.setPictureSize(mPreviewSize.width, mPreviewSize.height).
Matt Logan
2
@ F1sher, eu sempre recebo a exceção de ponteiro nulo parameters.setPreviewSize (mPreviewSize.width, mPreviewSize.height); Não foi possível encontrar nenhuma solução.
FIXI 30/09/14
3
@phyzalis Concordo com você. Na verdade, eu tive que fazer essa correção para que o código funcionasse conforme o esperado.
19615 fernio
149

A solução da F1Sher é boa, mas às vezes não funciona. Particularmente, quando o seu surfaceView não cobre a tela inteira. Nesse caso, você precisa substituir o método onMeasure (). Copiei meu código aqui para sua referência.

Como medi o surfaceView com base na largura, tenho um pouco de espaço branco no final da tela que preenchai por design. Você poderá corrigir esse problema se mantiver a altura e aumentar a largura multiplicando-a pela proporção. No entanto, ele irá pressionar o SurfaceView levemente.

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
fonte
11
essa deve ser a resposta correta; ele lida com o caso quando a visualização da câmera não cobre a tela inteira. +1.
21414 Houcine
1
quando eu inflar na minha candidatura atividade foi fechada <com.app.camera.CameraPreview Android: id = "@ + id / camera_preview" android: layout_width = "match_parent" android: layout_height = "match_parent" />
fish40
Você precisa fazer algumas modificações, mas eu aceito isso como uma solução. Up-vote.
precisa saber é o seguinte
2
Olá @ adnan9011, este tutorial pode ser útil provavelmente. airpair.com/android/android-camera-surface-view-fragment
Hesam
4
a altura e a largura precisavam ser trocadas getOptimalPreviewSizepara que isso funcionasse para mim. isso ocorre porque a orientação da tela foi definida como 90? Caso contrário, os cálculos da proporção não estavam nem perto (o objetivo era 1,7 e a proporção da câmera era menor que 1)
6/11/15
23

NOTA: MINHA SOLUÇÃO É UMA CONTINUAÇÃO DA SOLUÇÃO DE HESAM: https://stackoverflow.com/a/22758359/1718734

O que eu dirijo: Hesam disse que há um pequeno espaço em branco que pode aparecer em alguns telefones, como este:

NOTA: Embora a proporção esteja correta, a câmera não preenche a tela inteira.

Hesam sugeriu uma segunda solução, mas isso estraga a visualização. E em alguns dispositivos, isso distorce bastante.

Então, como podemos resolver esse problema. É simples ... multiplicando as proporções até preencher a tela. Eu notei que vários aplicativos populares como Snapchat, WhatsApp etc. funcionam da mesma maneira.

Tudo o que você precisa fazer é adicionar isso ao 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);
    }

Isso calculará a altura da tela e obterá a proporção entre a altura da tela e a altura do mPreviewSize. Em seguida, multiplica a largura e a altura da câmera pela nova taxa de altura e define a dimensão medida de acordo.

insira a descrição da imagem aqui

E a próxima coisa que você sabe, você acaba com isso: D

insira a descrição da imagem aqui

Isso também funciona bem com a câmera frontal. Eu acredito que esta é a melhor maneira de fazer isso. Agora, a única coisa que resta para o meu aplicativo é salvar a visualização em si mesma ao clicar em "Capturar". Mas sim, é isso.

Yoosuf
fonte
Isso funciona muito bem !! mas ele não funciona corretamente no Landscape: /
Matan Dahan
seu código resolveu o meu problema 2 meses vazio espaço preto :)
karthik kolanji
1
Funciona, mas no meu caso .. Eu tenho a barra de ação na parte superior, então a barra branca ficou menor, no entanto, depois de adicionar o código, minha aparência da câmera aumentou de 40 a 50%. Ao mudar para a câmera frontal, basicamente não consigo ver meu rosto olhando reto (apenas um pouco de cabelo), por causa desse zoom alto.
Makalele
@Yoosuf Estou recebendo uma tela branca quando segui seu código. No onMeasure(int widthMeasureSpec, int heightMeasureSpec)método, sempre obtenho largura = 0 e altura = 0;
Kush Patel
10

OK, então eu acho que não há resposta suficiente para o problema geral de alongamento da visualização da câmera. Ou pelo menos não encontrei um. Meu aplicativo também sofreu essa síndrome de alongamento e demorei um pouco para encontrar uma solução de todas as respostas dos usuários neste portal e na Internet.

Eu tentei a solução da @ Hesam mas ela não funcionou e deixou a visualização da câmera distorcida.

Primeiro, mostro o código da minha solução (as partes importantes do código) e depois explico por que tomei essas medidas. Há espaço para modificações de desempenho.

Layout xml da atividade 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>

Visualização da câmera:

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

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

Meu comentário:

O ponto de tudo isso é que, embora você calcular o tamanho da câmera ideal em getOptimalPreviewSize()você só escolher o rácio mais próximo para caber na tela. Portanto, a menos que a proporção seja exatamente a mesma, a visualização será ampliada.

Por que isso vai esticar? Como a visualização da câmera do FrameLayout está definida em layout.xml para corresponder_parent em largura e altura. É por isso que a visualização será ampliada para tela cheia.

O que precisa ser feito é definir a largura e a altura do layout da visualização da câmera para corresponder à proporção de tamanho da câmera escolhida , para que a visualização mantenha sua proporção e não distorça.

Tentei usar a CameraPreviewclasse para fazer todos os cálculos e alterações de layout, mas não consegui descobrir. Eu tentei aplicar esta solução , mas SurfaceViewnão reconhece getChildCount ()ou getChildAt (int index). Acho que consegui trabalhar eventualmente com uma referência a maLayoutPreview, mas estava se comportando mal e aplicou a proporção definida para todo o meu aplicativo e o fez depois que a primeira foto foi tirada. Então eu deixei para lá e mudei as modificações de layout para oMainActivity .

Em CameraPreviewmudei prSupportedPreviewSizese getOptimalPreviewSize()para público para que eu possa usá-lo MainActivity. Então eu precisei das dimensões da tela (menos a barra de navegação / status, se houver) e escolhi o tamanho ideal da câmera . Tentei obter o tamanho do RelativeLayout (ou FrameLayout) em vez do tamanho da exibição, mas ele estava retornando valor zero. Esta solução não funcionou para mim. O layout obteve seu valor apósonWindowFocusChanged (verificado no log).

Então, eu tenho meus métodos para calcular as dimensões do layout para corresponder à proporção do tamanho de câmera escolhido. Agora você só precisa definirLayoutParams o layout de visualização da câmera. Mude a largura, altura e centralize-a no pai.

Existem duas opções de como calcular as dimensões da visualização. Você deseja que ele se ajuste à tela com barras pretas (se windowBackground estiver definido como nulo) nas laterais ou na parte superior / inferior. Ou você deseja que a visualização seja ampliada para tela cheia . Deixei um comentário com mais informações em calcCamPrevDimensions().

Will Neithan
fonte
6

Olá, o getOptimalPreview () que está aqui não funcionou para mim, por isso quero compartilhar minha versão:

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
fonte
ele se converte em uma câmera quadrada, considerando que a entrada é (1920x1080) depois de otimizar a largura e a altura (1088x1088), meu dispositivo de teste é o Samsung S6. Posso saber qual é o motivo. compartilhe se você tiver alguma idéia sobre esse problema.
Mohanraj S
2

Apenas para tornar este tópico mais completo, estou adicionando minha versão da resposta:

O que eu queria alcançar: a visualização da superfície não deve ser esticada e deve cobrir a tela inteira. Além disso, havia apenas um modo paisagem no meu aplicativo.

Solução:

A solução é uma extensão extremamente pequena para a solução F1sher:

=> O primeiro passo é integrar a solução F1sher.

=> Agora, pode surgir um cenário na solução do F1sher quando a visualização da superfície não cobre a tela inteira. A solução é tornar a visualização da superfície maior que as dimensões da tela para que cubra a tela inteira, para:

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

Eu descobri qual é o problema - é com as mudanças de orientação . Se você alterar a orientação da câmera para 90 ou 270 graus, precisará trocar a largura e a altura dos tamanhos suportados e tudo ficará bem.

A visualização da superfície também deve estar em um layout de quadro e ter gravidade central.

Aqui está um exemplo em 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;
    }
}

Preste atenção que os parâmetros da câmera devem ser definidos como tamanho original (não trocados) e o tamanho da visualização da superfície deve ser trocada.

Alexander Danilov
fonte
1

Eu tentei toda a solução acima, mas nenhum deles funciona para mim. Finalmente eu mesmo resolvi e acho que é bem fácil. Existem dois pontos que você precisa ter cuidado.

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

este previewSize deve ser uma das resoluções suportadas pela câmera, que pode ser obtida abaixo:

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

geralmente um dos rawSupportedSize é igual à resolução do dispositivo.

Segundo, coloque o SurfaceView em um FrameLayout e defina a altura e a largura do layout da superfície no método surfaceChanged como acima

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

Ok, pronto, espero que isso possa ajudá-lo.

SalutonMondo
fonte
0

Meus requisitos são que a visualização da câmera precise ter tela cheia e manter a proporção. A solução de Hesam e Yoosuf foi ótima, mas vejo um problema de zoom alto por algum motivo.

A idéia é a mesma, ter o centro de contêiner de visualização no pai e aumentar a largura ou a altura, dependendo das proporções, até que possa cobrir a tela inteira.

Uma coisa a observar é que o tamanho da visualização está em paisagem porque definimos a orientação da exibição.

camera.setDisplayOrientation (90);

O contêiner ao qual adicionaremos a visualização SurfaceView:

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

Adicione a visualização ao seu contêiner com o centro no pai na sua atividade.

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 da 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
fonte
-1

Você deve definir cameraView.getLayoutParams (). Height e cameraView.getLayoutParams (). Width de acordo com a proporção que deseja.

user2919210
fonte
isso é diferente de definir o parâmetro da visualização da minha câmera dessa maneira (o que estou fazendo atualmente): parameters.setPreviewSize (maximumSize.width, idealSize.height); mCamera.setParameters (parâmetros);
scientiffic
-2

Eu desisti dos cálculos e simplesmente obtive o tamanho da visualização em que eu quero que a visualização da câmera seja exibida e defina o tamanho da visualização da mesma (apenas a largura / altura invertida devido à rotação) na minha implementação personalizada do 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
fonte
O documento do Android diz: Ao definir o tamanho da visualização, você deve usar valores de getSupportedPreviewSizes (). Não defina valores arbitrários no método setPreviewSize ().
G. Steigert 20/02