ADTS是Audio Data Transport Stream的简称。是AAC音频文件常见的传输格式。有的时候当你编码AAC裸流的时候,会遇到写出来的AAC文件并不能在PC和手机上播放,很大的可能就是AAC文件的每一帧里缺少了ADTS头信息文件的包装拼接。只需要加入头文件ADTS即可。一个AAC原始数据块长度是可变的,对原始帧加上ADTS头进行ADTS的封装,就形成了ADTS帧。
1. ADTS头文件结构和信息
AAC音频文件的每一帧由ADTS Header和AAC Audio Data组成。
大家可以用AAC Audio ES Viewer工具来查看AAC的ADTS Header。
每一帧的ADTS的头文件都包含了音频的采样率,声道,帧长度等信息,这样解码器才能解析读取。
一般情况下ADTS的头信息都是7个字节,分为2部分:
adts_fixed_header();
adts_variable_header();
先来看:adts_fixed_header();
- syncword :总是0xFFF, 代表一个ADTS帧的开始, 用于同步.
解码器可通过0xFFF确定每个ADTS的开始位置.
因为它的存在,解码可以在这个流中任何位置开始, 即可以在任意帧解码。 - ID:MPEG Version: 0 for MPEG-4,1 for MPEG-2
- Layer:always: ‘00’
- protection_absent:Warning, set to 1 if there is no CRC and 0 if there is CRC
- profile:表示使用哪个级别的AAC,如01 Low Complexity(LC) – AAC LC
profile的值等于 Audio Object Type的值减1.
profile = MPEG-4 Audio Object Type - 1
- channel_configuration:声道数,比如2表示立体声双声道
接下来看下adts_variable_header();
- aac_frame_length:一个ADTS帧的长度包括ADTS头和AAC原始流。frame length, this value must include 7 or 9 bytes of header length:
aac_frame_length = (protection_absent == 1 ? 7 : 9) + size(AACFrame)
protection_absent=0时, header length=9bytes
protection_absent=1时, header length=7bytes - adts_buffer_fullness:0x7FF 说明是码率可变的码流。
- number_of_raw_data_blocks_in_frame:表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。
所以说number_of_raw_data_blocks_in_frame == 0 表示说ADTS帧中有一个AAC数据块。
(一个AAC原始帧包含一段时间内1024个采样及相关数据)
2. 测试代码
/*
*ADTS头的实际长度是7个字节或9个字节,9个字节的是有2个Byte校验码的,这里以7字节为例
*设置音频的ADTS头信息;header为头信息的指针;dataLen为音频数据包的大小
*/
void fftest::adts_header(char* header, int dataLen)
{
// aac级别,0: AAC Main 1:AAC LC (Low Complexity) 2:AAC SSR (Scalable Sample Rate) 3:AAC LTP (Long Term Prediction)
int aac_type = 1;
// 采样率下标,下标7表示采样率为48000
int sampling_frequency_index = 3;
// 声道数
int channel_config = 2;
// ADTS帧长度,包括ADTS长度和AAC声音数据长度的和。
int adtsLen = dataLen + 7;
// syncword,标识一个帧的开始,固定为0xFFF,占12bit(byte0占8位,byte1占前4位)
header[0] = (uint8_t)0xff;
header[1] = (uint8_t)0xf0;
// ID,MPEG 标示符。0表示MPEG-4,1表示MPEG-2。占1bit(byte1第5位)
header[1] |= (0 << 3);
// layer,固定为0,占2bit(byte1第6、7位)
header[1] |= (0 << 1);
// protection_absent,标识是否进行误码校验。0表示有CRC校验,1表示没有CRC校验。占1bit(byte1第8位)
header[1] |= 1;
// profile,标识使用哪个级别的AAC。1: AAC Main 2:AAC LC 3:AAC SSR 4:AAC LTP。占2bit(byte2第1、2位)
header[2] = aac_type << 6;
// sampling_frequency_index,采样率的下标。占4bit(byte2第3、4、5、6位)
header[2] |= (sampling_frequency_index & 0x0f) << 2;
// private_bit,私有位,编码时设置为0,解码时忽略。占1bit(byte2第7位)
header[2] |= (0 << 1);
// channel_configuration,声道数。占3bit(byte2第8位和byte3第1、2位)
header[2] |= (channel_config & 0x04) >> 2;
header[3] = (channel_config & 0x03) << 6;
// original_copy,编码时设置为0,解码时忽略。占1bit(byte3第3位)
header[3] |= (0 << 5);
// home,编码时设置为0,解码时忽略。占1bit(byte3第4位)
header[3] |= (0 << 4);
// copyrighted_id_bit,编码时设置为0,解码时忽略。占1bit(byte3第5位)
header[3] |= (0 << 3);
// copyrighted_id_start,编码时设置为0,解码时忽略。占1bit(byte3第6位)
header[3] |= (0 << 2);
// aac_frame_length,ADTS帧长度,包括ADTS长度和AAC声音数据长度的和。占13bit(byte3第7、8位,byte4全部,byte5第1-3位)
header[3] |= ((adtsLen & 0x1800) >> 11);
header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);
header[5] = (uint8_t)((adtsLen & 0x7) << 5);
// adts_buffer_fullness,固定为0x7FF。表示是码率可变的码流 。占11bit(byte5后5位,byte6前6位)
header[5] |= 0x1f;
header[6] = (uint8_t)0xfc;
// number_of_raw_data_blocks_in_frame,值为a的话表示ADST帧中有a+1个原始帧,(一个AAC原始帧包含一段时间内1024个采样及相关数据)。占2bit(byte6第7、8位)。
header[6] |= 0;
}
void fftest::extractAudio(const char* src, const char* dst)
{
// 设置日志输出等级
av_log_set_level(AV_LOG_INFO);
AVFormatContext* fmt_ctx = NULL;
/*
* 如果传入的两个路径为空
*/
if (!src||!dst)
{
av_log(NULL, AV_LOG_ERROR, "传入的路径不能为空\n");
return;
}
//打开输入文件
int flag;
flag = avformat_open_input(&fmt_ctx, src, NULL, NULL);
if (flag < 0)
{
av_log(NULL, AV_LOG_ERROR, "打开文件失败");
avformat_close_input(&fmt_ctx);
return;
}
//打开要写入aac音频的文件,没有就会创建一个
std::ofstream file;
file.open(dst, std::ios::binary | std::ios::out);
//FILE* file/* = fopen(dst, "wb")*/;
//fopen_s(&file, dst, "wb");
if (!file.is_open()/*file == NULL*/)
{
av_log(NULL, AV_LOG_ERROR, "不能打开或创建存储文件!");
avformat_close_input(&fmt_ctx);
return;
}
//输出信息
av_dump_format(fmt_ctx, NULL, src, NULL);
/*
* 获取最好的一路流的资源
*第二个参数是流的类型
*第三个参数是流的索引号,不知道就写-1
*第四个参数是相关的对应的流的索引号,比如音频对应的视频流的索引号,可以不必关心填-1
*第五个参数是设置的编解码器,不设置就写NULL
*第六个参数是一些标准,暂时不关心填0
*返回值是所找到的流的索引值
* */
int audio_index;//音频索引值
audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (audio_index<0)
{
av_log(NULL, AV_LOG_ERROR, "获取流失败!");
//推出前关闭文件释放内容
avformat_close_input(&fmt_ctx);
file.close();
//fclose(file);
return;
}
//读取流中的包数据
AVPacket pkt;
av_init_packet(&pkt); //初始化pkt
//循环读取流中所有的包,这里注意传进去的是pkt的地址
while (av_read_frame(fmt_ctx, &pkt) >= 0)
{
//判断读取的包所属的流是否是我们找到的流
if (pkt.stream_index == audio_index)
{
//如果读取的包是我们找的流就写入文件
char adts_header_buf[7];
adts_header(adts_header_buf, pkt.size);
file.write(adts_header_buf, 1 * 7);
//fwrite(adts_header_buf, 1, 7, file);
if (!file.write((const char*)pkt.data,1*pkt.size)
/*fwrite(pkt.data,1,pkt.size,file)!=pkt.size*/
)
{
av_log(NULL, AV_LOG_WARNING, "警告,写入的大小与返回的大小不一致!");
}
}
// 因为每次循环都要为pkt分配内存,所以一轮循环结束时要释放内存
av_packet_unref(&pkt);
}
avformat_close_input(&fmt_ctx);
if (file.is_open()) {
file.close();
/*fclose(file);*/
}
}
声明:本文参考文章https://www.jianshu.com/p/5337260efd97,文件输出及个别地方参数做了修改,如有侵权,请告知。