Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ef14da32 authored by Ruben Brunk's avatar Ruben Brunk
Browse files

camera2: Fix handling for abandoned bufferqueues.

Bug: 15116722

- Adds exception handling utility for legacy device.
- Skip/ignore abandoned surfaces in legacy camera.

Change-Id: Id4de587779d3bc2415e22b10bcc841cc46ac5f1d
parent bcd83651
Loading
Loading
Loading
Loading
+54 −10
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.view.Surface;
import java.util.ArrayList;
import java.util.List;

import static android.hardware.camera2.legacy.LegacyExceptionUtils.*;
import static android.hardware.camera2.utils.CameraBinderDecorator.*;
import static com.android.internal.util.Preconditions.*;

@@ -183,8 +184,8 @@ public class LegacyCameraDevice implements AutoCloseable {
     * @return {@code true} if the surfaces uses {@link ImageFormat#YUV_420_888} or a compatible
     *          format.
     */
    static boolean needsConversion(Surface s) {
        int nativeType = LegacyCameraDevice.nativeDetectSurfaceType(s);
    static boolean needsConversion(Surface s) throws BufferQueueAbandonedException {
        int nativeType = detectSurfaceType(s);
        return nativeType == ImageFormat.YUV_420_888 || nativeType == ImageFormat.YV12 ||
                nativeType == ImageFormat.NV21;
    }
@@ -377,28 +378,71 @@ public class LegacyCameraDevice implements AutoCloseable {
     * @throws NullPointerException if the {@code surface} was {@code null}
     * @throws IllegalStateException if the {@code surface} was invalid
     */
    static Size getSurfaceSize(Surface surface) {
    static Size getSurfaceSize(Surface surface) throws BufferQueueAbandonedException {
        checkNotNull(surface);

        int[] dimens = new int[2];
        nativeDetectSurfaceDimens(surface, /*out*/dimens);
        LegacyExceptionUtils.throwOnError(nativeDetectSurfaceDimens(surface, /*out*/dimens));

        return new Size(dimens[0], dimens[1]);
    }

    protected static native int nativeDetectSurfaceType(Surface surface);
    static int detectSurfaceType(Surface surface) throws BufferQueueAbandonedException {
        checkNotNull(surface);
        return LegacyExceptionUtils.throwOnError(nativeDetectSurfaceType(surface));
    }

    static void configureSurface(Surface surface, int width, int height,
                                 int pixelFormat) throws BufferQueueAbandonedException {
        checkNotNull(surface);
        checkArgumentPositive(width, "width must be positive.");
        checkArgumentPositive(height, "height must be positive.");

        LegacyExceptionUtils.throwOnError(nativeConfigureSurface(surface, width, height,
                pixelFormat));
    }

    static void produceFrame(Surface surface, byte[] pixelBuffer, int width,
                             int height, int pixelFormat)
            throws BufferQueueAbandonedException {
        checkNotNull(surface);
        checkNotNull(pixelBuffer);
        checkArgumentPositive(width, "width must be positive.");
        checkArgumentPositive(height, "height must be positive.");

        LegacyExceptionUtils.throwOnError(nativeProduceFrame(surface, pixelBuffer, width, height,
                pixelFormat));
    }

    static void setSurfaceFormat(Surface surface, int pixelFormat)
            throws BufferQueueAbandonedException {
        checkNotNull(surface);

        LegacyExceptionUtils.throwOnError(nativeSetSurfaceFormat(surface, pixelFormat));
    }

    static void setSurfaceDimens(Surface surface, int width, int height)
            throws BufferQueueAbandonedException {
        checkNotNull(surface);
        checkArgumentPositive(width, "width must be positive.");
        checkArgumentPositive(height, "height must be positive.");

        LegacyExceptionUtils.throwOnError(nativeSetSurfaceDimens(surface, width, height));
    }

    private static native int nativeDetectSurfaceType(Surface surface);

    protected static native void nativeDetectSurfaceDimens(Surface surface,
    private static native int nativeDetectSurfaceDimens(Surface surface,
            /*out*/int[/*2*/] dimens);

    protected static native void nativeConfigureSurface(Surface surface, int width, int height,
    private static native int nativeConfigureSurface(Surface surface, int width, int height,
                                                        int pixelFormat);

    protected static native void nativeProduceFrame(Surface surface, byte[] pixelBuffer, int width,
    private static native int nativeProduceFrame(Surface surface, byte[] pixelBuffer, int width,
                                                    int height, int pixelFormat);

    protected static native void nativeSetSurfaceFormat(Surface surface, int pixelFormat);
    private static native int nativeSetSurfaceFormat(Surface surface, int pixelFormat);

    protected static native void nativeSetSurfaceDimens(Surface surface, int width, int height);
    private static native int nativeSetSurfaceDimens(Surface surface, int width, int height);

}
+77 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.hardware.camera2.legacy;

import android.hardware.camera2.utils.CameraBinderDecorator;
import android.util.AndroidException;

/**
 * Utility class containing exception handling used solely by the compatibility mode shim.
 */
public class LegacyExceptionUtils {
    private static final String TAG = "LegacyExceptionUtils";

    /**
     * Checked exception thrown when a BufferQueue has been abandoned by its consumer.
     */
    public static class BufferQueueAbandonedException extends AndroidException {
        public BufferQueueAbandonedException () {}

        public BufferQueueAbandonedException(String name) {
            super(name);
        }

        public BufferQueueAbandonedException(String name, Throwable cause) {
            super(name, cause);
        }

        public BufferQueueAbandonedException(Exception cause) {
            super(cause);
        }
    }

    /**
     * Throw error codes used by legacy device methods as exceptions.
     *
     * <p>Non-negative return values are passed through, negative return values are thrown as
     * exceptions.</p>
     *
     * @param errorFlag error to throw as an exception.
     * @throws {@link BufferQueueAbandonedException} for {@link CameraBinderDecorator#ENODEV}.
     * @throws {@link UnsupportedOperationException} for an unknown negative error code.
     * @return {@code errorFlag} if the value was non-negative, throws otherwise.
     */
    public static int throwOnError(int errorFlag) throws BufferQueueAbandonedException {
        switch (errorFlag) {
            case CameraBinderDecorator.NO_ERROR: {
                return CameraBinderDecorator.NO_ERROR;
            }
            case CameraBinderDecorator.ENODEV: {
                throw new BufferQueueAbandonedException();
            }
        }

        if (errorFlag < 0) {
            throw new UnsupportedOperationException("Unknown error " + errorFlag);
        }
        return errorFlag;
    }

    private LegacyExceptionUtils() {
        throw new AssertionError();
    }
}
+26 −10
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.hardware.camera2.legacy;

import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.util.Log;
import android.view.Surface;

import java.util.Collection;
@@ -26,6 +27,7 @@ import java.util.Collection;
 * Immutable container for a single capture request and associated information.
 */
public class RequestHolder {
    private static final String TAG = "RequestHolder";

    private final boolean mRepeating;
    private final CaptureRequest mRequest;
@@ -89,9 +91,13 @@ public class RequestHolder {
     */
    public boolean hasJpegTargets() {
        for (Surface s : getHolderTargets()) {
            try {
                if (jpegType(s)) {
                    return true;
                }
            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
                Log.w(TAG, "Surface abandoned, skipping...", e);
            }
        }
        return false;
    }
@@ -102,9 +108,13 @@ public class RequestHolder {
     */
    public boolean hasPreviewTargets() {
        for (Surface s : getHolderTargets()) {
            try {
                if (previewType(s)) {
                    return true;
                }
            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
                Log.w(TAG, "Surface abandoned, skipping...", e);
            }
        }
        return false;
    }
@@ -115,9 +125,13 @@ public class RequestHolder {
     */
    public Surface getFirstPreviewTarget() {
        for (Surface s : getHolderTargets()) {
            try {
                if (previewType(s)) {
                    return s;
                }
            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
                Log.w(TAG, "Surface abandoned, skipping...", e);
            }
        }
        return null;
    }
@@ -128,8 +142,9 @@ public class RequestHolder {
     * @param s a {@link Surface} to check.
     * @return true if the surface requires a jpeg buffer.
     */
    public static boolean jpegType(Surface s) {
        if (LegacyCameraDevice.nativeDetectSurfaceType(s) ==
    public static boolean jpegType(Surface s)
            throws LegacyExceptionUtils.BufferQueueAbandonedException {
        if (LegacyCameraDevice.detectSurfaceType(s) ==
                CameraMetadataNative.NATIVE_JPEG_FORMAT) {
            return true;
        }
@@ -149,8 +164,9 @@ public class RequestHolder {
     * @param s a {@link Surface} to check.
     * @return true if the surface requires a non-jpeg buffer type.
     */
    public static boolean previewType(Surface s) {
        if (LegacyCameraDevice.nativeDetectSurfaceType(s) !=
    public static boolean previewType(Surface s)
            throws LegacyExceptionUtils.BufferQueueAbandonedException {
        if (LegacyCameraDevice.detectSurfaceType(s) !=
                CameraMetadataNative.NATIVE_JPEG_FORMAT) {
            return true;
        }
+37 −22
Original line number Diff line number Diff line
@@ -201,12 +201,16 @@ public class RequestThreadManager {
                return;
            }
            for (Surface s : holder.getHolderTargets()) {
                try {
                    if (RequestHolder.jpegType(s)) {
                        Log.i(TAG, "Producing jpeg buffer...");
                    LegacyCameraDevice.nativeSetSurfaceDimens(s, data.length, /*height*/1);
                    LegacyCameraDevice.nativeProduceFrame(s, data, data.length, /*height*/1,
                        LegacyCameraDevice.setSurfaceDimens(s, data.length, /*height*/1);
                        LegacyCameraDevice.produceFrame(s, data, data.length, /*height*/1,
                                CameraMetadataNative.NATIVE_JPEG_FORMAT);
                    }
                } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
                    Log.w(TAG, "Surface abandoned, dropping frame. ", e);
                }
            }
            mReceivedJpeg.open();
        }
@@ -323,7 +327,8 @@ public class RequestThreadManager {

        if (outputs != null) {
            for (Surface s : outputs) {
                int format = LegacyCameraDevice.nativeDetectSurfaceType(s);
                try {
                    int format = LegacyCameraDevice.detectSurfaceType(s);
                    switch (format) {
                        case CameraMetadataNative.NATIVE_JPEG_FORMAT:
                            mCallbackOutputs.add(s);
@@ -332,15 +337,21 @@ public class RequestThreadManager {
                            mPreviewOutputs.add(s);
                            break;
                    }
                } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
                    Log.w(TAG, "Surface abandoned, skipping...", e);
                }
            }
        }
        mParams = mCamera.getParameters();
        if (mPreviewOutputs.size() > 0) {
            List<Size> outputSizes = new ArrayList<>(outputs.size());
            for (Surface s : mPreviewOutputs) {
                int[] dimens = {0, 0};
                LegacyCameraDevice.nativeDetectSurfaceDimens(s, dimens);
                outputSizes.add(new Size(dimens[0], dimens[1]));
                try {
                    Size size = LegacyCameraDevice.getSurfaceSize(s);
                    outputSizes.add(size);
                } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
                    Log.w(TAG, "Surface abandoned, skipping...", e);
                }
            }

            Size largestOutput = findLargestByArea(outputSizes);
@@ -434,7 +445,8 @@ public class RequestThreadManager {
         */
        List<Size> configuredJpegSizes = new ArrayList<Size>();
        for (Surface callbackSurface : callbackOutputs) {
            int format = LegacyCameraDevice.nativeDetectSurfaceType(callbackSurface);
            try {
                int format = LegacyCameraDevice.detectSurfaceType(callbackSurface);

                if (format != CameraMetadataNative.NATIVE_JPEG_FORMAT) {
                    continue; // Ignore non-JPEG callback formats
@@ -442,6 +454,9 @@ public class RequestThreadManager {

                Size jpegSize = LegacyCameraDevice.getSurfaceSize(callbackSurface);
                configuredJpegSizes.add(jpegSize);
            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
                Log.w(TAG, "Surface abandoned, skipping...", e);
            }
        }
        if (!configuredJpegSizes.isEmpty()) {
            /*
+40 −28
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.util.Log;
import android.util.Size;
import android.view.Surface;

import java.nio.ByteBuffer;
@@ -338,22 +339,25 @@ public class SurfaceTextureRenderer {
        }

        int maxLength = 0;
        int[] dimens = new int[2];
        for (EGLSurfaceHolder holder : surfaces) {
            LegacyCameraDevice.nativeDetectSurfaceDimens(holder.surface, dimens);
            int length = dimens[0] * dimens[1];
            try {
                Size size = LegacyCameraDevice.getSurfaceSize(holder.surface);
                int length = size.getWidth() * size.getHeight();
                // Find max surface size, ensure PBuffer can hold this many pixels
                maxLength = (length > maxLength) ? length : maxLength;
                int[] surfaceAttribs = {
                    EGL14.EGL_WIDTH, dimens[0],
                    EGL14.EGL_HEIGHT, dimens[1],
                        EGL14.EGL_WIDTH, size.getWidth(),
                        EGL14.EGL_HEIGHT, size.getHeight(),
                        EGL14.EGL_NONE
                };
            holder.width = dimens[0];
            holder.height = dimens[1];
                holder.width = size.getWidth();
                holder.height = size.getHeight();
                holder.eglSurface =
                        EGL14.eglCreatePbufferSurface(mEGLDisplay, mConfigs, surfaceAttribs, 0);
                checkEglError("eglCreatePbufferSurface");
            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
                Log.w(TAG, "Surface abandoned, skipping...", e);
            }
        }
        mPBufferPixels = ByteBuffer.allocateDirect(maxLength * PBUFFER_PIXEL_BYTES)
                .order(ByteOrder.nativeOrder());
@@ -438,8 +442,9 @@ public class SurfaceTextureRenderer {

        for (Surface s : surfaces) {
            // If pixel conversions aren't handled by egl, use a pbuffer
            try {
                if (LegacyCameraDevice.needsConversion(s)) {
                LegacyCameraDevice.nativeSetSurfaceFormat(s, ImageFormat.YV12);
                    LegacyCameraDevice.setSurfaceFormat(s, ImageFormat.YV12);
                    EGLSurfaceHolder holder = new EGLSurfaceHolder();
                    holder.surface = s;
                    mConversionSurfaces.add(holder);
@@ -448,6 +453,9 @@ public class SurfaceTextureRenderer {
                    holder.surface = s;
                    mSurfaces.add(holder);
                }
            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
                Log.w(TAG, "Surface abandoned, skipping configuration... ", e);
            }
        }

        // Set up egl display
@@ -503,10 +511,14 @@ public class SurfaceTextureRenderer {
                GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height, GLES20.GL_RGBA,
                        GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
                checkGlError("glReadPixels");
                int format = LegacyCameraDevice.nativeDetectSurfaceType(holder.surface);
                LegacyCameraDevice.nativeProduceFrame(holder.surface, mPBufferPixels.array(),
                try {
                    int format = LegacyCameraDevice.detectSurfaceType(holder.surface);
                    LegacyCameraDevice.produceFrame(holder.surface, mPBufferPixels.array(),
                            holder.width, holder.height, format);
                    swapBuffers(holder.eglSurface);
                } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
                    Log.w(TAG, "Surface abandoned, dropping frame. ", e);
                }
            }
        }
    }
Loading