AAC 音频
AAC(Advanced Audio Coding),即高级音频编码,是一种专为 声音数据 设计的 文件压缩格式,具有以下优点:
- 提升压缩率:以更小的文件大小获得更高的音质;
- 支持多声道:可提供最多 48 个全音域声道;
- 更高解析度:最高支持 96KHz 的采样频率;
- 提升解码效率:解码播放所占的资源更少。
首先给出 ffmpeg 解封装命令,用于从音视频文件中提取 aac 音频流:
// 解封装, 提取 aac 音频流
ffmpeg -i ./水流众生.mp4 -vn -acodec aac ./水流众生.aac
// 使用 ffplay 播放
ffplay -hide_banner -autoexit ./水流众生.aac
然后给出代码实现:
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include <stdio.h>
/**
* step 1 : 打开媒体文件 avformat_open_input
* step 2 : 获取码流信息 avformat_find_stream_info
* step 3 : 获取音频流 av_find_best_stream
* step 4 : 读取 packet 数据 av_read_frame
* step 5 : 释放 packet 资源 av_packet_unref
* step 6 : 关闭媒体文件 avformat_close_input
*/
int main(int argc, char* argv[])
{
av_log_set_level(AV_LOG_DEBUG);
if (argc != 3) {
av_log(NULL, AV_LOG_ERROR, "Usage, %s infile outfile\n", argv[0]);
exit(-1);
}
const char* infileName = argv[1];
const char* outfileName = argv[2];
AVFormatContext* inFormatCtx = NULL;
int ret = avformat_open_input(&inFormatCtx, infileName, NULL, NULL);
if (ret != 0) {
av_log(NULL, AV_LOG_ERROR, "open input file format failed: %s\n", av_err2str(ret));
exit(-1);
}
// 获取码流信息
ret = avformat_find_stream_info(inFormatCtx, NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "find stream info failed: %s\n", av_err2str(ret));
avformat_close_input(&inFormatCtx);
exit(-1);
}
// 获取音频流索引
int audioIndex = av_find_best_stream(inFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (audioIndex < 0) {
av_log(NULL, AV_LOG_ERROR, "find best stream failed, index is %d\n", audioIndex);
avformat_close_input(&inFormatCtx);
exit(-1);
}
av_log(NULL, AV_LOG_INFO, "the audio index is %d\n", audioIndex);
// 打开一个文件, 用于存储音频流数据
FILE* dest_fp = fopen(outfileName, "wb");
if (dest_fp == NULL) {
av_log(NULL, AV_LOG_ERROR, "open %s file failed\n", outfileName);
avformat_close_input(&inFormatCtx);
exit(-1);
}
AVPacket packet;
// 读取 packet 数据
while (av_read_frame(inFormatCtx, &packet) == 0) {
if (packet.stream_index == audioIndex) {
ret = fwrite(packet.data, 1, packet.size, dest_fp);
if (ret != packet.size) {
av_log(NULL, AV_LOG_ERROR, "write file failed!\n");
fclose(dest_fp);
avformat_close_input(&inFormatCtx);
exit(-1);
}
}
// 释放 packet 数据
av_packet_unref(&packet);
}
if (inFormatCtx) {
avformat_close_input(&inFormatCtx);
}
if (dest_fp) {
fclose(dest_fp);
}
return 0;
}
编译并运行上述代码,解封装测试文件(we_are_the_world.mp4):
使用 ffplay 播放:
提取出的 AAC 音频流无法正常播放。原因在于代码提取出的 AAC 没有包含用于解码的头部信息,下面将介绍 AAC 的两种格式。
AAC 编码存储格式
AAC 音频依据 流式传输 或者 存储 的需求存在多种格式,例如 ADTS(Audio Data Transport Stream)和 ASC(Audio Specific Config)。
1. ADTS 格式
ADTS(Audio Data Transport Stream),即音频数据传输流, 通常应用于 直播传输流,文件的每一帧前面都包含 ADTS 头信息。ADTS 格式一般用在裸 ADTS 封装中,或者在 MPEG2-TS 内用于流式传输。下图给出 ADTS 帧的构造方式:
下面通过代码构造 ADTS 头部信息:
static const int sampleFrequencyTable[] = {
96000,
88200,
64000,
48000,
44100,
32000,
24000,
22050,
16000,
12000,
11025,
8000,
7350
};
static void setADTSHeader(char* adtsHeader, int packetSize, int profile, int sampleRate, int channels)
{
int sampleFrequencyIndex = 3; // 默认使用 48000Hz
int adtsLength = packetSize + 7;
for (int i = 0; i < sizeof(sampleFrequencyTable) / sizeof(sampleFrequencyTable[0]); ++i) {
if (sampleRate == sampleFrequencyTable[i]) {
sampleFrequencyIndex = i;
break;
}
}
adtsHeader[0] = 0xff; // syncword, 帧同步标识一个帧的开始. 固定为 0xfff, 使得解析器寻找头部非常方便
adtsHeader[1] = 0xf0; // syncword 同步头 12 bits
adtsHeader[1] |= (0 << 3); // MPEG 标示符. 对于 MPEG-4 为 0, 对于 MPEG-2 为 1 1 bit
adtsHeader[1] |= (0 << 2); // Layer: 固定为 '00' 2 bits
// protection_absent: 表示是否进行CRC误码校验, 1 表示无CRC, 0 表示有CRC. 1 bit
adtsHeader[1] |= 1;
// profile表示使用哪个级别的 AAC profile 2 bits
// 1. AAC Main; 2. AAC LC(Low Complexity); 3. AAC SSR(Scalable Sample Rate); 4. AAC LTP(Long Term Prediction)
adtsHeader[2] = (profile << 6);
// sampling frequency index, 标识使用的采样率的下标 4 bits
adtsHeader[2] |= (sampleFrequencyIndex & 0x0f) << 2;
// 私有位, 编码时设置为 0, 解码时忽略 1 bit
adtsHeader[2] |= (0 << 1);
// channel configuration, 标识声道数 3 bits
adtsHeader[2] |= (channels & 0x04) >> 2;
adtsHeader[3] = (channels & 0x03) << 6; // channel configuration: channels
// original_copy, 编码时设置为 0, 解码时忽略 1 bit
adtsHeader[3] |= (0 << 5);
// home, 编码时设置为 0, 解码时忽略 1 bit
adtsHeader[3] |= (0 << 4);
// copyright_identification_bit: 编码时设置为 0 , 解码时忽略 1 bit
adtsHeader[3] |= (0 << 3);
// copyright_identification_start: 编码时设置为 0, 解码时忽略 1 bit
adtsHeader[3] |= (0 << 2);
// aac_frame_lenght: ADTS 帧长度 13 bits
// FrameLength = (ProtectionAbsent == 1 ? 7 : 9) + size(AACFrame)
adtsHeader[3] |= ((adtsLength & 0x1800) >> 11); // frame length
adtsHeader[4] = (uint8_t)((adtsLength & 0x7f8) >> 3); // frame length: value
adtsHeader[5] = (uint8_t)((adtsLength & 0x7) << 5); // frame length: value
adtsHeader[5] |= 0x1f; // buffer fullness:
adtsHeader[6] = 0xfc; // buffer fullness:
}
// ...
// 修改 main 函数中的 while 循环,将构造的 7 字节首部信息(不包含 CRC 校验)添加到 ADTS 裸流中。
while (av_read_frame(inFormatCtx, &packet) == 0) {
if (packet.stream_index == audioIndex) {
char adtsHeader[7] = {0};
setADTSHeader(adtsHeader, packet.size, inFormatCtx->streams[audioIndex]->codecpar->profile,
inFormatCtx->streams[audioIndex]->codecpar->sample_rate,
inFormatCtx->streams[audioIndex]->codecpar->ch_layout.nb_channels);
ret = fwrite(adtsHeader, 1, sizeof(adtsHeader), dest_fp);
if (ret != sizeof(adtsHeader)) {
av_log(NULL, AV_LOG_ERROR, "write file failed!\n");
fclose(dest_fp);
avformat_close_input(&inFormatCtx);
exit(-1);
}
ret = fwrite(packet.data, 1, packet.size, dest_fp);
if (ret != packet.size) {
av_log(NULL, AV_LOG_ERROR, "write file failed!\n");
fclose(dest_fp);
avformat_close_input(&inFormatCtx);
exit(-1);
}
}
// 释放 packet 数据
av_packet_unref(&packet);
}
编译并运行上述代码,解封装测试文件(we_are_the_world.mp4):
使用 ffplay 播放 AAC 文件:
AAC文件正常播放。
2. ASC 格式
ASC 通常 存储 在 MP4 格式中,在全局头部有一个配置,所以比 ADTS 更节省空间。ASC 格式通常用于 FLV、MOV/MP4。
参考资料
[1]. 《深入理解 FFmpeg》
[2]. 开课吧周李老师课程