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"