【发布时间】:2018-03-06 08:21:11
【问题描述】:
我正在 Android 设备上实现一个相机应用程序。目前,我使用Camera2 API和ImageReader获取YUV_420_888格式的图像数据,但我不知道如何将这些数据准确写入MediaCodec。
这是我的问题:
- 什么是
YUV_420_888?
YUV_420_888 的格式不明确,因为它可以是属于YUV420 系列的任何格式,例如YUV420P、YUV420PP、YUV420SP 和YUV420PSP,对吧?
通过访问图像的三个平面(#0、#1、#2),我可以获得该图像的 Y(#0)、U(#1)、V(#2) 值。但是这些值的排列在不同的设备上可能不一样。例如,如果YUV_420_888 真正表示YUV420P,则Plane#1 和Plane#2 的大小都是Plane#0 大小的四分之一。如果YUV_420_888 真的是YUV420SP,那么Plane#1 和Plane#2 的大小都是Plane#0 的一半(Plane#1 和Plane#2 都包含U、V 值)。
如果我想将图像的三个平面的这些数据写入MediaCodec,我需要转换成什么样的格式? YUV420、NV21、NV12、...?
- 什么是
COLOR_FormatYUV420Flexible?
COLOR_FormatYUV420Flexible 的格式也是模棱两可的,因为它可以是属于YUV420 系列的任何格式,对吧?如果我将 MediaCodec 对象的KEY_COLOR_FORMAT 选项设置为COLOR_FormatYUV420Flexible,我应该向 MediaCodec 对象输入什么格式的数据(YUV420P、YUV420SP...?)?
- 用
COLOR_FormatSurface怎么样?
我知道 MediaCodec 有自己的表面,如果我将 MediaCodec 对象的KEY_COLOR_FORMAT 选项设置为COLOR_FormatSurface,就可以使用它。并且使用 Camera2 API,我不需要自己将任何数据写入 MediaCodec 对象。我可以排空输出缓冲区。
但是,我需要更改相机中的图像。例如,绘制其他图片,在其上写一些文字,或者插入另一个视频作为 POP(Picture of Picture)。
我可以使用 ImageReader 从 Camera 读取图像,并在重新绘制之后,将新数据写入 MediaCodec 的表面,然后将其排出吗?该怎么做?
EDIT1
我使用COLOR_FormatSurface 和RenderScript 实现了该功能。这是我的代码:
onImageAvailable方法:
public void onImageAvailable(ImageReader imageReader) {
try {
try (Image image = imageReader.acquireLatestImage()) {
if (image == null) {
return;
}
Image.Plane[] planes = image.getPlanes();
if (planes.length >= 3) {
ByteBuffer bufferY = planes[0].getBuffer();
ByteBuffer bufferU = planes[1].getBuffer();
ByteBuffer bufferV = planes[2].getBuffer();
int lengthY = bufferY.remaining();
int lengthU = bufferU.remaining();
int lengthV = bufferV.remaining();
byte[] dataYUV = new byte[lengthY + lengthU + lengthV];
bufferY.get(dataYUV, 0, lengthY);
bufferU.get(dataYUV, lengthY, lengthU);
bufferV.get(dataYUV, lengthY + lengthU, lengthV);
imageYUV = dataYUV;
}
}
} catch (final Exception ex) {
}
}
将 YUV_420_888 转换为 RGB:
public static Bitmap YUV_420_888_toRGBIntrinsics(Context context, int width, int height, byte[] yuv) {
RenderScript rs = RenderScript.create(context);
ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
Type.Builder yuvType = new Type.Builder(rs, Element.U8(rs)).setX(yuv.length);
Allocation in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);
Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
Allocation out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
Bitmap bmpOut = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
in.copyFromUnchecked(yuv);
yuvToRgbIntrinsic.setInput(in);
yuvToRgbIntrinsic.forEach(out);
out.copyTo(bmpOut);
return bmpOut;
}
媒体编解码器:
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
...
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
...
surface = mediaCodec.createInputSurface(); // This surface is not used in Camera APIv2. Camera APIv2 uses ImageReader's surface.
在另一个线程中:
while (!stop) {
final byte[] image = imageYUV;
// Do some yuv computation
Bitmap bitmap = YUV_420_888_toRGBIntrinsics(getApplicationContext(), width, height, image);
Canvas canvas = surface.lockHardwareCanvas();
canvas.drawBitmap(bitmap, matrix, paint);
surface.unlockCanvasAndPost(canvas);
}
这种方式可行,但性能不佳。它不能输出 30fps 的视频文件(只有 ~12fps)。也许我不应该使用COLOR_FormatSurface 和表面的画布进行编码。计算出的 YUV 数据应直接写入 mediaCodec,无需任何表面进行任何转换。但我仍然不知道该怎么做。
【问题讨论】:
-
你有没有解决这个问题。如果是,你能发布答案吗?
-
@ShivamPokhriyal 据我所知,没有标准 SDK 的解决方案。
-
那么您知道任何解决方法吗?
标签: camera android-camera yuv android-camera2