MediaCodec和Camera:颜色空间不匹配

时间:2012-12-04 13:02:22

标签: android colors h.264 gstreamer rtp

我一直试图通过使用新的低级别MediaCodec在Android平板电脑上使用相机捕获的输入来使用H264编码。我对此遇到了一些困难,因为MediaCodecAPI的文档很少,但我终于有了一些工作。

我按照以下方式设置相机:

        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();

最终目标是创建一个RTP流(并与Skype对应),但到目前为止,我只是将原始H264直接流式传输到我的桌面。在那里,我使用以下GStreamer-pipeline来显示结果:

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.NV21ImageFormat.YV2

对于<2>,我检索了类型为 video / avc MediaCodecInfo.CodecCapabilities - 对象,为整数值19(对应MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar和2130708361(其中)与MediaCodecInfo.CodecCapabilities)的任何值都不对应。

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

组合这些设置会产生不同的结果,我将在下面展示。这是Android上的截图(即“真实”颜色): Input on Android-tablet 以下是Gstreamer所示的结果:

<1> = NV21,<2> = COLOR_FormatYUV420Planar Gstreamer-output for NV21-COLOR_FormatYUV420Planar

<1> = NV21,<2> = 2130708361 Gstreamer-output for NV21-2130708361

<1> = YV2,<2> = COLOR_FormatYUV420Planar Gstreamer-output for YV2-COLOR_FormatYUV420Planar

<1> = YV2,<2> = 2130708361 Gstreamer-output for YV2-2130708361

可以看出,这些都不令人满意。 YV2色空间看起来最有希望,但看起来像红色(Cr)和蓝色(Cb)是倒置的。我认为NV21看起来是隔行扫描的(但是,我不是这个领域的专家)。

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

5 个答案:

答案 0 :(得分:7)

我通过使用一个简单的函数在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;
}

答案 1 :(得分:6)

我认为交换价值更有效。

        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;
            }

也许更好,你可以改为

            inputBuffer.put(input);

3个平面切片的顺序正确

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

我认为这应该只有很小的开销

答案 2 :(得分:3)

似乎Android正在YV12中传输,但H264标头中设置的格式为YUV420。这些格式是相同的,除了U和V通道的顺序不同,这解释了红色和蓝色的交换。

最好的当然是修复Android方面的设置。但如果无法为摄像机和编码器设置兼容设置,则必须在GStreamer端强制格式化。

这可以通过在capssetter

之后添加ffdec_h264元素来完成

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

答案 3 :(得分:0)

在相机上设置ImageFormat.NV21,为编码器设置COLOR_FormatYUV420Planar,在我的情况下看起来类似的蓝色阴影重叠。据我所知,上面的交换功能不能用于我的情况,对算法的任何建议都可以用于此吗? ps:当摄像机预览格式设置为YV12

时,它在解码器处是一个完整的黑屏

答案 4 :(得分:0)

我使用 here 中的代码使用媒体编码器将相机图像转换为视频,这导致了同样的问题。

因此参考此 wikipedia article 我修改了代码 like this 并且输出似乎工作正常。代替 uvuv 字节顺序,将字节顺序改为 uuuuuvvvv ..