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 并希望它有效) .
我正在使用 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 并希望它有效) .