简单来说,aac是一种音频编码格式,需要解码后才能用于音频输出。aac编码格式,已经是一种很常见的音频编码格式,以至于很多系统都支持aac的编解码,比如iOS上的AudioConverterRef接口、Android上的MediaCodec接口等。
但是,不要以为用了系统的接口就是用了硬件解码,因为,这个系统接口有可能最终还是使用软件解码,比如有些手机(比如小米)的MediaCodec对于acc的解码,就是软解码,用的是google提供的OMX.google.aac.decoder,不要以为用了系统接口解码的速度就飞快了,要真是硬件支持才行的。
那什么是硬解码,什么是软解码呢?很简单,如果硬件芯片专门来做解码,就是硬解码,比如使用GPU或DSP之类的模块来处理解码就是硬解码(一般不会使用CPU),这需要硬件上的支持。而软解码就是用软件来解码了,就相当你写一个程序来解码,使用CPU来做事。优缺点方面,硬解速度快功耗低但兼容性差,软解速度慢功耗高但兼容性好。
但话说回来,不是非得要硬解码的,对于音频来说,当今的手机,除非你要大量的音效运算或合成处理,否则一般的解码,用软解码就足够了,根本就不需要硬解,硬解是视频的事情。
对于aac的解码,使用FFmpeg也是一个选择,但如果只为了解码aac而用FFmpeg,就有点大材小用了,要应对比较复杂的接口调用,另外FFmpeg体积也比较大(即便裁剪后可以使FFmpeg编译出来的库小很多),那么,是否有更加简单一点的解码库可以使用呢?
这个开源库就是faad。
本文讲解使用faad,把aac音频解码成pcm数据,并以wav来封装。 pcm数据,简单来说就是未经压缩的音频数据,可以直接交给音频输出模块(比如扬声器)进行播放,而wav文件一般存放pcm数据。
(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(因为我的是macos)上运行,而且是在mac系统上编译,所以confiure时并不需要指定特定的参数。
以上命令,使用automake来生成编译脚本(makefile),如果你发现这类指令执行不了,那有可能还没有正确安装,可以查阅相关的知识,也可以参考以下的内容,这是小程在网上摘录到的内容:
====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; }
faad的调用,跟一般的c库的使用一样,先创建一个handel,然后init,之后就是反复的decode,最后是close。
以上代码保存到aac2pcm.c文件,然后,makefile编译文件可以这样写(或者直接用gcc来编译):
out=aac2pcm obj=aac2pcm.c $(out):$(obj) gcc -o $(out) $(obj) -lfaad -L./libfaad clean: rm -rf $(out) *.o
执行这个程序,部分输出:
最后的输出:
此时,在执行目录中,out.wav已经生成。那用faad跟用FFmpeg来解码aac,出来的wav有多大的差别呢?
(4)pcm数据观察
与FFmpeg的解码作一个对比。
可以使用ffmpeg命令来解码一个aac文件:
ffmpeg -i aac20s.aac out_ff.wav
可以看到,faad与FFmpeg解码出来的wav文件的大小,有一点差别:
使用Audition,来对比FFmpeg与faad解码后的pcm数据:
基本看不出差别。
至此,使用faad来解码的流程就介绍完毕了。另外,对于aac的编码,可以使用faac或fdk-aac、neroaac,或硬编等,这些小程以后再作介绍。
总结一下,本文介绍了如何通过faad来解码aac格式的音频,并保存成wav文件;对于faad的编译与调用都作了相应介绍,最后还对比了FFmpeg与faad解码出来的数据差异。