MediaCodec和Camera:颜色空间不匹配

我一直在尝试使用H264编码来处理Android平板电脑上使用新的低级别MediaCodec的摄像头捕获的input。 我经历了一些困难,因为MediaCodecAPI的文档logging很差,但是我终于有了一些工作。

我正在设置相机,如下所示:

Camera.Parameters parameters = mCamera.getParameters(); parameters.setPreviewFormat(ImageFormat.YV12); // <1> parameters.setPreviewFpsRange(4000,60000); parameters.setPreviewSize(640, 480); mCamera.setParameters(parameters); 

对于编码部分,我正在实例化MediaCodec对象,如下所示:

  mediaCodec = MediaCodec.createEncoderByType("video/avc"); MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 640, 480); mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 500000); mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar); // <2> mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mediaCodec.start(); 

最后的目标是创build一个RTPstream(和Skype相对应),但到目前为止,我只是将原始的H264直接传输到我的桌面。 在那里我使用以下GStreamerpipe道来显示结果:

 gst-launch udpsrc port=5555 ! video/x-h264,width=640,height=480,framerate=15/1 ! ffdec_h264 ! autovideosink 

一切运作良好,除了颜色。 我需要在计算机中设置2种颜色格式:一种用于摄像机预览(标有<1> ),另一种用于MediaCodec对象(标有<2>

要确定行<1>的可接受值,我使用了parameters.getSupportedPreviewFormats() 。 从这里,我知道在相机上唯一支持的格式是ImageFormat.NV21和ImageFormat.YV2 。

对于<2> ,我检索了types为video / avc的 MediaCodecInfo.CodecCapabilities- object,它是整数值19(与MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar和2130708361(它不与MediaCodecInfo.CodecCapabilities的任何值对应)相对应)。

除上述以外的任何其他值都会导致崩溃。

结合这些设置给出了不同的结果,我将在下面展示。 这里是Android上的截图(即“真实”的颜色): 在Android平板电脑上输入 这是Gstreamer所显示的结果:

<1> = NV21,2 = COLOR_FormatYUV420Planar GSTreamer-NV21-COLOR_FormatYUV420Planar输出

<1> = NV21,2 = 2130708361 Gstreamer输出为NV21-2130708361

<1> = YV2,2 = COLOR_FormatYUV420Planar 用于YV2-COLOR_FormatYUV420Planar的Gstreamer输出

<1> = YV2,2 = 2130708361 Gstreamer-YV2-2130708361的输出

可以看出,这些都不令人满意。 YV2色彩空间看起来最有希望,但它看起来像是红色(Cr)和蓝色(Cb)倒置。 NV21看起来交错我猜(但是,我不是这方面的专家)。

由于目的是与Skype通信,我认为我不应该改变解码器(即Gstreamer命令),对不对? 这是在Android中解决,如果是这样的话:怎么样? 或者可以通过添加某些RTP有效负载信息来解决? 任何其他build议?

我通过使用一个简单的函数在Android级别上交换字节平面来解决它:

 public byte[] swapYV12toI420(byte[] yv12bytes, int width, int height) { byte[] i420bytes = new byte[yv12bytes.length]; for (int i = 0; i < width*height; i++) i420bytes[i] = yv12bytes[i]; for (int i = width*height; i < width*height + (width/2*height/2); i++) i420bytes[i] = yv12bytes[i + (width/2*height/2)]; for (int i = width*height + (width/2*height/2); i < width*height + 2*(width/2*height/2); i++) i420bytes[i] = yv12bytes[i - (width/2*height/2)]; return i420bytes; } 

我认为交换价值更有效率。

  int wh4 = input.length/6; //wh4 = width*height/4 byte tmp; for (int i=wh4*4; i<wh4*5; i++) { tmp = input[i]; input[i] = input[i+wh4]; input[i+wh4] = tmp; } 

也许更好,你可以改为replace

  inputBuffer.put(input); 

用正确顺序的3个平面切片

  inputBuffer.put(input, 0, wh4*4); inputBuffer.put(input, wh4*5, wh4); inputBuffer.put(input, wh4*4, wh4); 

我认为这应该只是一个小小的开销

看来Android是在YV12传输,但在H264头设置的格式是YUV420。 除U和V通道的顺序不同外,这些格式是相同的,这就解释了红色和蓝色的交换。

最好的当然是修复Android的设置。 但是,如果没有办法设置相机和编码器的兼容设置,则必须强制GStreamer侧的格式。

这可以通过在capssetter之后添加capssetter元素来完成

... ! ffdec_h264 ! capssetter caps="video/x-raw-yuv, format=(fourcc)YV12" ! colorspace ! ...

在相机上设置了ImageFormat.NV21,在编码器上使用了COLOR_FormatYUV420Planar,在我的例子中,看起来类似的蓝色阴影重叠。 据我所知,上面的交换function不能用在我的情况下,任何可以用于这个algorithm的build议? ps:当相机预览格式设置为YV12时,在解码器中是一个完整的黑屏