Mediacodec和相机,颜色空间不正确。

6

参考Aegonis的工作1工作2,我也得到了H.264流,但颜色不正确。我正在使用HTC Butterfly进行开发。这是我的部分代码:

相机:

parameters.setPreviewSize(width, height);
parameters.setPreviewFormat(ImageFormat.YV12);
parameters.setPreviewFrameRate(frameRate);

媒体编解码器:

mediaCodec = MediaCodec.createEncoderByType("video/avc");
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 320, 240);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 500000);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);   
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 
mediaCodec.start();   

当使用COLOR_FormatYUV420Planar时,会出现错误提示“[OMX.qcom.video.encoder.avc]不支持颜色格式19”,因此我只能使用COLOR_FormatYUV420SemiPlanar。有人知道为什么不支持吗?
明白了,通过使用:
int colorFormat = 0;
    MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
    for (int i = 0; i < capabilities.colorFormats.length && colorFormat == 0; i++) {
        int format = capabilities.colorFormats[i];
        Log.e(TAG, "Using color format " + format);           
    }

我们可以有颜色格式 21 (COLOR_FormatYUV420SemiPlanar) 和 2130708361 (没有对应的格式),我认为格式会因设备而异。
然后,我尝试了从 work 1work 2 中提供的建议中进行的颜色转换:
public static byte[] YV12toYUV420PackedSemiPlanar(final byte[] input, final byte[] output, final int width, final int height) {
    /* 
     * COLOR_TI_FormatYUV420PackedSemiPlanar is NV12
     * We convert by putting the corresponding U and V bytes together (interleaved).
     */
    final int frameSize = width * height;
    final int qFrameSize = frameSize/4;

    System.arraycopy(input, 0, output, 0, frameSize); // Y

    for (int i = 0; i < qFrameSize; i++) {
        output[frameSize + i*2] = input[frameSize + i + qFrameSize]; // Cb (U)
        output[frameSize + i*2 + 1] = input[frameSize + i]; // Cr (V)
    }
    return output;
}

public static byte[] YV12toYUV420Planar(byte[] input, byte[] output, int width, int height) {
    /* 
     * COLOR_FormatYUV420Planar is I420 which is like YV12, but with U and V reversed.
     * So we just have to reverse U and V.
     */
    final int frameSize = width * height;
    final int qFrameSize = frameSize/4;

    System.arraycopy(input, 0, output, 0, frameSize); // Y
    System.arraycopy(input, frameSize, output, frameSize + qFrameSize, qFrameSize); // Cr (V)
    System.arraycopy(input, frameSize + qFrameSize, output, frameSize, qFrameSize); // Cb (U)

    return output;
}

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

显然,YV12toYUV420PackedSemiPlanar 的颜色转换比其他两种方法表现更好。它相对来说更好,但与真实颜色相比仍然有所不同。我的代码是否有问题?欢迎留言评论。

1
感谢您以这样的方式提出问题。以下是有关编程的相关内容的翻译: - Ameer Moaaviah
“different”指的是像色度通道反了一样,还是“different”指的是微妙的差别?(如果您重新排列YV12toYUV420PackedSemiPlanar以交换Cb/Cr通道,它看起来是否正确?) - fadden
嗨,Fadden,谢谢你的建议。我测试了一下,但颜色似乎还是不正确。我观察到像素值在Y、Cb和Cr方面变化从-128到127。我认为像素值应该在0到255之间。 - Albert
颜色格式2130708361对应于COLOR_FormatSurface,也许你的问题是在API 18发布之前就被问过了。 - HPP
1
2130708361 -> COLOR_FormatSurface -> http://developer.android.com/reference/android/media/MediaCodecInfo.CodecCapabilities.html#COLOR_FormatSurface - thiagolr
显示剩余3条评论
2个回答

3

明白了,现在颜色看起来很好,测试是基于 HTC Butterfly 进行的。 当将分辨率设置为 320x240 时,您的颜色转换应如下所示:

    System.arraycopy(input, 0, output, 0, frameSize);
    for (int i = 0; i < (qFrameSize); i++) {  
        output[frameSize + i*2] = (input[frameSize + qFrameSize + i - 32 - 320]);  
        output[frameSize + i*2 + 1] = (input[frameSize + i - 32 - 320]);            
    }

对于分辨率640x480及以上的情况,

System.arraycopy(input, 0, output, 0, frameSize);    
    for (int i = 0; i < (qFrameSize); i++) {  
        output[frameSize + i*2] = (input[frameSize + qFrameSize + i]);  
        output[frameSize + i*2 + 1] = (input[frameSize + i]);   
    } 

对于帧率问题,我们可以使用getSupportedPreviewFpsRange()来检查我们设备支持的帧率范围,例如:

List<int[]> fpsRange = parameters.getSupportedPreviewFpsRange();
for (int[] temp3 : fpsRange) {
System.out.println(Arrays.toString(temp3));}

当播放已编码的H.264 ES时,以下设置可以正常运行:

parameters.setPreviewFpsRange(29000, 30000);    
//parameters.setPreviewFpsRange(4000,60000);//this one results fast playback when I use the FRONT CAMERA 

感谢你的回答,阿尔伯特。非常好的研究。我想知道为什么在编写320x240分辨率的U和V组件时会有-352字节的填充?而对于960x720分辨率,应该使用哪种填充呢? - Andrey Chernih
相关问题:https://dev59.com/hnTYa4cB1Zd3GeqPrCKQ#19883163 看起来,当将帧发送到编码器时,某些分辨率的Y分量应该对齐一定数量的字节。 - Andrey Chernih

2
阅读这个讨论后,发现对于编码各种分辨率的帧的更通用的方法是在将帧发送到MediaCodec之前将色度平面与2048字节对齐。这适用于我认为HTC Butterfly拥有的QualComm(OMX.qcom.video.encoder.avc)编码器,但仍无法很好地处理所有分辨率。720x480176x144根据输出视频仍然具有色度平面未对齐的问题。此外,避免大小不能被16整除的分辨率。

变换非常简单:

int padding = 0;
if (mediaCodecInfo.getName().contains("OMX.qcom")) {
  padding = (width * height) % 2048;
}
byte[] inputFrameBuffer = new byte[frame.length];
byte[] inputFrameBufferWithPadding = new byte[padding + frame.length];

ColorHelper.NV21toNV12(frame, inputFrameBuffer, width, height);
# copy Y plane
System.arraycopy(inputFrameBuffer, 0, inputFrameBufferWithPadding, 0, inputFrameBuffer.length);
int offset = width * height;
# copy U and V planes aligned by <padding> boundary
System.arraycopy(inputFrameBuffer, offset, inputFrameBufferWithPadding, offset + padding, inputFrameBuffer.length - offset);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接