Android Kamera Vorschau gestreckt

136

Ich habe daran gearbeitet, meine benutzerdefinierte Kameraaktivität unter Android zu erstellen, aber beim Drehen der Kamera wird das Seitenverhältnis der Oberflächenansicht durcheinander gebracht.

In meinem Oncreate für die Aktivität habe ich das Framelayout festgelegt, das die Oberflächenansicht enthält, in der die Parameter der Kamera angezeigt werden.

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

Dann habe ich in der Oberflächenansicht die Parameter der Kamera eingestellt, die angezeigt werden sollen

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

    }

Geben Sie hier die Bildbeschreibung ein

Sie können sehen, dass der LEGO-Mann größer und dünner wird, wenn das Telefon gedreht wird:

Wie kann ich sicherstellen, dass das Seitenverhältnis für meine Kameraansicht korrekt ist?

wissenschaftlich
quelle
@scientific Kannst du mir helfen, wo soll ich die angegebene Methode schreiben? Ich stehe auch vor dem gleichen Problem?
user3233280
Ich schien ein ähnliches Problem zu bekommen, aber was behoben wurde, war das Aufrufen des folgenden: mCamera.setDisplayOrientation (90) in der SurfaceCreated () -Methode meiner SurfaceView für die Kamera
Greg

Antworten:

163

Ich verwende diese Methode -> basierend auf API-Demos, um meine Vorschaugröße zu erhalten:

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

Wie Sie sehen, müssen Sie Breite und Höhe Ihres Bildschirms eingeben. Diese Methode berechnet das Bildschirmverhältnis basierend auf diesen Werten und wählt dann aus der Liste der unterstützten PreviewSizes das für Sie beste aus den verfügbaren aus. Stellen Sie Ihre unterstützte PreviewSize-Liste an einen Ort, an dem das Kameraobjekt nicht null ist

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

Und dann können Sie in onMeasure Ihre optimale Vorschau erhalten.

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

Und dann (in meinem Code in der SurfaceChanged-Methode, wie ich bereits sagte, verwende ich die API-Demostruktur des CameraActivity-Codes, können Sie ihn in Eclipse generieren):

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

Und ein Hinweis für Sie, denn ich habe fast die gleiche App wie Sie gemacht. Es empfiehlt sich, die Statusleiste auszublenden. Anwendungen wie Instagram machen es. Es reduziert Ihren Bildschirmhöhenwert und ändert Ihren Verhältniswert. Auf einigen Geräten können seltsame Vorschaugrößen angezeigt werden (Ihre SurfaceView wird ein wenig gekürzt).


Und um Ihre Frage zu beantworten, wie können Sie überprüfen, ob Ihr Vorschau-Verhältnis korrekt ist? Dann erhalten Sie Höhe und Breite der Parameter, die Sie eingestellt haben:

mCamera.setParameters(parameters);

Ihr festgelegtes Verhältnis entspricht Höhe / Breite. Wenn die Kamera auf Ihrem Bildschirm gut aussehen soll, muss das Verhältnis von Höhe zu Breite der von Ihnen auf Kamera eingestellten Parameter dem Verhältnis von Höhe (minus Statusleiste) zu Breite Ihres Bildschirms entsprechen.

F1sher
quelle
2
Nur eine Frage: Ich sehe, dass Sie 2 verschiedene berechnete Verhältnisse verwenden: double targetRatio = (double) h / w und double ratio = (double) size.width / size.height . Die erste ist die Höhe geteilt durch die Breite und die andere ist das Gegenteil, die Breite geteilt durch die Höhe. Sollten sie nicht die gleiche Berechnung sein?
Phyzalis
Es spielt keine Rolle, da die Liste der Größen jede Möglichkeit berücksichtigt, zum Beispiel: Sie werden so etwas wie 480x680 finden, aber früher oder später wird es 680x480 (für Querformat) geben, also müssen Sie nur diese Liste durchgehen und eine finden, die entspricht Ihren Bedürfnissen. Sie werden es früher oder später finden.
F1sher
Wenn Sie möchten, dass Ihre Bilder auch diese Größe haben (wie sie wahrscheinlich sein sollten), haben Sie die Bildgröße mit festgelegt parameters.setPictureSize(mPreviewSize.width, mPreviewSize.height).
Matt Logan
2
@ F1sher, ich erhalte immer Nullzeiger-Ausnahmeparameter.setPreviewSize (mPreviewSize.width, mPreviewSize.height); Ich kann keine Lösung finden.
FIXI
3
@phyzalis Ich stimme dir zu. Tatsächlich musste ich diese Korrektur vornehmen, damit der Code wie erwartet funktioniert.
Fernio
149

Die Lösung von F1Sher ist nett, funktioniert aber manchmal nicht. Insbesondere, wenn Ihre SurfaceView nicht den gesamten Bildschirm abdeckt. In diesem Fall müssen Sie die onMeasure () -Methode überschreiben. Ich habe meinen Code hier als Referenz kopiert.

Da ich die Oberflächenansicht anhand der Breite gemessen habe, habe ich am Ende meines Bildschirms eine kleine weiße Lücke, die ich beabsichtigt ausgefüllt habe. Sie können dieses Problem beheben, wenn Sie die Höhe beibehalten und die Breite erhöhen, indem Sie sie mit dem Verhältnis multiplizieren. Die Oberflächenansicht wird jedoch leicht gequetscht.

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
quelle
11
Dies sollte die richtige Antwort sein. Sie behandelt den Fall, dass die Vorschau der Kamera nicht den gesamten Bildschirm abdeckt. +1.
Houcine
1
Als ich mich in meiner Aktivität aufblähte, wurde die Anwendung geschlossen. <com.app.camera.CameraPreview android: id = "@ + id / camera_preview" android: layout_width = "match_parent" android: layout_height = "match_parent" />
fish40
Sie müssen zwar einige Änderungen vornehmen, aber ich akzeptiere dies als Lösung. Up-Voted.
JaydeepW
2
Hallo @ adnan9011, dieses Tutorial könnte wahrscheinlich hilfreich sein. airpair.com/android/android-camera-surface-view-fragment
Hesam
4
Die Höhe und Breite mussten umgeschaltet werden, getOptimalPreviewSizedamit dies für mich funktioniert. Liegt das daran, dass die Ausrichtung des Displays auf 90 eingestellt wurde? Ansonsten wurden die Verhältnisberechnungen nicht einmal in der Nähe (Ziel war 1,7 und die Kamera - Verhältnisse kleiner als 1)
Ono
23

HINWEIS: MEINE LÖSUNG IST EINE FORTSETZUNG DER HESAM-LÖSUNG: https://stackoverflow.com/a/22758359/1718734

Was ich anspreche: Hesam hat gesagt, dass auf einigen Telefonen ein kleiner weißer Bereich angezeigt wird:

HINWEIS: Obwohl das Seitenverhältnis korrekt ist, füllt die Kamera nicht den gesamten Bildschirm aus.

Hesam schlug eine zweite Lösung vor, aber das drückt die Vorschau zusammen. Und auf einigen Geräten verzerrt es stark.

Wie können wir dieses Problem beheben? Es ist einfach ... durch Multiplizieren der Seitenverhältnisse, bis der Bildschirm ausgefüllt ist. Ich habe festgestellt, dass einige beliebte Apps wie Snapchat, WhatsApp usw. auf die gleiche Weise funktionieren.

Sie müssen dies lediglich zur onMeasure-Methode hinzufügen:

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

Dadurch wird die Bildschirmhöhe berechnet und das Verhältnis zwischen Bildschirmhöhe und mPreviewSize-Höhe ermittelt. Anschließend multipliziert es die Breite und Höhe der Kamera mit dem neuen Höhenverhältnis und stellt die gemessene Abmessung entsprechend ein.

Geben Sie hier die Bildbeschreibung ein

Und das nächste, was Sie wissen, ist Folgendes: D.

Geben Sie hier die Bildbeschreibung ein

Dies funktioniert auch gut mit der Frontkamera. Ich glaube, das ist der beste Weg, dies zu tun. Jetzt muss meine App nur noch die Vorschau selbst speichern, wenn Sie auf "Erfassen" klicken. Aber ja, das ist es.

Yoosuf
quelle
Das funktioniert sehr gut !! aber es funktioniert nicht richtig in Landschaft: /
Matan Dahan
Ihr Code löste mein 2 Monate leeres Schwarzraumproblem :)
karthik kolanji
1
Es funktioniert, aber in meinem Fall. Ich habe oben eine Aktionsleiste, sodass die weiße Leiste kleiner war. Nach dem Hinzufügen Ihres Codes wurde mein Kamera-Look jedoch um 40-50% vergrößert. Wenn ich zur nach vorne gerichteten Kamera wechsle, kann ich mein Gesicht aufgrund des hohen Zooms im Grunde nicht gerade sehen (nur ein bisschen Haar).
Makalele
@ Yoosuf Ich bekomme einen weißen Bildschirm, als ich Ihrem Code gefolgt bin. In der onMeasure(int widthMeasureSpec, int heightMeasureSpec)Methode bekomme ich immer Breite = 0 und Höhe = 0;
Kush Patel
10

OK, ich denke, es gibt keine ausreichende Antwort auf das allgemeine Problem mit der Dehnung der Kameravorschau. Zumindest habe ich keinen gefunden. Meine App litt auch unter diesem Stretching-Syndrom und es dauerte eine Weile, bis ich eine Lösung aus allen Benutzerantworten auf diesem Portal und im Internet gefunden hatte.

Ich habe die Lösung von @ Hesam ausprobiert, aber sie hat nicht funktioniert und meine Kamera-Vorschau stark verzerrt.

Zuerst zeige ich den Code meiner Lösung (die wichtigen Teile des Codes) und erkläre dann, warum ich diese Schritte unternommen habe. Es gibt Raum für Leistungsänderungen.

Hauptaktivität XML-Layout:

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

Kameravorschau:

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

Hauptaktivität:

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

Mein Kommentar:

Der Sinn all dessen ist, dass Sie, obwohl Sie die optimale Kameragröße in berechnen getOptimalPreviewSize(), nur das nächstgelegene Verhältnis auswählen, das zu Ihrem Bildschirm passt. Wenn das Verhältnis nicht genau gleich ist, wird die Vorschau gedehnt.

Warum wird es sich dehnen? Weil Ihre FrameLayout Kameravorschau wird in gesetzt layout.xml zu match_parent in Breite und Höhe. Aus diesem Grund wird die Vorschau auf den Vollbildmodus ausgedehnt.

Sie müssen lediglich die Breite und Höhe des Kamera-Vorschau-Layouts so einstellen , dass sie dem gewählten Kameragrößenverhältnis entsprechen , damit die Vorschau ihr Seitenverhältnis beibehält und nicht verzerrt.

Ich habe versucht, mit der CameraPreviewKlasse alle Berechnungen und Layoutänderungen vorzunehmen, konnte es aber nicht herausfinden. Ich habe versucht, diese Lösung anzuwenden , SurfaceViewerkenne aber nicht getChildCount ()oder getChildAt (int index). Ich denke, ich habe es irgendwann mit einem Verweis auf maLayoutPreviewzum Laufen gebracht, aber es hat sich schlecht benommen und das eingestellte Verhältnis auf meine gesamte App angewendet, und das tat es, nachdem das erste Bild aufgenommen wurde. Also ließ ich es los und verschob die Layoutänderungen auf die MainActivity.

In CameraPreviewich geändert prSupportedPreviewSizesund getOptimalPreviewSize()zu öffentlich , damit ich es in nutzen MainActivity. Dann brauchte ich die Anzeigeabmessungen (abzüglich der Navigations- / Statusleiste, falls vorhanden) und wählte die optimale Kameragröße . Ich habe versucht, die RelativeLayout-Größe (oder FrameLayout-Größe) anstelle der Anzeigegröße zu ermitteln, aber es wurde ein Wert von Null zurückgegeben. Diese Lösung hat bei mir nicht funktioniert. Das Layout hat seinen Wert nach onWindowFocusChanged(im Protokoll überprüft).

Ich habe also meine Methoden zur Berechnung der Layoutabmessungen, um sie an das Seitenverhältnis der ausgewählten Kameragröße anzupassen. Jetzt müssen Sie nur noch das LayoutParamsLayout Ihrer Kameravorschau festlegen . Ändern Sie die Breite, Höhe und zentrieren Sie sie im übergeordneten Element.

Es gibt zwei Möglichkeiten, die Vorschaudimensionen zu berechnen. Entweder möchten Sie, dass der Bildschirm mit schwarzen Balken (wenn windowBackground auf null gesetzt ist) an den Seiten oder oben / unten versehen wird. Oder Sie möchten, dass die Vorschau auf Vollbild vergrößert wird . Ich habe einen Kommentar mit weiteren Informationen in hinterlassen calcCamPrevDimensions().

Will Neithan
quelle
6

Hallo, das getOptimalPreview (), das hier ist, hat bei mir nicht funktioniert, daher möchte ich meine Version teilen:

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
quelle
Es wird in eine quadratische Kamera umgewandelt. Die Eingabe erfolgt (1920 x 1080), nachdem die Breite und Höhe (1088 x 1088) optimiert wurden. Mein Testgerät ist Samsung S6. Darf ich wissen, was der Grund ist. Bitte teilen Sie uns mit, wenn Sie eine Idee zu diesem Thema haben.
MohanRaj S
2

Um diesen Thread zu vervollständigen, füge ich meine Version der Antwort hinzu:

Was ich erreichen wollte: Die Oberflächenansicht sollte nicht gedehnt werden und den gesamten Bildschirm abdecken. Außerdem gab es in meiner App nur einen Querformatmodus.

Lösung:

Die Lösung ist eine extrem kleine Erweiterung der F1sher-Lösung:

=> Der erste Schritt ist die Integration der F1sher-Lösung.

=> Nun kann es in der F1sher-Lösung zu einem Szenario kommen, in dem die Oberflächenansicht nicht den gesamten Bildschirm abdeckt. Die Lösung besteht darin, die Oberflächenansicht größer als die Bildschirmabmessungen zu machen, sodass sie den gesamten Bildschirm abdeckt.

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

Ich habe herausgefunden, was das Problem ist - es ist mit Orientierungsänderungen . Wenn Sie die Kameraausrichtung auf 90 oder 270 Grad ändern, müssen Sie Breite und Höhe der unterstützten Größen austauschen, und alles ist in Ordnung.

Auch die Oberflächenansicht sollte in einem Rahmenlayout liegen und den Schwerpunkt haben.

Hier ist ein Beispiel für 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;
    }
}

Achten Sie darauf, dass die Kameraparameter als Originalgröße (nicht vertauscht) und die Größe der Oberflächenansicht vertauscht werden.

Alexander Danilov
quelle
1

Ich habe alle oben genannten Lösungen ausprobiert, aber keine davon funktioniert für mich. Schließlich habe ich es selbst gelöst und finde es eigentlich ganz einfach. Es gibt zwei Punkte, bei denen Sie vorsichtig sein müssen.

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

Diese Vorschaugröße muss eine der von der Kamera unterstützten Auflösungen sein, die wie folgt angezeigt werden können:

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

Normalerweise entspricht eine der rawSupportedSize der Geräteauflösung.

Zweitens platzieren Sie Ihre SurfaceView in einem FrameLayout und legen die Höhe und Breite des Oberflächenlayouts wie oben in der surfaceChanged-Methode fest

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

Ok, Dinge erledigt, hoffe das könnte dir helfen.

SalutonMondo
quelle
0

Meine Anforderungen sind, dass die Kameravorschau im Vollbildmodus sein und das Seitenverhältnis beibehalten muss. Die Lösung von Hesam und Yoosuf war großartig, aber ich sehe aus irgendeinem Grund ein Problem mit hohem Zoom.

Die Idee ist dieselbe: Lassen Sie das Vorschau-Container-Zentrum im übergeordneten Element und erhöhen Sie die Breite oder Höhe abhängig von den Seitenverhältnissen, bis es den gesamten Bildschirm abdecken kann.

Zu beachten ist, dass sich die Vorschaugröße im Querformat befindet, da wir die Ausrichtung der Anzeige festlegen.

camera.setDisplayOrientation (90);

Der Container, dem wir die SurfaceView-Ansicht hinzufügen:

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

Fügen Sie die Vorschau zu dem Container hinzu, in dessen Aktivität die Mitte im übergeordneten Element steht.

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

Innerhalb der CameraPreview-Klasse:

@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
quelle
-1

Sie müssen cameraView.getLayoutParams (). Height und cameraView.getLayoutParams (). Width entsprechend dem gewünschten Seitenverhältnis festlegen.

user2919210
quelle
unterscheidet sich dies vom Einstellen des Parameters meiner Kameraansicht auf diese Weise (was ich gerade mache): parameters.setPreviewSize (optimalSize.width, optimalSize.height); mCamera.setParameters (Parameter);
wissenschaftlicher
-2

Ich habe die Berechnungen aufgegeben und einfach die Größe der Ansicht abgerufen, in der die Kameravorschau angezeigt werden soll, und die Vorschaugröße der Kamera in meiner benutzerdefinierten SurfaceView-Implementierung gleich eingestellt (nur gespiegelte Breite / Höhe aufgrund von Drehung):

@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
quelle
Das Android-Dokument sagt: Wenn Sie die Vorschaugröße festlegen, müssen Sie Werte aus getSupportedPreviewSizes () verwenden. Legen Sie in der Methode setPreviewSize () keine beliebigen Werte fest.
G. Steigert