赞
踩
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
- ...
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。