如何从MediaCodec解码器的输出中提取PCM样本

时间:2014-03-26 21:04:20

标签: android audio codec

我正在尝试从解码的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流,还是依赖于解码器的实现?

2 个答案:

答案 0 :(得分:4)

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

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

在我的情况下,启动时间总是2112个样本,其余部分根据音频大小自然变化。

答案 1 :(得分:1)

我知道问题在这里解决了。但MediaCodec在当前代码中使用Synchronously,目前已弃用。我从这个问题中学习并使用MediaCodec Async使用了同样的东西。只需发布github链接,以便稍后可以帮助某人。

Github异步实现:link

FYI :使用的audioplayer暂时只是来自其他线程的复制粘贴。它被贬低了。我有空的时候会更新它。代码也在Kotlin中。(它仍然很容易理解)

请查看官方MediaCodec文档的Async链接