AAC音频格式解析(ADTS)

  AAC 有两种数据交换格式分别为:ADTS 和 ADIF。 ADIF 因为整个流中只包含一个文件头,不能在任意时码处读取音频压缩包来解析,而ADTS在每一帧的音频压缩数据包中都有一个文件头信息,记录了音频的采样率、通道数等参数,因此ADTS的用途要比 ADIF 广泛。本文先介绍ADTS的文件头参数,而后用 ffmpeg 读取音频包保存成 AAC文件。

一、AAC文件头信息

ADTS的头信息分为:固定头信息(adts_fixed_header)和可变头信息(adts_variable_header)两部分。

1.1 adts_fixed_header

在这里插入图片描述syncword :同步头代表着1个ADTS帧的开始,所有bit置1,即 0xFFF
ID:MPEG标识符,0标识MPEG-4,1标识MPEG-2
Layer: 直接置00
protection_absent:表示是否误码校验。1 no CRC , 0 has CRC
profile:AAC 编码级别, 0: Main Profile, 1:LC(最常用), 2: SSR, 3: reserved.
sampling_frequency_index:采样率标识
Private bit:直接置0,解码时忽略这个参数
channel_configuration: 声道数标识
original_copy: 直接置0,解码时忽略这个参数
home:直接置0,解码时忽略这个参数
采样率和通道数标识表:
在这里插入图片描述



1.2 adts_variable_header

在这里插入图片描述

copyright_identification_bit: 直接置0,解码时忽略这个参数
copyright_identification_start: 直接置0,解码时忽略这个参数
aac_frame_lenght: 当前音频帧的字节数,编码元数据字节数 + 文件头字节数(0 == protection_absent ? 7: 9)
adts_buffer_fullness: 当设置为0x7FF时表示时可变码率
number_of_raw_data_blocks_in_frames: 当前音频包里面包含的音频编码帧数, 置为 aac_nums - 1, 即只有一帧音频时置0

二、代码实现结合 FFmpeg 保存AAC 文件

2.1 FFmpeg读取 AAC数据包

  先检测素材是否含有 AAC格式的音频流,再读取数据


bool CheckAACInClip(const char* url)
{
	bool has_aac = false;
	int  rec = -1;
	do
	{
		rec = avformat_open_input(&g_filefmt_ctx, url, nullptr, nullptr);
		if (rec < 0)
		{
			break;
		}

		rec = avformat_find_stream_info(g_filefmt_ctx, nullptr);
		if (rec < 0)
		{
			break;
		}

		av_dump_format(g_filefmt_ctx, 0, url, 0);

		g_audio_stream_index = av_find_best_stream(g_filefmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
		if (AVERROR_STREAM_NOT_FOUND == g_audio_stream_index)
		{
			break;
		}

		if (AV_CODEC_ID_AAC != g_filefmt_ctx->streams[g_audio_stream_index]->codecpar->codec_id)
		{
			break;
		}

		has_aac = true;

	} while (false);

	return has_aac;
}

bool ReadAACPkt(AVPacket* aac_pkt)
{
	bool get_acc = false;
	
	while ( 0 == av_read_frame(g_filefmt_ctx, aac_pkt))
	{
		if (g_audio_stream_index == aac_pkt->stream_index)
		{
			get_acc = true;
			break;		
		}

		av_packet_unref(aac_pkt);
	}

	return get_acc;
}

2.2 保存AAC文件头信息

void SaveCurADTSInfo(AVPacket* aac_pkt)
{
	//syncword 0xFFF
	g_adts_header[0] |= 0xff;
	g_adts_header[1] |= 0xf0;

	//ID 
	g_adts_header[1] |= (0<<3);

	//layer
	g_adts_header[1] |= (0<<1);

	//potection_absent
	g_adts_header[1] |= 1;

	//profile
	int profile = g_filefmt_ctx->streams[g_audio_stream_index]->codecpar->profile;
	g_adts_header[2] = (profile << 6);

	//sampple rate
	int sample_rate = g_filefmt_ctx->streams[g_audio_stream_index]->codecpar->sample_rate;
	int samplerate_mark = g_samplerate_table.find(sample_rate)->second;
	g_adts_header[2] |= (samplerate_mark << 2);

	//private bit
	g_adts_header[2] |= (0<<1);
	
	//channel
	int channels = g_filefmt_ctx->streams[g_audio_stream_index]->codecpar->channels;
	g_adts_header[2] |= (channels & 0x04) >> 2;
	g_adts_header[3] =  (channels & 0x03) << 6;

	//original_copy
	g_adts_header[3] |= (0<<5);

	//home
	g_adts_header[3] |= (0<<4);

	//copyright_identification_bit
	g_adts_header[3] |= (0<<3);

	//copyright_identification_start
	g_adts_header[3] |= (0<<2);

	//aac_frame_length
	int data_lenth = aac_pkt->size + static_cast<int>( g_adts_header.size() );
	//int data_lenth = aac_pkt->size + 7;
	g_adts_header[3] |= ( (data_lenth & 0x1800) >> 11 );
	g_adts_header[4] =  ( (data_lenth & 0x7F8) >> 3 );
	g_adts_header[5] =  ( (data_lenth & 0x7) << 5 );

	//adts_buffer_fullness
	g_adts_header[5] |= (0x7C >> 6);
	g_adts_header[6] |=  (0x3F << 2);
	
	//number_of_raw_data_blocks_in_frame
	g_adts_header[6] |= (0x00);
}

2.3 实现

static unordered_map<int, int> g_samplerate_table = {
	{96000, 0},
	{88200, 1},
	{64000, 2},
	{48000, 3},
	{44100, 4},
	{32000, 5},
	{24000, 6},
	{22050, 7},
	{16000, 8},
	{12000, 9},
	{11025, 10},
	{8000,  11},
	{7350,  12}
};
 
static AVFormatContext* g_filefmt_ctx = nullptr;
static int g_audio_stream_index = -1;
static vector<char> g_adts_header(7);

int main(int argc, char* argv[])
{
	char* src_file = argv[1];
	char* dst_aac_file = argv[2];
	int  rec = -1;

	ofstream out_aac_file;
	bool prepare = false; 

	do 
	{
		if (!CheckAACInClip(src_file))
		{
			cout << "there is no aac stream in clip" << endl;
			break;
		}

		out_aac_file.open(dst_aac_file, ios_base::binary | ios_base::out | ios_base::app);
		if (!out_aac_file.good())
		{
			break;
		}
		prepare = true;

	} while (false);

	
	AVPacket* aac_pkt =av_packet_alloc();
	av_packet_unref(aac_pkt);
	while (prepare && ReadAACPkt(aac_pkt))
	{
		SaveCurADTSInfo(aac_pkt);
		out_aac_file.write(&g_adts_header.front(), g_adts_header.size() );
		out_aac_file.write(reinterpret_cast<char*>(aac_pkt->data), aac_pkt->size);
		
		av_packet_unref(aac_pkt);
	}

	out_aac_file.close();
	av_packet_free(&aac_pkt);
	avformat_close_input(&g_filefmt_ctx);
	return 0;
}

2.4 代码地址和测试

 完整代码地址:https://github.com/pengguoqing/samples_code/tree/master/AV/ADTS
 对于生成完的素材直接用 ffplay播放测试效果

  .\ffplay.exe  "test_out.aac"
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值