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上的截图(即“真实”的颜色): 这是Gstreamer所显示的结果:
<1>
= NV21,2 = COLOR_FormatYUV420Planar
<1>
= NV21,2 = 2130708361
<1>
= YV2,2 = COLOR_FormatYUV420Planar
<1>
= YV2,2 = 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时,在解码器中是一个完整的黑屏