如何使用 Android MediaCodec 生成 AAC ADTS 基本流

Posted

技术标签:

【中文标题】如何使用 Android MediaCodec 生成 AAC ADTS 基本流【英文标题】:How to generate the AAC ADTS elementary stream with Android MediaCodec 【发布时间】:2013-09-22 15:37:34 【问题描述】:

我正在尝试做的事情:使用 android 的 MediaCodec 将原始 PCM 音频样本编码为原始 AAC 文件。

我遇到的问题:当我使用 FFMPEG 将生成的原始 AAC 文件打包到 M4A 容器中时,FFMPEG 抱怨文件中缺少编解码器参数。

详情:

由于我找不到任何用于生成输出 AAC 文件的音频编码器的 MediaCodec 示例代码,因此我尝试将视频编码器修改为音频编码器。原代码在这里:source_code

我这样配置音频编码器:

    mEncoderFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", (int)mAudiosampleRate, 2);

    // redundant?
    mEncoderFormat.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
    mEncoderFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, 
                      MediaCodecInfo.CodecProfileLevel.AACObjectELD);
    mEncoderFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, kSampleRates);
    mEncoderFormat.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates);
    mEncoderFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
    testEncoderWithFormat("audio/mp4a-latm", mEncoderFormat);

    try 
        codec.configure(
                mEncoderFormat,
                null /* surface */,
                null /* crypto */,
                MediaCodec.CONFIGURE_FLAG_ENCODE);
     catch (IllegalStateException e) 
        Log.e(TAG, "codec '" + componentName + "' failed configuration.");
        return;
    
    Log.d(TAG, "  testEncoder configured with format = " + format);

然后我为编码器提供每帧 10 毫秒的 PCM 样本。编码器获取每一帧,生成一帧比特流,然后我将比特流写入 FileOutputStream。循环一直持续到输入文件结束。

代码运行到结束。我执行“adb pull”以将生成的 AAC 文件从设备获取到我的 PC,并使用 FFMPEG 读取它。下面是命令和错误 FFMPEG 吐出:

$ ffmpeg -f aac -i BlessedNoColor_nexus7_api18.aac
ffmpeg version N-45739-g04bf2e7 Copyright (c) 2000-2012 the FFmpeg developers
  built on Oct 20 2012 00:20:36 with gcc 4.7.2 (GCC)
  configuration: --enable-gpl --enable-version3 --disable-pthreads --enable-runt
ime-cpudetect --enable-avisynth --enable-bzlib --enable-frei0r --enable-libass -
-enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libfreetype --enab
le-libgsm --enable-libmp3lame --enable-libnut --enable-libopenjpeg --enable-libo
pus --enable-librtmp --enable-libschroedinger --enable-libspeex --enable-libtheo
ra --enable-libutvideo --enable-libvo-aacenc --enable-libvo-amrwbenc --enable-li
bvorbis --enable-libvpx --enable-libx264 --enable-libxavs --enable-libxvid --ena
ble-zlib
  libavutil      51. 76.100 / 51. 76.100
  libavcodec     54. 67.100 / 54. 67.100
  libavformat    54. 33.100 / 54. 33.100
  libavdevice    54.  3.100 / 54.  3.100
  libavfilter     3. 19.103 /  3. 19.103
  libswscale      2.  1.101 /  2.  1.101
  libswresample   0. 16.100 /  0. 16.100
  libpostproc    52.  1.100 / 52.  1.100
[aac @ 00000000002efae0] channel element 2.0 is not allocated
[aac @ 00000000003cf520] decoding for stream 0 failed
[aac @ 00000000003cf520] Could not find codec parameters for stream 0 (Audio: aac, 0 channels, s16): unspecified sample rate
Consider increasing the value for the 'analyzeduration' and 'probesize' options
[aac @ 00000000003cf520] Estimating duration from bitrate, this may be inaccurate

BlessedNoColor_nexus7_api18.aac: could not find codec parameters

我的问题:

    我在调用 codec.start() 之前配置了编码器。为什么生成的 AAC 文件缺少编解码参数? 在原始视频编解码器示例中,参数“csd-0”从编码器传递到解码器,但未明确写入比特流文件。我需要将它们显式写入 AAC 文件吗? 我将输入的 PCM 样本分成每帧 10 毫秒,这不一定会产生完整的输出数据包。对于每个输入帧,我只需将编码器输出的任何内容写入文件。这值得关注吗?

任何帮助将不胜感激。如果有一个示例项目可以完成我在这里尝试做的事情,那就太好了。如果我的源代码可以帮助你帮助我,我会发布它。我需要做一些清理工作。谢谢!

编辑:将标题从“MediaCodec 缺少编解码器参数生成的基本 AAC 文件”更改为“”

【问题讨论】:

FWIW,CTS音频编码器测试为android.googlesource.com/platform/cts/+/jb-mr2-release/tests/… @fadden:我也看过那个测试。它不会生成输出 AAC 文件。我确实尝试添加代码以将比特流写入文件。生成的文件也有同样的问题:ffmpeg 无法识别为 AAC 文件。我刚刚编辑了我的问题以添加我找不到生成比特流文件的示例代码(在我的情况下为 AAC)。 阅读this post和this post后,我检查了编码器输出 italic bold ` (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG ) != 0`。输出为 4 个字节:0xF8、0xE8、0x40、00。它们不是 ESDS(基本流描述符)。它们是什么? 如何输入数据进行编码? 【参考方案1】:

我最终生成了可在 Android 设备和 Windows 主机上播放的 AAC 文件。我在这里发布我的解决方案,希望它可以帮助其他人。

首先,我之前关于 Android MediaCodec 编码器生成基本 AAC 流的假设并不准确。 MediaCodec 编码器生成原始 AAC 流。这就是无法播放文件的原因。原始 AAC 流需要转换为可播放格式,例如 ADTS 流。我更改了这篇文章的标题以反映我的新理解。有another post 提出了类似的问题,并且给出了很好的答案。但是,新手可能不一定能理解那里的简要说明。我第一次阅读那篇文章时并没有完全理解。

所以,为了生成可以由媒体播放器播放的 AAC 比特流,我从 fadden 在他的第 1 条评论中给出的 EncoderTest 示例开始,但修改了原始代码以添加每个输出帧的 ADTS 标头(访问单元),并将生成的流写入文件(将原始代码的第 248 到 267 行替换为以下代码 sn-p):

if (index >= 0) 
    int outBitsSize   = info.size;
    int outPacketSize = outBitsSize + 7;    // 7 is ADTS size
    ByteBuffer outBuf = codecOutputBuffers[index];

    outBuf.position(info.offset);
    outBuf.limit(info.offset + outBitsSize);
    try 
        byte[] data = new byte[outPacketSize];  //space for ADTS header included
        addADTStoPacket(data, outPacketSize);
        outBuf.get(data, 7, outBitsSize);
        outBuf.position(info.offset);
        mFileStream.write(data, 0, outPacketSize);  //open FileOutputStream beforehand
     catch (IOException e) 
        Log.e(TAG, "failed writing bitstream data to file");
        e.printStackTrace();
    

    numBytesDequeued += info.size;

    outBuf.clear();
    codec.releaseOutputBuffer(index, false /* render */);
    Log.d(TAG, "  dequeued " + outBitsSize + " bytes of output data.");
    Log.d(TAG, "  wrote " + outPacketSize + " bytes into output file.");

else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) 

else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) 
    codecOutputBuffers = codec.getOutputBuffers();

在循环之外,我这样定义函数 addADTStoPacket:

/**
 *  Add ADTS header at the beginning of each and every AAC packet.
 *  This is needed as MediaCodec encoder generates a packet of raw
 *  AAC data.
 *
 *  Note the packetLen must count in the ADTS header itself.
 **/
private void addADTStoPacket(byte[] packet, int packetLen) 
    int profile = 2;  //AAC LC
                      //39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
    int freqIdx = 4;  //44.1KHz
    int chanCfg = 2;  //CPE

    // fill in ADTS data
    packet[0] = (byte)0xFF;
    packet[1] = (byte)0xF9;
    packet[2] = (byte)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
    packet[3] = (byte)(((chanCfg&3)<<6) + (packetLen>>11));
    packet[4] = (byte)((packetLen&0x7FF) >> 3);
    packet[5] = (byte)(((packetLen&7)<<5) + 0x1F);
    packet[6] = (byte)0xFC;

我还添加了代码来控制如​​何停止生成 AAC ADTS 流,但这是特定于应用程序的,所以我不会在这里详细说明。通过所有这些更改,生成的 AAC 文件可以在 Android 设备、我的 Windows PC 上播放,ffmpeg 对它们很满意。

【讨论】:

伙计,这 7 行代码节省了我数天的时间来试图弄清楚。太棒了! 这太好了,@hubeir 您是否设法使用 ffmpeg 将 aac 转换为 m4a? 我发现我想补充一件事。我们必须避免将第一个数据包 deque'd 写入文件。此数据包包含会扰乱 Android 音乐播放器播放的编解码器信息。出于同样的原因,也不要写流数据包的结尾。文件中应包含的只是带有 ADTS 标头的数据包。 不要盲目地跳过第一个数据包,而是检查info.flags &amp; MediaCodec.BUFFER_FLAG_CODEC_CONFIG。 (并且为了灵活支持其他配置,可以解析编解码器配置缓冲区中的数据,并使用 ADTS 标头中设置的频率索引和频道配置。) @mstorsjo 你如何解析 BufferInfo 中的 frequencyIndex 和 channelConfig?

以上是关于如何使用 Android MediaCodec 生成 AAC ADTS 基本流的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Android MediaCodec 编码相机数据(YUV420sp)

使用 MediaCodec 和 Surface 进行 Android 编码

Android媒体解码MediaCodec MediaExtractor学习

在 Android 上使用 AudioRecord 和 MediaCodec 编码 AAC 音频

Android MediaCodec

Android MediaCodec硬件解码视频播放