和其他编码格式一样,AAC只是数据编码格式,码流组织格式有ADIF(Audio Data Interchange Format) 和 ADTS (Audio Data Transport Stream)。
ADIF
与 ADTS
的显著区别就是前者的编码信息存在一个固定的地方,后者的编码信息是每一个包中都有。所以ADIF主要用于磁盘存储文件,ADTS主要用于渐进式传输的网络流,本文主要分析ADTS流。
ADTS 流格式
ADTS流格式为ADTS头部加AAC裸数据。
[ADTS Header](AAC ES data) | [ADTS Header](AAC ES data) | ...
ADTS Header为固定的7字节长度,格式可以用下面字母序列来表示,一个字母表示一种字段,数量表示比特长度。
AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ)
其中A-J称为ADTS固定头部,K-Q称为可变头部。
标志 | 比特长度 | 描述 |
---|---|---|
A | 12 | 同步字段,全1 |
B | 1 | MPEG版本: 0 for MPEG-4, 1 for MPEG-2,mp4是0 |
C | 2 | Layer: 全0 |
D | 1 | 保护缺失标识ProtectionAbsent , 1 表示无 CRC ,0 表示有 CRC |
E | 2 | AAC编码级别, MPEG-4 Audio Object Type减1。0: Main Profile, 1:LC, 2: SSR,3:保留。常用低复杂度编码LC。 |
F | 4 | MPEG-4 采样率表序号,注意这里是序号,不是采样率值,参考采样率表。 |
G | 1 | 私有位,设为0,解码时忽略 |
H | 3 | 声道数,取值范围1-7。 |
I | 1 | 源标识, 编码设为0,解码忽略 |
J | 1 | home, 编码设为0,解码忽略 |
K | 1 | 版权标志位,编码设为0,解码忽略 |
L | 1 | 版权标志开始位,编码设为0,解码忽略 |
M | 13 | 帧长度,为AAC原始数据长度+ADTS头长度(ProtectionAbsent == 1 ? 7 : 9) |
O | 11 | Buffer fullness, 0x7FF 说明是码率可变的码流 |
P | 2 | AAC帧数量减1值,有1个帧时此值为0 |
Q | 16 | 如果保护缺失标识ProtectionAbsent为0,标识有2字节CRC校验字段 |
官方文档原图为
采样率表如下所示。
FFmpeg 解析保存mp4中aac码流方法
FFmpeg使用av_read_frame(AVFormatContext *s, AVPacket *pkt);
函数从mp4文件中读取到的AVPacket的data是AAC裸数据,直接保存成文件的话,是没法播放的,因为没有采样率等信息。可以根据上文ADTS头部结构描述,每个帧都填充1个Header信息。
下面是我自己使用的ADTS头部定义,可以参考。注意其中单个字节内的按位定义是倒序的,可能不便于理解。
ADTSHeader.h
#ifndef _ADTSHEADER_H
#define _ADTSHEADER_H
#include <string.h>
// 7 bytes
struct ADTS_Header {
// fixed header
// 1 byte
uint8_t sync_word_l : 8; // 0xFF
// 2 byte
// sync_word_h + id + layer + protection_absent
uint8_t protection_absent : 1; // 1 no CRC, 0 has CRC
uint8_t layer : 2; // 00
uint8_t id : 1; // 0: MPEG-4, 1: MPEG-2
uint8_t sync_word_h : 4; // 0xF
// 3rd byte
// profile + sampling_frequency_index + private_bit + channel_configuration_l
uint8_t channel_configuration_l : 1;
uint8_t private_bit : 1;
uint8_t sampling_frequency_index : 4;
uint8_t profile : 2; // 0:main, 1: LC, 2: SSR, 3: reserved
// 4th byte
uint8_t aac_frame_length_l : 2;
uint8_t copyright_identification_start : 1;
uint8_t copyright_identification_bit : 1;
uint8_t home : 1;
uint8_t original_copy : 1;
uint8_t channel_configuration_h : 2;
// 5th byte
uint8_t aac_frame_length_m : 8;
// 6th byte
uint8_t adts_buffer_fullness_l : 5;
uint8_t aac_frame_length_h : 3;
// 7th byte
uint8_t number_of_raw_data_blocks_in_frame : 2;
uint8_t adts_buffer_fullness_h : 6; // adts_buffer_fullness 0x7ff VBR
ADTS_Header()
{
memset(this, 0, sizeof(ADTS_Header));
setSyncWord();
protection_absent = 1;
profile = 1;
}
ADTS_Header(int samplingFreq, int channel, int length)
{
memset(this, 0, sizeof(ADTS_Header));
setSyncWord();
setSamplingFrequency(samplingFreq);
setChannel(channel);
setLength(length);
protection_absent = 1;
profile = 1;
}
ADTS_Header &setSyncWord()
{
sync_word_l = 0xff;
sync_word_h = 0xf;
return *this;
}
ADTS_Header &setSamplingFrequency(int sf)
{
int sampling_frequency_table[13] = {96000, 88200, 64000, 48000, 44100, 32000, 24000,
22050, 16000, 12000, 11025, 8000, 7350};
sampling_frequency_index = 0xf;
for (int i = 0; i < 13; ++i) {
if (sampling_frequency_table[i] == sf) {
sampling_frequency_index = i;
break;
}
}
return *this;
}
ADTS_Header &setChannel(int ch)
{
if (ch > 0 && ch < 7)
channel_configuration_h = ch;
else if (ch == 8)
channel_configuration_h = 7 >> 24;
return *this;
}
// length = header length + aac es stream length
ADTS_Header &setLength(int length)
{
aac_frame_length_l = (length >> 11) & 0x03;
aac_frame_length_m = (length >> 3) & 0xff;
aac_frame_length_h = (length & 0x07);
return *this;
}
int getLength()
{
int l = (aac_frame_length_l << 11) | (aac_frame_length_m << 3) | (aac_frame_length_h);
return l;
}
int getChannel()
{
if (channel_configuration_h == 8)
return 8;
return channel_configuration_h;
}
};
#endif // _ADTSHEADER_H
保存文件的时候,在每个帧之前都写一个ADTS Header就可以播放了。
ADTS_Header header(48000, 2, avPacket->size + 7); //假设是48K采样率2声道音频
fwrite(&header, 1, sizeof(ADTS_Header), _file);
fwrite(avPacket->data, 1, avPacket->size, _file);
提醒一下,AAC编码1个帧为1024个采样点,保存文件的时候不需要时间戳信息,播放时就按采样率播放。