Android直播开发之旅(3):AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer)

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;  
  • 11
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 62
    评论
评论 62
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值