1.问题描述

 

在android camera2基础上取到img的Y,U,V三个通道数据后,生成uyvy格式并转为nv12后发生崩溃现象,打开logcat日志,发现有如下异常信息:

2022-12-15 17:14:54.881 9593-9628/com.arcsoft.se1000dmstestbed E/AndroidRuntime: FATAL EXCEPTION: BasicCamera2Thread
    Process: com.arcsoft.se1000dmstestbed, PID: 9593
    java.lang.IllegalStateException: maxImages (1) has already been acquired, call #close before acquiring more.
        at android.media.ImageReader.acquireNextImage(ImageReader.java:527)
        at com.arcsoft.se1000dmstestbed.BasicCamera2$4.onImageAvailable(BasicCamera2.java:343)
        at android.media.ImageReader$ListenerHandler.handleMessage(ImageReader.java:812)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.os.HandlerThread.run(HandlerThread.java:67)

2.camera2业务流程

mBackgroundHandler = new Handler(mBackgroundThread.getLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(@NonNull Message message) {
                if (message.what != 1) {
                    return false;
                }
                if (null == mByteNv12) {
                    mByteNv12 = new byte[mPreviewReader.getWidth() * mPreviewReader.getHeight() * 3 / 2];
                }
                if ((true == mDebugUYVY) && (null == mByteUYVY)) {
                    mByteUYVY = new byte[mPreviewReader.getWidth() * mPreviewReader.getHeight() * 2];
                }
                long curTimeMillis = System.currentTimeMillis();
                final Image img = (Image)message.obj;
                Log.d(TAG, "Capture Y size " + img.getPlanes()[0].getBuffer().remaining());
                Log.d(TAG, "Capture U size " + img.getPlanes()[1].getBuffer().remaining());
                Log.d(TAG, "Capture V size " + img.getPlanes()[2].getBuffer().remaining());
                Log.d(TAG, "Preview wdith:" + mPreviewReader.getWidth() + " height:" + mPreviewReader.getHeight());
                boolean ret = getBytesForImage(img);
                img.close();
                ...
}

如上述代码,在获取ImageReader中img数据后,执行了getBytesForImage业务,然后关闭img:

//private boolean getBytesForImage(final Image img) {
private boolean getBytesForImage(Image img) {
    int imageType = YuvUtil.getImageType(img);
    if (YuvUtil.NoneType == imageType) {
        img.close();
        Log.e(TAG, "can not get correct yuv format from image!");
        return false;
    }
    byte byteY[] = new byte[img.getPlanes()[0].getBuffer().remaining()];
    byte byteU[] = new byte[img.getPlanes()[1].getBuffer().remaining()];
    byte byteV[] = new byte[img.getPlanes()[2].getBuffer().remaining()];
    img.getPlanes()[0].getBuffer().get(byteY, 0, img.getPlanes()[0].getBuffer().remaining());
    img.getPlanes()[1].getBuffer().get(byteU, 0, img.getPlanes()[1].getBuffer().remaining());
    img.getPlanes()[2].getBuffer().get(byteV, 0, img.getPlanes()[2].getBuffer().remaining());
    img.close();    // quickly close the image to prevent abnormality
    switch (imageType) {
        case YuvUtil.SE1000:
            if (true == mDebugUYVY) {
                YuvUtil.makeUYVY(byteY, byteU, byteV, mByteUYVY);
            }
            else {
                YuvUtil.changeUYVYToNV12(byteY, byteU, byteV, mPreviewReader.getWidth(), mByteNv12);
            }
            break;
        case YuvUtil.YUV420P:
            YuvUtil.Yuv420ToNV12(byteY, byteU, byteV, mPreviewReader.getWidth(), mPreviewReader.getHeight(), mByteNv12);
            break;
        default:
            break;
    }
    return true;
}

在imagerReader初始化时后只有imagebuffer容量是1,如下所示:

mPreviewReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), mPreviewFormat,1);

这样的话会导致1个问题,当imageReader回调函数将Image放入缓冲区,而后台线程并没有执行Image.close,这就会导致如上述错误,因此修改imageReader的缓冲区为3,如下所示:

mPreviewReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), mPreviewFormat,3);  // stored 3 images

3.从img生成uyvy代码

由于摄像头是uyvy格式,因此camera2拿到的数据Y、U、V也满足YUV422格式,相应代码如下:

    public static void makeUYVY(byte[] yuv420y, byte[] yuv420u, byte[] yuv420v, byte[] uyvy) {
        int yLen = yuv420y.length;
        int uLen = yuv420u.length;
        int vLen = yuv420v.length;
        int yPos = 0;
        int uPos = 0;
        int vPos = 0;
        int offset = 0;
        // copy uyvy
        while ((yPos < yLen) && (yPos + 1 < yLen) && (uPos < uLen) && (vPos < vLen)) {
            uyvy[offset++] = yuv420u[uPos++];
            uyvy[offset++] = yuv420y[yPos++];
            uyvy[offset++] = yuv420v[vPos++];
            uyvy[offset++] = yuv420y[yPos++];
        }
    }

4.由uyvy转nv12代码

    public static void changeUYVYToNV12(byte[] yuv420y, byte[] yuv420u, byte[] yuv420v, int width, byte[] nv12) {
        // copy y channel
        System.arraycopy(yuv420y,0, nv12, 0, yuv420y.length);
        // copy uv channel
        int offset = yuv420y.length;
        int uv_size = yuv420u.length;
        int uv_row_size = width / 2;        // One row of u accounts for half of the width
        int pos = 0;
        while (pos < uv_size) {
            nv12[offset++] = yuv420u[pos];
            nv12[offset++] = yuv420v[pos];
            if (0 == ((pos + 1) % uv_row_size)) {
                pos += (uv_row_size + 1);
            }
            else {
                pos++;
            }
        }
    }

5.问题解决

修改业务代码,如下所示,尽快关闭img

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐