31

我试图让它在 Android 4.1 上运行(使用升级的 Asus Transformer 平板电脑)。感谢Alex 对我之前的问题的回答,我已经能够将一些原始 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();
    }

}

将编码器类型更改为“video/mp4”显然可以解决帧率问题,但由于主要目标是制作流媒体服务,因此这不是一个好的解决方案。

我知道考虑到 SPS 和 PPS NALU,我删除了一些 Alex 的代码,但我希望这不是必需的,因为该信息也来自outData并且我认为编码器会正确格式化它。如果不是这种情况,我应该如何在我的文件/流中安排不同类型的 NALU?

那么,为了制作有效的、工作的 H.264 流,我在这里缺少什么?我应该使用哪些设置来匹配相机的色彩空间和编码器的色彩空间?

我觉得这更像是一个与 H.264 相关的问题,而不是 Android/MediaCodec 主题。还是我仍然没有正确使用 MediaCodec API?

提前致谢。

4

5 回答 5

9

对于您的快速播放 - 帧速率问题,您无需在此处执行任何操作。由于它是流式解决方案,因此必须提前告知对方帧速率或每帧的时间戳。这两者都不是基本流的一部分。要么选择预先确定的帧速率,要么传递一些 sdp 或类似的东西,或者使用现有的协议,如 rtsp。在第二种情况下,时间戳是以 rtp 之类的形式发送的流的一部分。然后客户端必须depay rtp流并播放它。这就是基本流的工作方式。[如果您有固定速率编码器或给出时间戳,请修复您的帧速率]

本地 PC 播放会很快,因为它不知道 fps。通过在输入之前给出 fps 参数,例如

ffplay -fps 30 in.264

您可以在 PC 上控制播放。

至于无法播放的文件:是否有SPS和PPS。此外,您应该启用 NAL 标头 - 附件 b 格式。我对 android 了解不多,但这是任何 h.264 基本流在它们不在任何容器中并且需要在以后转储和播放时都可以播放的要求。如果android默认是mp4,但是默认的附件b头会被关闭,所以也许有一个开关可以启用它。或者,如果您逐帧获取数据,只需自己添加即可。

至于颜色格式:我猜默认应该可以。所以尽量不要设置。如果没有尝试 422 Planar 或 UVYV / VYUY 交错格式。通常相机就是其中之一。(但不是必须的,这些可能是我遇到的比较多的)。

于 2012-11-24T20:01:58.513 回答
7

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

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

有关执行此操作的示例,请参阅CameraToMpegTest 源。(它还演示了使用 OpenGL ES 片段着色器对正在录制的视频进行简单的编辑。)

于 2013-07-24T19:45:54.613 回答
6

如果您已将预览颜色空间设置为 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;
    }
于 2013-01-16T16:42:16.980 回答
2

您可以查询 MediaCodec 以了解其支持的位图格式并查询您的预览。问题是,一些 MediaCodecs 仅支持您无法从预览中获得的专有打包 YUV 格式。特别是 2130706688 = 0x7F000100 = COLOR_TI_FormatYUV420PackedSemiPlanar 。预览的默认格式为 17 = NV21 = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV411Planar = YCbCr 420 Semi Planar

于 2012-12-05T09:53:15.847 回答
2

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

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

Google 文档还声称,所有 API >= 12 的设备都必须支持YV12平面 YUV 作为相机预览格式。因此,尝试它可能很有用(MediaCodec 等效项是您在代码片段中使用的COLOR_FormatYUV420Planar )。

更新:正如 Andrew Cottrell 提醒我的,YV12 仍然需要色度交换才能成为 COLOR_FormatYUV420Planar。

于 2013-01-16T20:20:03.960 回答