1、AAC:AAC是一种音频编码格式,由于其可以任意帧解码的优点,常用于直播中。
AAC的封装格式:ADTS、ADIF。那为什么需要对AAC进行封装呢?这是由于音频流在传输的过程中,是以一个一个数据包进行发送的,我们需要明确各个数据包之间的界限,这样可以避免数据包的丢失导致的解码错误。
2、ADTS封装格式有这样一个优点:可以在任意帧进行解码而不需要等待数据发送完成后再进行,这是由于ADTS是对每个数据包都进行了头部封装。ADIF是对所有数据包加一个头部,这样只能在接收到所有数据后在进行解码,实际上我们使用ADTS这种封装格式更多。
3、ADTS的头部一般是7字节,包括固定头部与可变头部。由于头部信息过多,我们只关心常见的:syncword:0xFFF用于帧边界划分,channel:声道数,frame_length:头部长度+pkt长度。对于MP4/FLV格式的视频文件读出来的AAC没有ADTS头部,需要手动封装。
固定头部:
字节 | 位 | 字段 | 说明 |
---|---|---|---|
0 | 8 | syncword | 同步字,固定为 0xFFF |
1 | 1 | ID | MPEG 版本,0 表示 MPEG-4,1 表示 MPEG-2 |
1 | 2 | layer | 固定为 00 |
1 | 1 | protection_absent | 是否有 CRC 校验,0 表示有 CRC,1 表示无 CRC |
1-2 | 2 | profile | AAC 编码等级 |
2 | 4 | sampling_frequency_index | 采样率索引 |
2 | 1 | private_bit | 私有位 |
2 | 3 | channel_configuration | 声道配置 |
3 | 1 | original/copy | 原始或复制标志 |
3 | 1 | home | Home 标志 |
可变头部:
字节 | 位 | 字段 | 说明 |
---|---|---|---|
3 | 1 | copyright_identification_bit | 版权标志 |
3 | 1 | copyright_identification_start | 版权开始 |
3-5 | 13 | frame_length | ADTS 帧长度,包括头部和AACd数据 |
5-7 | 11 | adts_buffer_fullness | ADTS 缓冲区满度 |
6-7 | 2 | number_of_raw_data_blocks_in_frame | 当前帧中的原始数据块数目 |
4、FLV文件中提取AAC并添加ADTS头部
完整代码如下:
#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>
#define ADTS_HEADER_LEN 7;
// 采样率选择结构体
const int sampling_frequencies[] = {
96000,
88200,
64000,
48000,
44100,
32000,
24000,
22050,
16000,
12000,
11025,
8000
};
int adts_header(char* p_adts_header,int data_length,int profile,int samplerate,int channels)// 构建ADTS头部7字节
{
int adts_size = data_length + 7; // ADTS数据长度
int i = 0;
int sampling_frequency_index = 3; // 默认采用48KHZ采样率
for(i = 0;i<sizeof(sampling_frequencies)/sizeof(sampling_frequencies[0]);i++)
{
if(sampling_frequencies[i] == samplerate)
{
sampling_frequency_index = i;
break;
}
}
if(i >= sizeof(sampling_frequencies)/sizeof(sampling_frequencies[0]))
{
printf("not support samplerate:%d\n",samplerate);
return -1;
}
p_adts_header[1] = 0xf0; // sync:0xFFF
p_adts_header[1] |= (0 << 3);
p_adts_header[1] |= (0 << 1);
p_adts_header[1] |= 1;
p_adts_header[2] = (profile)<<6;
p_adts_header[2] |= (sampling_frequency_index & 0x0f)<<2;
p_adts_header[2] |= (0 << 1);
p_adts_header[2] |= (channels & 0x04)>>2;
p_adts_header[3] = (channels & 0x03)<<6;
p_adts_header[3] |= (0 << 5);
p_adts_header[3] |= (0 << 4);
p_adts_header[3] |= (0 << 3);
p_adts_header[3] |= (0 << 2);
p_adts_header[3] |= ((adts_size & 0x1800) >> 11); // ADTS总长度
p_adts_header[4] = (uint8_t)((adts_size & 0x7f8) >> 3);
p_adts_header[5] = (uint8_t)((adts_size & 0x7) << 5);
p_adts_header[5] |= 0x1f;
p_adts_header[6] = 0xfc;
return 0;
}
int aac_adts()
{
char* in_filename = "/home/yx/media_file/believe.flv"; // 输入文件路径
char* out_filename = "/home/yx/media_file/believe.aac"; // 输出文件路径
AVFormatContext* in_file_ctx = NULL; // 解复用器上下文
FILE* out_aac_fd = NULL; // 输出文件句柄
int audio_index = -1; // 音频标签
AVPacket* pkt = av_packet_alloc(); // 保存音频包
out_aac_fd = fopen(out_filename,"w"); // 打开输出文件
avformat_open_input(&in_file_ctx,in_filename,NULL,NULL); // 将输入媒体流与输入文件上下文进行关联
avformat_find_stream_info(in_file_ctx, NULL); // 查找媒体流信息
audio_index = av_find_best_stream(in_file_ctx,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0); // 查找音频流对应的标签
printf("audio_index:%d\n",audio_index);
while(1)
{
if(av_read_frame(in_file_ctx,pkt) < 0) // 读取数据包
{
av_free_packet(pkt);
break;
}
if(pkt->stream_index == audio_index) // 如果是音频流
{
char adts_header_buf[7] = {0};
// 构建ADTS头部7字节
adts_header(adts_header_buf,pkt->size,in_file_ctx->streams[audio_index]->codecpar->profile,in_file_ctx->streams[audio_index]->codecpar->sample_rate,in_file_ctx->streams[audio_index]->codecpar->channels);
fwrite(adts_header_buf,1,7,out_aac_fd); // 将ADTS头部写到文件中
fwrite(pkt->data,1,pkt->size,out_aac_fd);
}
av_packet_unref(pkt);
}
}
int main()
{
aac_adts();
printf("Hello World!\n");
return 0;
}