从编码H.264摄像头的Android MediaCodec(Encoding H.264 from

2019-06-21 05:22发布

我试图得到这个在Android 4.1的工作(使用升级后的华硕变压器平板电脑)。 由于亚历克斯对我以前的问题的回应 ,我已经能写一些原始H.264数据到一个文件,但该文件只与可玩ffplay -f h264 ,和它看起来像它失去关于帧率的所有信息(极快速播放)。 另外,色彩空间看起来不正确(使用编码器的侧相机的默认ATM)。

public class AvcEncoder {

private MediaCodec mediaCodec;
private BufferedOutputStream outputStream;

public AvcEncoder() { 
    File f = new File(Environment.getExternalStorageDirectory(), "Download/video_encoded.264");
    touch (f);
    try {
        outputStream = new BufferedOutputStream(new FileOutputStream(f));
        Log.i("AvcEncoder", "outputStream initialized");
    } catch (Exception e){ 
        e.printStackTrace();
    }

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

public void close() {
    try {
        mediaCodec.stop();
        mediaCodec.release();
        outputStream.flush();
        outputStream.close();
    } catch (Exception e){ 
        e.printStackTrace();
    }
}

// called from Camera.setPreviewCallbackWithBuffer(...) in other class
public void offerEncoder(byte[] input) {
    try {
        ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
        ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
        int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
        if (inputBufferIndex >= 0) {
            ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
            inputBuffer.clear();
            inputBuffer.put(input);
            mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0);
        }

        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0);
        while (outputBufferIndex >= 0) {
            ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
            byte[] outData = new byte[bufferInfo.size];
            outputBuffer.get(outData);
            outputStream.write(outData, 0, outData.length);
            Log.i("AvcEncoder", outData.length + " bytes written");

            mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
            outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);

        }
    } catch (Throwable t) {
        t.printStackTrace();
    }

}

更改编码器类型为“视频/ MP4”显然解决了帧率-问题,但由于主要目标是使流媒体服务,这不是一个很好的解决方案。

我知道,我放弃了一些Alex的代码考虑SPS和PPS NALU的,但我希望这不会是必要的,因为这些信息也从未来outData ,我认为编码器将正确格式化这个。 如果不是这种情况,应该怎么安排不同类型的NALU在我的文件/流?

那么,什么是我在这里失踪,为了使有效的工作H.264码流? 并设置我应该使用,使相机的色彩空间和编码器的色彩空间之间的匹配?

我有一种感觉,这是一个多的Android / MediaCodec话题H.264相关的问题。 或者,我仍无法正常使用MediaCodec API?

提前致谢。

Answer 1:

为了您的快速播放 - 帧速率的问题,有什么你必须在这里做。 由于它是一个流解决方案的另一侧具有被告知提前或时间戳与每个帧中的帧速率。 这两个都没有基本流的一部分。 无论是预先确定的帧速率选择,或者你传递一些SDP或类似的东西,或者你使用像RTSP现有协议。 在第二种情况下的时间戳是在像RTP形式发送的流的一部分。 然后客户端必须depay RTP流和BACL播放。 这是怎么流基本工作原理。 [要么解决您的帧速率,如果你有一个固定速率编码器或给时间戳]

本地PC播放将会很快,因为它不会知道的FPS。 通过输入如之前给FPS参数

ffplay -fps 30 in.264

你可以控制电脑上播放。

至于没有可播放的文件:它是否有一个SPS和PPS。 你也应该启用NAL头 - 附件B格式。 我不知道很多关于Android,但这是任何h.264基本流可玩时,他们没有任何的容器和需要转储和以后播放的要求。 如果Android的默认为MP4,但是默认annexb头将被关闭,所以也许有一个开关来启用它。 或者,如果你是帧获取数据帧,只是自行添加。

至于颜色格式:我猜默认应工作。 所以尽量不要设置它。 如果没有尝试422平面或UVYV / VYUY交错格式。 一般相机是其中的一个。 (但不是必要的,这些可能是我所遇到更多的时候的那些)。



Answer 2:

的Android 4.3(API 18)提供了一个简单的解决方案。 该MediaCodec类现在接受输入从表面上,这意味着你可以在相机的表面预览连接到编码器,并绕过所有古怪的YUV格式的问题。

还有一个新的MediaMuxer类 ,将您的原始H.264码流转换为.MP4文件(可选音频流中的混合)。

见CameraToMpegTest源正是这种做的一个例子。 (这也说明了如何使用OpenGL ES的片段着色器对视频进行编辑小事,因为它是被记录的。)



Answer 3:

您可以转换色彩空间就是这样,如果你已经设置预览色彩空间YV12:

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


Answer 4:

对于它支持位图格式和查询您预览您可以查询MediaCodec。 问题是,一些MediaCodecs只支持专有的打包YUV格式,你不能从预览获得。 特别2130706688 = 0x7F000100 = COLOR_TI_FormatYUV420PackedSemiPlanar。 用于预览默认格式是17 = NV21 = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV411Planar = 420的YCbCr半平面



Answer 5:

如果您没有明确要求的其他像素格式,相机预览缓冲区将在被称为YUV格式420到达NV21 ,为此COLOR_FormatYCrYCb是MediaCodec等同。

不幸的是,在这个页面上提到其他的答案,有你的设备上,AVC编码器支持这种格式无法保证。 请注意,存在不支持NV21一些奇怪的设备,但我不知道任何可以升级到16 API(因此,有MediaCodec)。

谷歌文档还声称, YV12平面YUV必须支持的相机预览格式与API> = 12的所有设备。因此,它可能是有益的尝试它(相当于MediaCodec是COLOR_FormatYUV420Planar您在您的代码段使用)。

更新 :安德鲁·科特雷尔提醒我,YV12仍然需要色度交换成为COLOR_FormatYUV420Planar。



文章来源: Encoding H.264 from camera with Android MediaCodec