音频编码格式介绍-AAC
Posted 音视频开发老马
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了音频编码格式介绍-AAC相关的知识,希望对你有一定的参考价值。
目录
-
概述
-
帧格式
-
算法简介
-
开源的软件
-
AAC和MP3的关键性不同
-
参考阅读
1. 概述 AAC(Advanced Audio Coding),被认为是MP3的继任者,相对MP3有更高的压缩效率。由Fraunhofer IIS、杜比实验室、AT&T、Sony(索尼)等公司共同开发。1997年由MPEG正式宣布为国际标准,为MPEG-2标准的第7部分-ISO/IEC 13818-7:1997。MPEG-4标准中,AAC音频流部分在ISO/IEC 14496-3 (subpart 4)中规定。 AAC被iPhone、iTunes以及大多数便携式设备所使用。 AAC有很多的选项和profiles。 2. 帧格式 AAC音频格式有:
-
ADIF(Audio Data Interchage Format),音频数据交换格式:只有一个统一的头,必须得到所有数据后解码,适用于本地文件。
-
ADTS(Audio Data Transport Stream),音视数据传输流:每一帧都有头信息,任意帧解码,适用于传输流。
下面主要介绍ADTS。 ADTS的组成单元是ADTS Frame。
-
ADTS Frame由ADTS_Header和AAC ES组成。
-
ADTS_Header包含采样率、声道数、帧长度的信息。
-
ADTS头信息的长度是7个字节或9字节(有CRC的情况)。
ADTS_Header的可以分为以下三部分:
-
adts_fixed_header:每一帧的内容是不变的。
-
adts_variable_header:每一帧的内容是存在变化的。
-
crc:16bits,protection_absent字段为0时存在。
adts_fixed_header:
在MPEG-2 AAC中定义了3种profile:
-
MPEG-2 AAC Main
-
MPEG-2 AAC LC (Low Complexity)
-
MPEG-2 AAC SSR (Scalable Sampling Rate)
在MPEG-4 AAC中定义了6种profile:
-
MPEG-4 AAC Main
-
MPEG-4 AAC LC (Low Complexity)
-
MPEG-4 AAC SSR (Scalable Sample Rate)
-
MPEG-4 AAC LTP (Long Term Predicition)
-
MPEG-4 AAC LD (Low Delay)
-
MPEG-4 AAC HE (High Efficiency) AACPlusV1/V2(3GPP)
adts_variable_header:
【免费分享】文章最后领取音视频资料
AAC ES部分说明
-
一个frame的原始数据包含1024个样本时间段的音频数据。
ffmpeg中添加ADTS头的代码,可以很清晰的了解ADTS头的结构:
static int adts_write_frame_header(ADTSContext *ctx,
uint8_t *buf, int size, int pce_size)
PutBitContext pb;
unsigned full_frame_size = (unsigned)ADTS_HEADER_SIZE + size + pce_size;
if (full_frame_size > ADTS_MAX_FRAME_BYTES)
av_log(NULL, AV_LOG_ERROR, "ADTS frame size too large: %u (max %d)\\n",
full_frame_size, ADTS_MAX_FRAME_BYTES);
return AVERROR_INVALIDDATA;
init_put_bits(&pb, buf, ADTS_HEADER_SIZE);
/* adts_fixed_header */
put_bits(&pb, 12, 0xfff); /* syncword */
put_bits(&pb, 1, 0); /* ID */
put_bits(&pb, 2, 0); /* layer */
put_bits(&pb, 1, 1); /* protection_absent */
put_bits(&pb, 2, ctx->objecttype); /* profile_objecttype */
put_bits(&pb, 4, ctx->sample_rate_index);
put_bits(&pb, 1, 0); /* private_bit */
put_bits(&pb, 3, ctx->channel_conf); /* channel_configuration */
put_bits(&pb, 1, 0); /* original_copy */
put_bits(&pb, 1, 0); /* home */
/* adts_variable_header */
put_bits(&pb, 1, 0); /* copyright_identification_bit */
put_bits(&pb, 1, 0); /* copyright_identification_start */
put_bits(&pb, 13, full_frame_size); /* aac_frame_length */
put_bits(&pb, 11, 0x7ff); /* adts_buffer_fullness */
put_bits(&pb, 2, 0); /* number_of_raw_data_blocks_in_frame */
flush_put_bits(&pb);
return 0;
3. 算法简介 AAC是一种宽带音频编码算法,它利用两种主要的编码策略来大幅减少表示高质量数字音频所需的数据量:
-
丢弃在感知上不相关的信号分量。
-
消除了编码音频信号中的冗余。
实际的编码过程包括以下步骤:
-
使用前向修改的离散余弦变换(MDCT)将信号从时域转换到频域。这是通过使用滤波器组来完成的,这些滤波器组采用适当数量的时间采样并将其转换为频率采样。
-
基于心理声学模型量化频域信号并对其进行编码。
-
添加内部纠错码。
-
存储或传输信号。
-
为了防止损坏的样本,将Luhn mod N算法的现代实现应用于每个帧。
算法部分更多介绍可参考[5]。 4. 开源软件
5. AAC和MP3的关键性不同 AAC是在MP3基础上开发出来的,所以两者的编码系统有一些相同之处。但是对比一下两者的编码流程图,你会发现AAC的编码工序更为复杂。
-
滤波器组(Filter bank):
-
时域噪音修整(Temporal Noise Shaping,TNS):这项神奇的技术可以通过在频率域上的预测,来修整时域上的量化噪音的分布。在一些特殊的语音和剧烈变化信号的量化上,TNS技术对音质的提高贡献巨大!
-
预测(Prediction):对音频信号进行预测可以减少重复冗余信号的处理,提高效率。
-
量化(Quantization):AAC的量化过程是使用两个巢状循环进行反复运算。通过对量化分析的良好控制,比特率能够被更高效地利用。
-
比特流格式(Bit-stream format):在AAC中,信息的传输都要经过熵编码,以保证冗余尽可能少。此外AAC拥有一个弹性的比特流结构,使得编码效率进一步提高。
-
长时期预测(Long Term Prediction,LTP):这是一个MPEG-4 AAC中才有的工具,它用来减少连续两个编码音框之间的信号冗余,对于处理低码率的语音非常有效。
-
知觉噪音代替(Perceptual Noise Substitution,PNS):这也是MPEG-4 AAC中才有的工具,当编码器发现类似噪音的信号时,并不对其进行量化,而是作个标记就忽略过去,当解码时再还原出来,这样就提高了效率。
6. 参考
解码aac,并生成wav文件
小程在讲多媒体的编码格式时,详细介绍过pcm跟aac等概念。简单来说,pcm是没有压缩的数字信号,可以直接用于音频输出,而aac则是一种音频编码格式,需要解码后才能用于音频输出。
aac编码格式,已经是一种很常见的音频编码格式,硬件设备(比如电脑芯片、手机、其它终端设备)都集成了aac的解码功能,而且有些系统还提供了调用硬件解码的接口,比如iOS上的AudioConverterRef接口、Android上的MediaCodec接口等。
从性能的角度,使用硬件解码是最佳选择,但如果从通用的角度,使用软件解码(相当于写个程序)是最佳的选择。这一次,小程介绍的是音频的软解码。
本文讲解使用fadd,把aac音频解码成pcm数据,并以wav来封装。
对于aac的软解码,使用FFmpeg也是一个选择,但如果只为了解码aac而用FFmpeg,就有点大材小用了,而且要应对比较复杂的接口调用,另外FFmpeg体积也比较大(即便裁剪后可以使FFmpeg编译出来的库小很多),那么,是否有更加简单一点的解码库可以使用呢?
这个开源库就是faad。
接下来,小程介绍使用faad来解码aac,并生成wav文件的流程。
(1)下载faad
git clone git://git.code.sf.net/p/faac/faad2 faac-faad2
文件结构大概是这样的:
这个开源项目,似乎一直有维护与更新:
(2)编译faad
执行以下指令,生成configure配置文件,以及makefile编译脚本,然后,再make出faad的库文件。
aclocal
autoconf
autoheader
libtoolize --force
automake --add-missing
./configure
make
由于只考虑在macos上运行,而且是在mac系统上编译,所以confiure时并不需要指定特定的参数。
以上命令,使用automake来生成编译脚本(makefile),如果读者发现这类指令执行不了,那有可能还没有正确安装,可以查阅相关的知识,也可以参考以下的内容,这是小程在网上摘录到的内容(小程使用的是macos):
====install autoconf and automake(摘录)
curl -O http://mirrors.kernel.org/gnu/m4/m4-1.4.13.tar.gz
tar -xzvf m4-1.4.13.tar.gz
cd m4-1.4.13
./configure --prefix=/usr/local
make
sudo make install
cd ..
curl -O http://mirrors.kernel.org/gnu/autoconf/autoconf-2.65.tar.gz
tar -xzvf autoconf-2.65.tar.gz
cd autoconf-2.65
./configure --prefix=/usr/local # ironic, isn‘t it?
make
sudo make install
cd ..
# here you might want to restart your terminal session, to ensure the new autoconf is picked up and used in the rest of the script
curl -O http://mirrors.kernel.org/gnu/automake/automake-1.11.tar.gz
tar xzvf automake-1.11.tar.gz
cd automake-1.11
./configure --prefix=/usr/local
make
sudo make install
cd ..
curl -O http://mirrors.kernel.org/gnu/libtool/libtool-2.2.6b.tar.gz
tar xzvf libtool-2.2.6b.tar.gz
cd libtool-2.2.6b
./configure --prefix=/usr/local
make
sudo make install
最终,生成faad库文件:
通过lipo来查看库文件支持的指令集:
(3)调用faad
这里演示一下,把一个aac文件解码成pcm数据,并用wav容器来封装。
demo的文件结构:
demo的代码:
#include "libfaad/include/faad.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct WavFileHeader
{
char id[4]; // should always contain "RIFF"
int totallength; // total file length minus 8
char wavefmt[8]; // should be "WAVEfmt "
int format; // 16 for PCM format
short pcm; // 1 for PCM format
short channels; // channels
int frequency; // sampling frequency
int bytes_per_second;
short bytes_by_capture;
short bits_per_sample;
char data[4]; // should always contain "data"
int bytes_in_data;
};
void write_wav_header(FILE* file, int totalsamcnt_per_channel, int samplerate, int channels){
struct WavFileHeader filler;
strcpy(filler.id, "RIFF");
filler.bits_per_sample = 16;
filler.totallength = (totalsamcnt_per_channel * channels * filler.bits_per_sample/8) + sizeof(filler) - 8; //81956
strcpy(filler.wavefmt, "WAVEfmt ");
filler.format = 16;
filler.pcm = 1;
filler.channels = channels;
filler.frequency = samplerate;
filler.bytes_per_second = filler.channels * filler.frequency * filler.bits_per_sample/8;
filler.bytes_by_capture = filler.channels*filler.bits_per_sample/8;
filler.bytes_in_data = totalsamcnt_per_channel * filler.channels * filler.bits_per_sample/8;
strcpy(filler.data, "data");
fwrite(&filler, 1, sizeof(filler), file);
}
int main(int argc, char *argv[])
{
printf("hello faad\n");
NeAACDecHandle faadhandle = NeAACDecOpen();
if (faadhandle) {
printf("aacopen ok\n");
const char* aacfile = "aac20s.aac";
FILE* file = fopen(aacfile, "rb");
if (file) {
printf("fopen aac ok\n");
fseek(file, 0, SEEK_END);
long filelen = ftell(file);
fseek(file, 0, SEEK_SET);
unsigned char* filebuf = (unsigned char*)malloc(filelen);
int len = fread(filebuf, 1, filelen, file);
fclose(file);
unsigned long samplerate = 0;
unsigned char channel = 0;
int ret = NeAACDecInit(faadhandle, filebuf, len, &samplerate, &channel);
if (ret >= 0) {
printf("aacinit ok: sam=%lu, chn=%d\n", samplerate, channel);
NeAACDecFrameInfo frameinfo;
unsigned char* curbyte = filebuf;
unsigned long leftsize = len;
const char* wavename = "out.wav";
FILE* wavfile = fopen(wavename, "wb");
if (wavfile) {
int wavheadsize = sizeof(struct WavFileHeader);
fseek(wavfile, wavheadsize, SEEK_SET);
int totalsmp_per_chl = 0;
void* out = NULL;
while (out = NeAACDecDecode(faadhandle, &frameinfo, curbyte, leftsize)) {
printf("decode one frame ok: sam:%ld, chn=%d, samplecount=%ld, obj_type=%d, header_type=%d, consumed=%ld\n",
frameinfo.samplerate, frameinfo.channels, frameinfo.samples, frameinfo.object_type,
frameinfo.header_type, frameinfo.bytesconsumed);
curbyte += frameinfo.bytesconsumed;
leftsize -= frameinfo.bytesconsumed;
fwrite(out, 1, frameinfo.samples*2, wavfile); // frameinfo.samples是所有声道数的样本总和;16bit位深
totalsmp_per_chl += frameinfo.samples / frameinfo.channels;
}
printf("aac decode done, totalsmp_per_chl=%d\n", totalsmp_per_chl);
fseek(wavfile, 0, SEEK_SET);
write_wav_header(wavfile, totalsmp_per_chl, (int)samplerate, (int)channel);
fclose(wavfile);
}
}
free(filebuf);
}
NeAACDecClose(faadhandle);
}
return 0;
}
以上代码保存到aac2pcm.c文件,然后,makefile编译文件可以这样写(或者直接用gcc来编译):
out=aac2pcm
obj=aac2pcm.c
$(out):$(obj)
gcc -o $(out) $(obj) -lfaad -L./libfaad
clean:
rm -rf $(out) *.o
执行这个程序,部分输出:
最后的输出:
(4)pcm数据观察
与FFmpeg的解码作一个对比。
可以使用ffmpeg命令来解码一个aac文件:
ffmpeg -i aac20s.aac out_ff.wav
可以看到,faad与FFmpeg解码出来的wav文件的大小,有一点差别:
使用Audition,来对比FFmpeg与faad解码后的pcm数据:
基本看不出差别。
至此,使用fadd来解码的流程就介绍完毕了。另外,对于aac的编码,可以使用faac或fdk-aac、neroaac,或硬编等,这些小程以后再作介绍。
总结一下,本文介绍了如何通过fadd来解码aac格式的音频文件,并生成wav文件。其中,介绍了faad的编译,操作的难度系数为2(考虑到有automake的使用),然后介绍了写代码调用fadd的接口,并把解码后的数据,保存到wav的文件容器中,操作的难度系数为3。
以上是关于音频编码格式介绍-AAC的主要内容,如果未能解决你的问题,请参考以下文章