如何正确使用ImageReader与YUV_420_888和MediaCodec将视频编码为h264格式?


1

我正在Android设备上实现摄像头应用程序。目前,我使用Camera2 API和ImageReader以YUV_420_888格式获取图像数据,但我不知道如何将这些数据完全写入MediaCodec。

这里是我的问题:

  1. 什么是YUV_420_888

格式YUV_420_888是不明确的,因为它可以是属于家族YUV420任何格式,如YUV420PYUV420PPYUV420SPYUV420PSP,是吗?

通过访问图像的三个平面(#0,#1,#2),我可以得到这个图像的Y(#0),U(#1),V(#2)值。但是这些值的排列在不同的设备上可能不一样。例如,如果YUV_420_888确实意味着YUV420P,那么平面#1和平面#2的尺寸是平面#0尺寸的四分之一。如果YUV_420_888确实意味着YUV420SP,则平面#1和平面#2的尺寸是平面#0的一半尺寸(平面#1和平面#2中的每一个都包含U,V值)。

如果我想将这些数据从图像的三个平面写入MediaCodec,我需要将哪种格式转换为? YUV420,NV21,NV12,...?

  1. 什么是​​?

格式​​也是明确的,因为它可以属于YUV420家庭的任何格式,对不对?如果将MediaCodec对象的KEY_COLOR_FORMAT选项设置为​​,则应将什么格式的数据(YUV420P,YUV420SP ...?)输入到MediaCodec对象?

  1. 如何使用COLOR_FormatSurface

我知道MediaCodec都有自己的表面,它可以,如果我设置MediaCodec对象的KEY_COLOR_FORMAT选项COLOR_FormatSurface使用。使用Camera2 API,我不需要自己将任何数据写入MediaCodec对象。我可以排空输出缓冲区。

但是,我需要更改相机的图像。例如,绘制其他图片,在上面写上一些文字,或插入另一个视频作为POP(图片图片)。

我可以使用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; 
} 

MediaCodec:

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. 

而且在athother螺纹:

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,而不需要任何表面进行任何转换。但我仍然不知道该怎么做。

0

你说得对,YUV_420_888是一种可以包装不同YUV 420格式的格式。该规范仔细地解释说,U和V飞机的排列没有规定,但有一定的限制;例如如果U平面的像素跨度为2,则V相同(然后底层字节缓冲区可以是NV21)。

COLOR_FormatYUV420FlexibleYUV_420_888的同义词,但它们分别属于不同的类:MediaCodec和ImageFormat。

的规格说明:

所有的视频编解码器,支持灵活的YUV 4:因为LOLLIPOP_MR1 0缓存:2。

COLOR_FormatSurface是一种不透明的格式,它可以为MediaCodec提供最佳性能,但是这样做的代价是:您无法直接读取或操作其内容。如果您需要处理传送到MediaCodec的数据,则使用ImageReader是一个选项;它是否比ByteBuffer更高效,取决于你做什么以及如何做。请注意,对于API 24+,您可以在C++中使用camera2和MediaCodec。

MediaCodec的无价资源细节是http://www.bigflake.com/mediacodec。它引用了264编码的full example

  0

谢谢。我试图通过COLOR_FormatSurface对视频进行编码,但性能不佳(请参阅我的EDIT1部分)。有没有关于使用COLOR_FormatYUV420Flexible(或其他YUV格式)编码视频的例子? 26 9月. 172017-09-26 07:20:10

  0

如果我理解正确,您打算在** onImageAvailable()**中添加更多代码,以便** imageYUV **将成为相机框的编辑副本。因为否则没有理由简单地将平面从** image **复制到另一个ByteBuffer。 26 9月. 172017-09-26 08:35:57

  0

无论如何,即使使用renderscript,您的显示过程也并不理想。使用OpenGL可以更快,使用着色器可以将YUV420输入转换为RGB。 26 9月. 172017-09-26 08:38:13

  0

我知道imageYUV很奇怪。但实际上我有多个相机来源。我使用变量来存储每个相机的帧在同一时刻。并使用不同的线程来计算此时来自每个摄像机的帧,并将结果写入视频文件。顺便说一下,如何使用OpenGL将YUV420_888转换为RGB? 26 9月. 172017-09-26 14:14:35

  0

诀窍不是将YUV“转换”为RGB,而是使用OpenGL着色器,它可以将输入视为YUV并将其转换为可以显示的纹理。这样,你不需要读取RGB数据,它直接进入屏幕缓冲区。请检查[这个例子](http://bigflake.com/mediacodec/#DecodeEditEncodeTest)了解一些提示。 26 9月. 172017-09-26 14:28:42