如何使用 Android MediaCodec 生成 AAC ADTS 基本流 [英] How to generate the AAC ADTS elementary stream with Android MediaCodec

查看:33
本文介绍了如何使用 Android MediaCodec 生成 AAC ADTS 基本流的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想要做什么:使用 Android 的 MediaCodec 将原始 PCM 音频样本编码为原始 AAC 文件.

What I am trying to do: use Android's MediaCodec to encode raw PCM audio samples into a raw AAC file.

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

The problem I have: when I use FFMPEG to pack the generated raw AAC file into an M4A container, FFMPEG complains about missing codec parameters in the file.

详情:

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

Since I can't find any MediaCodec sample code for the audio encoder that generates an output AAC file, I tried to modify the video encoder into an audio encoder. The original code is here: source_code

我这样配置音频编码器:

I configured the audio encoder like this:

    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.循环一直持续到输入文件结束.

Then I feed the encoder with 10ms worth of PCM samples per frame. The encoder takes each frame, generates a frame of bitstream, and I write the bitstream into an FileOutputStream. The loop continues until the end of the input file.

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

The code runs to the finish. I do 'adb pull' to get the generated AAC file from the device to my PC, and use FFMPEG to read it. Below is the command and the error FFMPEG spits out:

$ 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

我的问题:

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

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

Any helps will be deeply appreciated. It'd be great if there is a sample project that does what I'm trying to do here. If my source code can help you help me, I'll post it. I need to do some cleanup. Thanks!

编辑:将标题从MediaCodec 缺少编解码器参数生成的基本 AAC 文件"更改为如何使用 Android MediaCodec 生成 AAC ADTS 基本流"

Edit: Changed the title from "Elementary AAC file generated by MediaCodec missing codec parameters" to "How to generate the AAC ADTS elementary stream with Android MediaCodec"

推荐答案

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

I finally generated AAC files that are playable on both the Android device and the Windows host computer. I am posting my solution here, hoping it could help others.

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

First, my previous assumption that the Android MediaCodec encoder generates the elementary AAC stream was not accurate. The MediaCodec encoder generates the raw AAC stream. That's why the files could not be played. The raw AAC stream needs to be converted into a playable format, such as the ADTS stream. I have changed the title of this post to reflect my new understanding. There was another post that asked a similar question, and had an excellent answer. However, a novice may not necessarily understand the brief descriptions there. I didn't quite get it the 1st time I read that post.

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

So, in order to generate an AAC bitstream that can be played by a media player, I started from the EncoderTest example given by fadden in his 1st comment, but modified the original code to add the ADTS header per output frame (access unit), and to write the resulting stream into a file (replaced lines 248 through 267 of the original code with the following code snippet):

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 函数:

Outside the loop, I defined the function addADTStoPacket like this:

/**
 *  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 对它们很满意.

I also added code to control how to stop generating the AAC ADTS stream, but that's application specific, so I won't detail here. With all these changes, the generated AAC files can be played on the Android device, on my Windows PC, and ffmpeg is happy with them.

这篇关于如何使用 Android MediaCodec 生成 AAC ADTS 基本流的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆