1. 程式人生 > >解碼aac,並生成wav文件

解碼aac,並生成wav文件

fadd 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,並生成wav文件