多媒体开发(12):解码aac到wav文件

简单来讲,aac是一种音频编码格式,须要解码后才能用于音频输出。aac编码格式,已是一种很常见的音频编码格式,以致于不少系统都支持aac的编解码,好比iOS上的AudioConverterRef接口、Android上的MediaCodec接口等。git

可是,不要觉得用了系统的接口就是用了硬件解码,由于,这个系统接口有可能最终仍是使用软件解码,好比有些手机(好比小米)的MediaCodec对于acc的解码,就是软解码,用的是google提供的OMX.google.aac.decoder,不要觉得用了系统接口解码的速度就飞快了,要真是硬件支持才行的。macos

那什么是硬解码,什么是软解码呢?很简单,若是硬件芯片专门来作解码,就是硬解码,好比使用GPU或DSP之类的模块来处理解码就是硬解码(通常不会使用CPU),这须要硬件上的支持。而软解码就是用软件来解码了,就至关你写一个程序来解码,使用CPU来作事。优缺点方面,硬解速度快功耗低但兼容性差,软解速度慢功耗高但兼容性好。session

但话说回来,不是非得要硬解码的,对于音频来讲,当今的手机,除非你要大量的音效运算或合成处理,不然通常的解码,用软解码就足够了,根本就不须要硬解,硬解是视频的事情。curl

对于aac的解码,使用FFmpeg也是一个选择,但若是只为了解码aac而用FFmpeg,就有点大材小用了,要应对比较复杂的接口调用,另外FFmpeg体积也比较大(即使裁剪后可使FFmpeg编译出来的库小不少),那么,是否有更加简单一点的解码库可使用呢?google

这个开源库就是faad。编码

本文讲解使用faad,把aac音频解码成pcm数据,并以wav来封装。 pcm数据,简单来讲就是未经压缩的音频数据,能够直接交给音频输出模块(好比扬声器)进行播放,而wav文件通常存放pcm数据。url

(1)下载faad

git clone git://git.code.sf.net/p/faac/faad2 faac-faad2.net

文件结构大概是这样的:
目录结构rest

这个开源项目,彷佛一直有维护与更新(请以最新的为准):
git提交code

(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库文件:
faad库文件

经过lipo来查看库文件支持的指令集:
faad库文件的指令集

(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

执行这个程序,部分输出:
运行输出1

最后的输出:
运行输出2

此时,在执行目录中,out.wav已经生成。那用faad跟用FFmpeg来解码aac,出来的wav有多大的差异呢?

(4)pcm数据观察

与FFmpeg的解码做一个对比。

可使用ffmpeg命令来解码一个aac文件:

ffmpeg -i aac20s.aac out_ff.wav

能够看到,faad与FFmpeg解码出来的wav文件的大小,有一点差异:
faad与ffmpeg转码出来的wav

使用Audition,来对比FFmpeg与faad解码后的pcm数据:
faad与ffmpeg解码的pcm对比

基本看不出差异。

至此,使用faad来解码的流程就介绍完毕了。另外,对于aac的编码,可使用faac或fdk-aac、neroaac,或硬编等,这些小程之后再做介绍。

总结一下,本文介绍了如何经过faad来解码aac格式的音频,并保存成wav文件;对于faad的编译与调用都做了相应介绍,最后还对比了FFmpeg与faad解码出来的数据差别。


well

相关文章
相关标签/搜索