Rozciągnięty podgląd aparatu w Androidzie

136

Pracowałem nad wykonaniem mojej niestandardowej aktywności aparatu w systemie Android, ale podczas obracania aparatu proporcje widoku powierzchni ulegają zepsuciu.

W moim utworze do ćwiczenia ustawiłem układ framelayout, który zawiera widok powierzchni, który wyświetla parametry aparatu.

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

Następnie w widoku powierzchni ustawiam parametry kamery do wyświetlania

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

    }

wprowadź opis obrazu tutaj

Widać, że człowiek LEGO staje się wyższy i chudszy, gdy telefon jest obrócony:

Jak mogę się upewnić, że proporcje obrazu z mojej kamery są prawidłowe?

naukowy
źródło
@scientific czy u pls mogą mi pomóc, gdzie powinienem napisać daną metodę, w której również mam ten sam problem?
user3233280
Wydawało mi się, że mam podobny problem, ale to, co naprawiło, to wywołanie następującego: mCamera.setDisplayOrientation (90) w metodzie surfaceCreated () mojego SurfaceView dla aparatu
Greg

Odpowiedzi:

163

Używam tej metody -> na podstawie demonstracji API, aby uzyskać rozmiar podglądu:

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

Jak widać, musisz wprowadzić szerokość i wysokość ekranu. Ta metoda obliczy współczynnik ekranu na podstawie tych wartości, a następnie z listy obsługiwanych rozmiarów podglądu wybierze najlepszy dla Ciebie spośród dostępnych. Uzyskaj listę supportedPreviewSize w miejscu, w którym obiekt Camera nie jest null, za pomocą

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

A następnie w onMeasure możesz uzyskać optymalny podgląd rozmiaru:

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

A potem (w moim kodzie w metodzie surfaceChanged, tak jak powiedziałem, używam struktury API Demos kodu CameraActivity, możesz wygenerować ją w Eclipse):

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

I jedna wskazówka dla Ciebie, ponieważ zrobiłem prawie taką samą aplikację jak Ty. Dobrą praktyką w przypadku aktywności kamery jest ukrycie paska stanu. Aplikacje takie jak Instagram robią to. Zmniejsza wartość wysokości ekranu i zmienia wartość współczynnika. Na niektórych urządzeniach można uzyskać dziwne rozmiary podglądu (Twój SurfaceView zostanie trochę przycięty)


A odpowiadając na pytanie, jak sprawdzić, czy współczynnik podglądu jest poprawny? Następnie uzyskaj wysokość i szerokość parametrów, które ustawiasz w:

mCamera.setParameters(parameters);

Twój ustawiony stosunek jest równy wysokości / szerokości. Jeśli chcesz, aby kamera dobrze wyglądała na ekranie, stosunek wysokości do szerokości parametrów, które ustawisz dla kamery, musi być taki sam, jak stosunek wysokości (bez paska stanu) do szerokości ekranu.

F1sher
źródło
2
Tylko jedno pytanie: Widzę, że używasz 2 różnych obliczonych współczynników: podwójny targetRatio = (podwójny) h / w i podwójny współczynnik = (podwójny) size.width / size.height . Pierwsza to wysokość podzielona przez szerokość, a druga to odwrotność, szerokość podzielona przez wysokość. Czy nie powinny to być te same obliczenia?
phyzalis
Nie ma to znaczenia, ponieważ lista rozmiarów bierze pod uwagę każdą możliwość, na przykład: znajdziesz coś takiego jak 480x680, ale prędzej czy później będzie 680x480 (dla krajobrazu), więc wystarczy przejrzeć tę listę i znaleźć taki, który odpowiada Twoim potrzebom. Znajdziesz to prędzej czy później.
F1sher
Ponadto, jeśli chcesz, aby Twoje zdjęcia również miały ten rozmiar (tak jak prawdopodobnie powinny), ustaw rozmiar obrazu za pomocą parameters.setPictureSize(mPreviewSize.width, mPreviewSize.height).
Matt Logan
2
@ F1sher, zawsze otrzymuję parametry wyjątku wskaźnika o wartości null.setPreviewSize (mPreviewSize.width, mPreviewSize.height); Nie mogę znaleźć rozwiązania.
FIXI
3
@phyzalis Zgadzam się z tobą. W rzeczywistości musiałem wprowadzić tę korektę, aby kod działał zgodnie z oczekiwaniami.
fernio
149

Rozwiązanie F1Sher jest fajne, ale czasami nie działa. Szczególnie, gdy SurfaceView nie obejmuje całego ekranu. W takim przypadku musisz nadpisać metodę onMeasure (). Skopiowałem tutaj mój kod w celach informacyjnych.

Ponieważ zmierzyłem surfaceView na podstawie szerokości, na końcu ekranu mam małą białą lukę, którą wypełniłem zgodnie z projektem. Możesz rozwiązać ten problem, jeśli zachowasz wysokość i zwiększysz szerokość, mnożąc ją przez współczynnik. Jednak spowoduje to nieznaczne zgniecenie 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
źródło
11
to powinna być prawidłowa odpowiedź, obsługuje przypadek, gdy podgląd z kamery nie obejmuje całego ekranu. +1.
Houcine
1
kiedy zwiększam poziom aktywności, aplikacja została zamknięta <com.app.camera.CameraPreview android: id = "@ + id / camera_preview" android: layout_width = "match_parent" android: layout_height = "match_parent" />
fish40
Musisz jednak wprowadzić pewne modyfikacje, ale przyjmuję to jako rozwiązanie. Głosowano za.
Jaydeep Ś,
2
Cześć @ adnan9011, ten samouczek może być prawdopodobnie pomocny. airpair.com/android/android-camera-surface-view-fragment
Hesam.
4
wysokość i szerokość trzeba było zmienić, getOptimalPreviewSizeaby to zadziałało. czy to dlatego, że orientacja wyświetlania została ustawiona na 90? W przeciwnym razie, obliczenia ratio nie były nawet blisko (target wynosił 1,7 i proporcje były mniej niż kamera 1)
Ono
23

UWAGA: MOJE ROZWIĄZANIE TO KONTYNUACJA ROZWIĄZANIA HESAM: https://stackoverflow.com/a/22758359/1718734

Do czego się zwracam: Hesam powiedział, że na niektórych telefonach może pojawić się mała biała przestrzeń, na przykład:

UWAGA: Chociaż współczynnik proporcji jest prawidłowy, aparat nie wypełnia całego ekranu.

Hesam zasugerował drugie rozwiązanie, ale to ogranicza podgląd. A na niektórych urządzeniach mocno zniekształca.

Jak więc rozwiązać ten problem. Jest to proste ... mnożąc proporcje, aż wypełni ekran. Zauważyłem, że kilka popularnych aplikacji, takich jak Snapchat, WhatsApp itp., Działa w ten sam sposób.

Wszystko, co musisz zrobić, to dodać to do metody 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);
    }

To obliczy wysokość ekranu i pobierze stosunek wysokości ekranu do wysokości mPreviewSize. Następnie mnoży szerokość i wysokość kamery przez nowy współczynnik wysokości i odpowiednio ustawia zmierzony wymiar.

wprowadź opis obrazu tutaj

I następna rzecz, którą wiesz, kończy się na tym: D

wprowadź opis obrazu tutaj

Działa to również dobrze z przednim aparatem. Uważam, że to najlepszy sposób, aby to osiągnąć. Teraz jedyne, co pozostało dla mojej aplikacji, to zapisanie samego podglądu po kliknięciu przycisku „Przechwyć”. Ale tak, to jest to.

Yoosuf
źródło
To działa bardzo dobrze !! ale to nie działa poprawnie w Landscape: /
Matan Dahan
Twój kod rozwiązał mój 2-miesięczny problem z pustą czarną przestrzenią :)
karthik kolanji
1
Działa, ale w moim przypadku .. mam actionbar na górze, więc biały pasek był mniejszy, jednak po dodaniu twojego kodu mój wygląd kamery powiększył się o 40-50%. Po przełączeniu na przedni aparat w zasadzie nie widzę mojej twarzy, która wygląda prosto (tylko trochę włosów) z powodu dużego zoomu.
Makalele
@Yoosuf Otrzymuję biały ekran, gdy śledzę twój kod. W onMeasure(int widthMeasureSpec, int heightMeasureSpec)metodzie zawsze otrzymuję szerokość = 0 i wysokość = 0;
Kush Patel
10

OK, więc myślę, że nie ma wystarczającej odpowiedzi na ogólny problem z rozciąganiem podglądu kamery. A przynajmniej nie znalazłem. Moja aplikacja również cierpiała na syndrom rozciągania i zajęło mi trochę czasu, aby wspólnie znaleźć rozwiązanie ze wszystkich odpowiedzi użytkowników w tym portalu i internecie.

Wypróbowałem rozwiązanie @ Hesam, ale nie zadziałało i mój podgląd kamery został znacznie zniekształcony.

Najpierw pokazuję kod mojego rozwiązania (ważne części kodu), a następnie wyjaśniam, dlaczego podjąłem te kroki. Jest miejsce na modyfikacje wydajności.

Układ XML głównej aktywności:

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

Podgląd aparatu:

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

Główna aktywność:

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

Mój komentarz:

Punktem wszystkim jest to, że chociaż można obliczyć wielkość optymalna kamery w getOptimalPreviewSize()wybrać tylko najbliższy proporcje dopasowane do rozmiaru ekranu. Więc jeśli stosunek nie jest dokładnie taki sam, podgląd zostanie rozciągnięty.

Dlaczego się rozciągnie? Ponieważ podgląd kamery FrameLayout jest ustawiony w pliku layout.xml na match_parent pod względem szerokości i wysokości. Dlatego podgląd rozciąga się na pełny ekran.

To, co należy zrobić, to ustawić szerokość i wysokość układu podglądu kamery, aby pasowały do ​​wybranego współczynnika rozmiaru kamery , aby podgląd zachował proporcje i nie zniekształcił.

Próbowałem użyć CameraPreviewklasy, aby wykonać wszystkie obliczenia i zmiany układu, ale nie mogłem tego rozgryźć. Próbowałem zastosować to rozwiązanie , ale SurfaceViewnie rozpoznaje getChildCount ()lub getChildAt (int index). Myślę, że w końcu udało mi się to zadziałać z odniesieniem do maLayoutPreview, ale źle się zachowywał i zastosowałem ustawiony współczynnik do całej mojej aplikacji i tak się stało po zrobieniu pierwszego zdjęcia. Więc odpuściłem i przeniosłem modyfikacje układu do MainActivity.

W CameraPreviewzmieniłem prSupportedPreviewSizesi getOptimalPreviewSize()do publiczności , dzięki czemu można go używać w MainActivity. Następnie potrzebowałem wymiarów wyświetlacza (bez paska nawigacji / stanu, jeśli taki istnieje) i wybrałem optymalny rozmiar kamery . Próbowałem uzyskać rozmiar RelativeLayout (lub FrameLayout) zamiast rozmiaru wyświetlacza, ale zwracał wartość zerową. To rozwiązanie nie zadziałało. Układ ma swoją wartość po onWindowFocusChanged(zaznaczony w dzienniku).

Mam więc swoje metody obliczania wymiarów układu, aby pasowały do ​​proporcji wybranego rozmiaru aparatu. Teraz wystarczy ustawić LayoutParamsukład podglądu aparatu. Zmień szerokość, wysokość i wyśrodkuj w nadrzędnym.

Istnieją dwie możliwości obliczenia wymiarów podglądu. Albo chcesz, aby pasował do ekranu z czarnymi paskami (jeśli windowBackground ma wartość null) po bokach lub u góry / u dołu. Lub chcesz, aby podgląd był powiększony do pełnego ekranu . Zostawiłem komentarz z większą ilością informacji w calcCamPrevDimensions().

Will Neithan
źródło
6

Cześć, getOptimalPreview (), który jest tutaj, nie działał dla mnie, więc chcę udostępnić moją wersję:

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
źródło
konwertuje do kwadratowej kamery, Podane dane wejściowe to (1920 x 1080) po optymalizacji szerokości i wysokości, które podała (1088x1088), Moim urządzeniem testowym jest Samsung S6. Czy mogę wiedzieć, jaki jest tego powód. udostępnij, jeśli masz jakiś pomysł na ten problem.
MohanRaj S
2

Aby uzupełnić ten wątek, dodaję moją wersję odpowiedzi:

Co chciałem osiągnąć: widok powierzchni nie powinien być rozciągany, a powinien obejmować cały ekran, ponadto w mojej aplikacji był tylko tryb krajobrazu.

Rozwiązanie:

Rozwiązaniem jest niezwykle małe rozszerzenie rozwiązania F1sher:

=> Pierwszym krokiem jest integracja rozwiązania F1sher.

=> Teraz w rozwiązaniu F1sher może pojawić się scenariusz, w którym widok powierzchni nie obejmuje całego ekranu.Rozwiązaniem jest, aby widok powierzchni był większy niż wymiary ekranu, tak aby obejmował cały ekran, w tym celu:

    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
źródło
1

Zrozumiałem, w czym problem - to ze zmianami orientacji . Jeśli zmienisz orientację kamery na 90 lub 270 stopni, musisz zamienić szerokość i wysokość obsługiwanych rozmiarów i wszystko będzie w porządku.

Również widok powierzchni powinien leżeć w układzie ramy i mieć środek ciężkości.

Oto przykład w 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;
    }
}

Zwróć uwagę, że parametry kamery powinny być ustawione jako oryginalne (nie zamienione), a rozmiar widoku powierzchni powinien zostać zamieniony.

Aleksandra Daniłowa
źródło
1

Wypróbowałem wszystkie powyższe rozwiązania, ale żadne z nich nie działa dla mnie. w końcu rozwiązałem to sam i stwierdziłem, że jest to całkiem łatwe. trzeba uważać na dwa punkty.

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

ten rozmiar podglądu musi być jedną z obsługiwanych przez kamerę rozdzielczości, którą można uzyskać jak poniżej:

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

zwykle jeden z rawSupportedSize jest równy rozdzielczości urządzenia.

Po drugie, umieść SurfaceView w FrameLayout i ustaw wysokość i szerokość układu powierzchni w metodzie surfaceChanged, jak powyżej

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

OK, gotowe, mam nadzieję, że to ci pomoże.

SalutonMondo
źródło
0

Moje wymagania są takie, że podgląd z kamery musi być pełnoekranowy i zachować proporcje. Rozwiązanie Hesama i Yoosufa było świetne, ale z jakiegoś powodu widzę problem z dużym zoomem.

Pomysł jest taki sam, umieść środek kontenera podglądu w nadrzędnym i zwiększaj szerokość lub wysokość w zależności od współczynników proporcji, aż pokryje cały ekran.

Należy zwrócić uwagę na to, że rozmiar podglądu jest w poziomie, ponieważ ustawiamy orientację wyświetlania.

camera.setDisplayOrientation (90);

Kontener, do którego dodamy widok SurfaceView:

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

Dodaj podgląd do jego kontenera ze środkiem w rodzicu w swojej aktywności.

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

W klasie 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
źródło
-1

Musisz ustawić cameraView.getLayoutParams (). Height i cameraView.getLayoutParams (). Width zgodnie z wymaganym współczynnikiem proporcji.

user2919210
źródło
różni się to od ustawienia parametru widoku z mojej kamery w ten sposób (co obecnie robię): parameters.setPreviewSize (optimSize.width, optimSize.height); mCamera.setParameters (parametry);
naukowy
-2

Zrezygnowałem z obliczeń i po prostu uzyskałem rozmiar widoku, w którym chcę wyświetlać podgląd kamery i ustawiłem ten sam rozmiar podglądu kamery (po prostu odwrócił szerokość / wysokość z powodu obrotu) w mojej niestandardowej implementacji 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
źródło
Dokument dotyczący Androida mówi: Ustawiając rozmiar podglądu, musisz użyć wartości z getSupportedPreviewSizes (). Nie ustawiaj dowolnych wartości w metodzie setPreviewSize ().
G. Steigert,