【问题标题】:How to extract PCM samples from MediaCodec decoder's output如何从 MediaCodec 解码器的输出中提取 PCM 样本
【发布时间】:2014-03-26 21:04:20
【问题描述】:

我正在尝试从解码的 mp4 缓冲区获取 PCM 样本以进行进一步处理。我首先从使用手机的相机应用程序录制的视频文件中提取音轨,并确保在获得“audio/mp4”mime 键时选择了音轨:

MediaExtractor extractor = new MediaExtractor();
try {
    extractor.setDataSource(fileUri.getPath());
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
int numTracks = extractor.getTrackCount();
for(int i =0; i<numTracks; ++i) {
    MediaFormat format = extractor.getTrackFormat(i);
    String mime = format.getString(MediaFormat.KEY_MIME);
    //Log.d("mime =",mime);
    if(mime.startsWith("audio/")) {
        extractor.selectTrack(i);
        decoder = MediaCodec.createDecoderByType(mime);
        decoder.configure(format, null, null, 0);

        //getSampleCryptoInfo(MediaCodec.CryptoInfo info)
        break;
    }
}
if (decoder == null) {
    Log.e("DecodeActivity", "Can't find audio info!");
    return;
}
decoder.start();

之后,我遍历轨道,向编解码器提供编码访问单元流,并将解码后的访问单元拉入 ByteBuffer(这是我从此处发布的视频渲染示例中回收的代码 https://github.com/vecio/MediaCodecDemo):

ByteBuffer[] inputBuffers = decoder.getInputBuffers();
ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
BufferInfo info = new BufferInfo();

boolean isEOS = false;

while (true) {
    if (!isEOS) {
        int inIndex = decoder.dequeueInputBuffer(10000);
        if (inIndex >= 0) {
            ByteBuffer buffer = inputBuffers[inIndex];
            int sampleSize = extractor.readSampleData(buffer, 0);
            if (sampleSize < 0) {
                // We shouldn't stop the playback at this point, just pass the EOS
                // flag to decoder, we will get it again from the
                // dequeueOutputBuffer
                Log.d("DecodeActivity", "InputBuffer BUFFER_FLAG_END_OF_STREAM");
                decoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                isEOS = true;
            } else {
                decoder.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);
                extractor.advance();
            }
        }
    }

    int outIndex = decoder.dequeueOutputBuffer(info, 10000);
    switch (outIndex) {
    case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
        Log.d("DecodeActivity", "INFO_OUTPUT_BUFFERS_CHANGED");
        outputBuffers = decoder.getOutputBuffers();
        break;
    case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
        Log.d("DecodeActivity", "New format " + decoder.getOutputFormat());
        break;
    case MediaCodec.INFO_TRY_AGAIN_LATER:
        Log.d("DecodeActivity", "dequeueOutputBuffer timed out!");
        break;
    default:
        ByteBuffer buffer = outputBuffers[outIndex];
        // How to obtain PCM samples from this buffer variable??

        decoder.releaseOutputBuffer(outIndex, true);
        break;
    }

    // All decoded frames have been rendered, we can stop playing now
    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
        Log.d("DecodeActivity", "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
        break;
    }
}

到目前为止,该代码似乎没有任何错误,但我目前正试图弄清楚如何从获取输出缓冲区值的 ByteBuffer 中获取 PCM 样本。我想我可以假设因为我正在使用 16 位立体声音频文件,所以在交错方案中应该至少有两个字节......但是我不太确定这一点,所以要明确检索 PCM 样本从这个字节流。有人知道如何从 MediaCodec API 获取这些吗?

我已经阅读了一些使用 ffmpeg 或 openSL 的替代方案,但由于我是 Android 编程新手,我希望避免使用基于 c 的 API 的复杂性,并仅使用 Android 提供的工具构建我的第一个应用程序框架(我正在使用 KitKat)。任何帮助将不胜感激。

更新:我能够提取 PCM 样本,这是我假设的方式以及@marcone 指出的方式。为此,我在缓冲区分配下方添加了这些行:

byte[] b = new byte[info.size-info.offset];                         
int a = buffer.position();
buffer.get(b);
buffer.position(a);

最后将字节数组写入文件:

f.write(b,0,info.size-info.offset);

我现在处理的问题是:

解码后的音频样本与 iZotope 对 mp4 音轨的解码不完全匹配。波形文件大小有 48 个样本不匹配,解码信号有 2112 个样本延迟。我现在的问题是:所有 mp4 解码器会产生相同的输出 PCM 流,还是取决于解码器的实现?

【问题讨论】:

  • 缓冲区中的数据由交错的样本组成(除非是单声道的,在这种情况下显然不会有交错)。对于 mp3,输出样本几乎可以保证为 16 位(兼容设备必须通过的 CTS 测试假定它们是),因此您可以使用例如 ByteBuffer.getShort() 来读取它们。
  • 感谢 marcone,会尝试一下并告诉您结果。虽然我假设输出是未压缩的 PCM,而不是 mp3...,但我错过了什么吗?
  • 它确实有效,但是现在我面临一些额外样本和延迟的问题,正如我在上面的编辑中所包含的那样。
  • @jimijazz 你是最棒的.. 感谢分享你的发现..

标签: android audio codec


【解决方案1】:

我发现延迟是由 AAC 编码启动和剩余时间引起的,如下所述:

https://developer.apple.com/library/mac/documentation/quicktime/qtff/QTFFAppenG/QTFFAppenG.html

在我的例子中,启动时间总是 2112 个样本,其余的自然会根据音频大小而变化。

【讨论】:

    【解决方案2】:

    我知道问题在这里解决了。但是 MediaCodec 在当前代码中同步使用,目前已弃用。我从这个问题中学习,并通过 Async 使用 MediaCodec 做了同样的事情。只需发布 github 链接,以便以后可能对某人有所帮助。

    Github 异步实现:link

    仅供参考:使用的音频播放器暂时只是从其他线程复制粘贴。它被贬低了。有时间我会更新的。代码也在Kotlin中。(仍然很容易理解)

    请查看Async 链接以获取官方 MediaCodec 文档

    【讨论】:

    • 这部分 val b = ByteArray(info.size - info.offset) val a = buffer.position() buffer.get(b) buffer.position(a) 救了我的命!!谢谢 但是我仍然不明白为什么需要它,因为同步代码不需要它
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-04-19
    • 2014-06-25
    • 2018-11-18
    • 1970-01-01
    • 2021-06-24
    • 1970-01-01
    相关资源
    最近更新 更多