Android直播开发之旅(3):AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer)
(码字不易,转载请声明出处:http://blog.csdn.net/andrexpert/article/details/72523408)
1. AAC编码格式分析
(1) AAC简介
高级音频编码(AdvancedAudio Coding,AAC)一种基于MPEG-4的音频编码技术,它由杜比实验室、AT&T等公司共同研发,目的是替换MP3编码方式。作为一种高压缩比的音频压缩算法,AAC的数据压缩比约为18:1,压缩后的音质可以同未压缩的CD音质相媲美。因此,相对于MP3、WMA等音频编码标准来说,在相同质量下码率更低,有效地节约了传输带宽,被广泛得应用于互联网流媒体、IPTV等领域(低码率,高音质)。主要有以下特点:
a) 比特率:AAC- 最高512kbps(双声道时)/MP3- 32~320kbps
b) 采样率:AAC- 最高96kHz / MP3 - 最高48kHz
c) 声道数:AAC– 最高48个全音域声道/MP3 - 两声道
d) 采样精度:AAC- 最高32bit / MP3 - 最高16bit
AAC的不足之处是,它属于有损压缩的格式,相对于APE和FLAC等主流无损压缩,音色“饱满度”差距比较大。另外,除了流媒体网络传输,其所能支持的设备较少。
(2) AAC编码封装格式
音频数据在压缩编码之前,要先进行采样与量化,以样值的形式存在。音频压缩编码的输出码流,以音频帧的形式存在。每个音频帧包含若干个音频采样的压缩数据,AAC的一个音频帧包含960或1024个样值,这些压缩编码后的音频帧称为原始数据块(RawData Block),由于原始数据块以帧的形式存在,即简称为原始帧。原始帧是可变的,如果对原始帧进行ADTS的封装,得到的原始帧为ADTS帧;如果对原始帧进行ADIF封装,得到的原始帧为ADIF帧。它们的区别如下:
a) ADIF:AudioData Interchange Format,音频数据交换格式。这种格式明确解码必须在明确定义的音频数据流的开始处进行,常用于磁盘文件中;
b) ADTS:AudioData Transport Stream,音频数据传输流。这种格式的特点是它一个有同步字的比特流,且允许在音频数据流的任意帧解码,也就是说,它每一帧都有信息头。
一个AAC原始数据库长度是可变的,对原始帧加上ADTS头进行ADTS封装就形成了ADTS帧。AAC音频的每一帧(ADTS帧)体由ADTS Header和AAC Audio Data(包含1~4个音频原始帧)组成,其中,ADTS Header占7个字节或9个字节,由两部分组成:固定头信息(adts_fixed_header)、可变头信息(adts_variable_header)。固定头信息中的数据每一帧都是相同的,主要定义了音频的采样率、声道数、帧长度等关键信息,这是解码AAC所需关键信息;可变头信息则在帧与帧之间可变。
下面是多个ADTS帧组成的AAC数据流结构,示意图如下:
a) 固定信息头
说明:
* syncword:占12bits。同步头,表示一个ADTS帧的开始,总是0xFFF。正是因为它的存在,才支持解码任意帧;
* ID: 占1bit。MPEG的版本,0为MPGE-4,1为MPGE-2;
* Layer: 占2bits。总是”00”;
* protection_absent:占1bit。=0时,ADTS Header长度占9字节;=1时,ADTS Header占7字节;
* profile: 占2bit。使用哪个级别的AAC,值00、01、10分别对应Mainprofile、LC、SSR;
* sampling_frequency_index:占4bits。表示使用的采样率下标,通过这个下标在Sampling Frequencies[ ]数组中查找得知采样率的值,如0xb,对应的采样率为8000Hz;
* channel_configuration:表示声道数,如1-单声道,2-立体声
(b)可变信息头
说明:
* frame_length:占13bits。表示一个ADTS帧的长度,即ADTS头(7或9字节)+sizeof(AAC Frame);
* adts_buffer_fullness:占11bits。值0x7FF,说明是码率可变的码流
* number_of_raw_data_blocks_In_frame:占2bits。表示ADTS帧中有(number_of_raw_data_blocks_In_frame+1)个AAC原始帧
(3) 将AAC打包成ADTS格式
众所周知,在使用MediaCodec将PCM压缩编码为AAC时,编码器输出的AAC是没有ADTS头的原始帧,如果我们直接保存为AAC文件或推流,VLC等工具是无法将AAC数据流解码播放的。因此,我们需要对MediaCodec编码PCM输出的AAC原始帧添加ADTS数据头,然后再进行文件保存或者推流。MediaCodec部分代码如下:
private void encodeBytes(byte[] audioBuf, int readBytes) {
ByteBuffer[] inputBuffers = mAudioEncoder.getInputBuffers();
ByteBuffer[] outputBuffers = mAudioEncoder.getOutputBuffers();
int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(TIMES_OUT);
if(inputBufferIndex >= 0){
ByteBuffer inputBuffer = null;
if(!isLollipop()){
inputBuffer = inputBuffers[inputBufferIndex];
}else{
inputBuffer = mAudioEncoder.getInputBuffer(inputBufferIndex);
}
if(audioBuf==null || readBytes<=0){
mAudioEncoder.queueInputBuffer(inputBufferIndex,0,0,getPTSUs(),MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}else{
inputBuffer.clear();
inputBuffer.put(audioBuf);
mAudioEncoder.queueInputBuffer(inputBufferIndex,0,readBytes,getPTSUs(),0);
}
}
// 返回一个输出缓存区句柄,当为-1时表示当前没有可用的输出缓存区
// mBufferInfo参数包含被编码好的数据,timesOut参数为超时等待的时间
MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = -1;
do{
outputBufferIndex = mAudioEncoder.dequeueOutputBuffer(mBufferInfo,TIMES_OUT);
if(outputBufferIndex == MediaCodec. INFO_TRY_AGAIN_LATER){
Log.i(TAG,"获得编码器输出缓存区超时");
}else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){
}else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
}else{
if((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0){
mBufferInfo.size = 0;
}
if((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0){
break;
}
// 获取一个只读的输出缓存区inputBuffer ,它包含被编码好的数据
ByteBuffer mBuffer = ByteBuffer.allocate(10240);
ByteBuffer outputBuffer = null;
if(!isLollipop()){
outputBuffer = outputBuffers[outputBufferIndex];
}else{
outputBuffer = mAudioEncoder.getOutputBuffer(outputBufferIndex);
}
if(mBufferInfo.size != 0){
Log.i(TAG,"AAC流添加ADTS头,缓存到mBuffer");
mBuffer.clear();
// 拷贝outputBuffer编码好的AAC原始帧到mBuffer,从第8个字节存放
// mBuffer的前7个字节留用(数组下标0~6)
outputBuffer.get(mBuffer.array(), 7, mBufferInfo.size);
outputBuffer.clear();
// 将buffer的position置7 + mBufferInfo.size
mBuffer.position(7 + mBufferInfo.size);
// 添加ADTS头,其中(mBufferInfo.size + 7)为ADTS帧长度
addADTStoPacket(mBuffer.array(), mBufferInfo.size + 7);
// 将buffer的position置0
mBuffer.flip();
// 推流AAC
...
}
mAudioEncoder.releaseOutputBuffer(outputBufferIndex,false);
}
}while (outputBufferIndex >= 0);
}
//----------------------------添加ADTS头,7个字节-------------------------------
private void addADTStoPacket(byte[] packet, int packetLen) {
int profile = 2;
int chanCfg = 1;
int sampleRate = mSamplingRateIndex ;
packet[0] = (byte) 0xFF;