当前位置: 首页 > 知识库问答 >
问题:

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

井疏珂
2023-03-14

我正在尝试从解码的mp4缓冲区中获取PCM样本以进行进一步处理。我首先从使用手机的相机应用程序录制的视频文件中提取音轨,并且我已经确保在获得“音频/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样本,按照我假设的方式以及way@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个答案

周培
2023-03-14

我知道问题在这里解决了。但MediaCodec在当前代码中是同步使用的,到目前为止,该代码已被弃用。我从这个问题中学到了一些东西,并在异步使用MediaCodec时做了同样的事情。只需发布github链接,以便日后对他人有所帮助。

Github异步实现:链接

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

请查看官方MediaCodec文档的异步链接

夏振国
2023-03-14

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

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

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

 类似资料:
  • Android MediaCodec可以用来解码输出的yuv420格式。我猜应该是NV12格式,但是当我在Nexus6 7.1.1设备上试用的时候。结果很奇怪: > 对于720p视频,它工作良好,输出的yuv可以通过ffplay播放,使用以下命令: ffplay-V信息-F rawvideo-PIXEL_FORMAT yuv420p-VIDEO_SIZE 1280x720 OUT.YUV 当试图解

  • 我有一个Android MediaCodec解码器,配置了一个来自SurfaceTexture对象的Surface。MP4文件解码工作良好,帧可以在设备上看到。但是如果尝试用MediaMuxer重新编码为新的MP4文件,则输出文件大小为零,因为SurfaceTexture.getTimestamp()返回的总是0。在这种情况下,获得帧呈现时间的适当方法是什么?

  • 数据:“{\”数据\“:[\”124“,\”611“]}”,Lasteventid:“”} 是否可以只抓取,因为否则客户端在反序列化方面会出现问题。

  • 大小范围在2.5MB-20MB之间。这个问题在较长的剪辑上变得更糟,例如7分钟的范围是9MB-120MB。 正常吗? 我试图捕捉同样的场景,但还是得到了不同的结果。

  • 问题内容: 要获取有关媒体文件的大量信息,可以做 它会输出很多行,特别是一行 我只想输出,所以我尝试 但是它可以打印所有内容,而不仅仅是长度。 甚至输出所有内容。 如何获得持续时间长度? 问题答案: ffmpeg正在将该信息写入,而不是。尝试这个: 注意重定向到: 编辑: 您的陈述也不起作用。尝试这个:

  • 问题内容: 我写了一个小小的go脚本,并使用strace跟踪了该脚本,我试图使用netlink协议从内核中获取审核消息,就像auditd一样。 以下是我的旅途中strace的输出脚本- http://paste.ubuntu.com/8272760/ 我试图找到auditd提供给sendto函数的参数。当我在auditd上运行strace时,我得到以下输出 当我追踪我的go文件时,我得到以下输出。