Android 视频处理 - 如何将 ImageReader Surface 连接到预览?

Android Video Processing - how to connect the ImageReader Surface to the preview?

我正在使用 Android 的 Camera2 API 并想对相机预览帧执行一些图像处理,然后在预览 (TextureView) 上显示更改。

从常见的 camera2video 示例开始,我在我的 openCamera() 中设置了一个 ImageReader。

    mImageReader = ImageReader.newInstance(mVideoSize.getWidth(),
    mVideoSize.getHeight(), ImageFormat.YUV_420_888, mMaxBufferedImages);
    mImageReader.setOnImageAvailableListener(mImageAvailable, mBackgroundHandler);

在我的 startPreview() 中,我设置了表面以从 CaptureRequest 接收帧。

    SurfaceTexture texture = mTextureView.getSurfaceTexture();
    assert texture != null;
    texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
    mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

    List<Surface> surfaces = new ArrayList<>();

    // Here is where we connect the mPreviewSurface to the mTextureView.
    mPreviewSurface = new Surface(texture);
    surfaces.add(mPreviewSurface);
    mPreviewBuilder.addTarget(mPreviewSurface);

    // Connect our Image Reader to the Camera to get the preview frames.
    Surface readerSurface = mImageReader.getSurface();
    surfaces.add(readerSurface);
    mPreviewBuilder.addTarget(readerSurface);

然后我将在 OnImageAvailableListener() 回调中修改图像数据。

ImageReader.OnImageAvailableListener mImageAvailable = new ImageReader.OnImageAvailableListener() {
    @Override
    public void onImageAvailable(ImageReader reader) {
        try {
            Image image = reader.acquireLatestImage();
            if (image == null)
                return;

            final Image.Plane[] planes = image.getPlanes();

            // Do something to the pixels.
            // Black out part of the image.
            ByteBuffer y_data_buffer = planes[0].getBuffer();
            byte[] y_data = new byte[y_data_buffer.remaining()];
            y_data_buffer.get(y_data);
            byte y_value;
            for (int row = 0; row < image.getHeight() / 2; row++) {
                for (int col = 0; col < image.getWidth() / 2; col++) {
                    y_value = y_data[row * image.getWidth() + col];
                    y_value = 0;
                    y_data[row * image.getWidth() + col] = y_value;
                }
            }

            image.close();
        } catch (IllegalStateException e) {
            Log.d(TAG, "mImageAvailable() Too many images acquired");
        }
    }
};

据我所知,我正在将图像发送到 2 个 Surface 实例,一个用于 mTextureView,另一个用于我的 ImageReader。

如何让我的 mTextureView 使用与 ImageReader 相同的表面,或者我应该直接从 mTextureView 的表面处理图像数据?

谢谢

如果您只想显示修改后的输出,那么我不确定您为什么要配置两个输出(TextureView 和 ImageReader)。

一般来说,如果您想要

camera -> in-app edits -> display

您有多种选择,具体取决于您想要的编辑类型,以及编码难易程度、性能等之间的各种权衡。

最有效的选择之一是作为 OpenGL 着色器进行编辑。 在这种情况下,GLSurfaceView 可能是最简单的选择。 使用 GLSurfaceView 的 EGL 上下文中未使用的纹理 ID 创建一个 SurfaceTexture 对象,并将从 SurfaceTexture 创建的 Surface 传递给相机会话和请求。 然后在SurfaceView的绘图方法中,调用SurfaceTexture的updateTexImage()方法,然后使用纹理ID来渲染你想要的输出。

这确实需要大量 OpenGL 代码,所以如果您不熟悉它,那可能会很有挑战性。

您也可以使用 RenderScript 来获得类似的效果;在那里您将有一个输出 SurfaceView 或 TextureView,然后是一个 RenderScript 脚本,该脚本从相机的输入分配中读取并将输出分配写入视图;您可以从表面创建此类分配。 Google HdrViewfinderDemo camera2 示例应用程序使用这种方法。样板文件少了很多。

第三,您可以像现在这样使用 ImageReader,但您必须自己进行大量转换才能将其写入屏幕。最简单(但最慢)的选择是从 SurfaceView 或 ImageView 获取 Canvas,然后将像素一个一个地写入它。或者你可以通过 ANativeWindow NDK 来做到这一点,它更快但需要编写 JNI 代码并且仍然需要你自己进行 YUV->RGB 转换(或者使用未记录的 API 将 YUV 推送到 ANativeWindow 并希望它有效) .