乐于分享
好东西不私藏

faad源码分析和aac转pcm实战附案例

faad源码分析和aac转pcm实战附案例

faad功能是很强大的,既可以解析mp4容器从中提取出来aac又可以封装成wav,还有解码成pcm。

本篇围绕着我需求的一个功能:aac to pcm来分析下faad这个命令行程序代码中所做的事情!

faad命令如何aac转pcm

我有一个mp4:

root@truedei-code:~/project/small-media-server/libs/faad2-2.11.2/build# ffprobe -hide_banner ~/1080P.mp4 Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '/root/1080P.mp4':  Metadata:    major_brand     : isom    minor_version   : 512    compatible_brands: isomiso2avc1mp41    encoder         : Lavf58.29.100    description     : Packed by Bilibili XCoder v2.0.2  Duration: 00:03:46.53, start: 0.000000, bitrate: 3087 kb/s  Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 2951 kb/s, 30 fps, 30 tbr, 16k tbn, 60 tbc (default)    Metadata:      handler_name    : VideoHandler      vendor_id       : [0][0][0][0]  Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default)    Metadata:      handler_name    : SoundHandler      vendor_id       : [0][0][0][0]root@truedei-code:~/project/small-media-server/libs/faad2-2.11.2/build# 

可以看到mp4中包含一路aac音频,参数如下:

Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default)    Metadata:      handler_name    : SoundHandler      vendor_id       : [0][0][0][0]

从mp4中提取aac

faad -a out.aac  ~/1080P.mp4

可以查看是否成功:

root@truedei-code:~/project/small-media-server/libs/faad2-2.11.2/build# ./faad -i out.aac  *********** Ahead Software MPEG-4 AAC Decoder V2.11.2 ****************** Build: May  4 2026 Copyright 2002-2004: Ahead Software AG http://www.audiocoding.com bug tracking: https://sourceforge.net/p/faac/bugs/ Floating point version This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License. **************************************************************************out.aac file info:ADTS, 226.534 sec, 130 kbps, 44100 Hzroot@truedei-code:~/project/small-media-server/libs/faad2-2.11.2/build# 

从aac转成pcm

faad -f 2 -o out.pcm out.aac

可以用ffplay判断是否成功:如果能播放出来,说明是成功的

ffplay -f s16le -ar 44100  -i out.pcm

代码分析faad

围绕着aac to pcm命令:

faad -f 2 -o out.pcm out.aac

faad程序源码位置:faad2-2.11.2/frontend

../frontend/├── audio.c├── audio.h├── faad.man├── getopt.c├── getopt.h├── main.c├── mp4read.c├── mp4read.h├── unicode_support.c└── unicode_support.h0 directories, 10 files

faad程序的入口:faad2-2.11.2/frontend/main.c

可以看到函数也就如下这些,比较简练,其余的都被封装在audio.c,getopt.c,mp4read.c等

先看main函数:

main函数中做了平台差异化开发,如果是windows则设置字符编码,防止乱码,然后调用faad_main函数!

先找到-f 2

case 'f':if (optarg){  char dr[10];  if (sscanf(optarg, "%s", dr) < 1)  {      format = 1;  } else {      format = atoi(dr);      if ((format < 1) || (format > 2))          showHelp = 1;  }}break;

-f可以看到是给format赋值

核心分支是这里:

判断是否为mp4文件,是的话就走decodeMP4file,否则就走decodeAACfile

if (mp4file){    result = decodeMP4file(aacFileName, audioFileName, adtsFileName, writeToStdio,        outputFormat, format, downMatrix, noGapless, infoOnly, adts_out, &length, seekTo);else {if (readFromStdin == 1) {ungetc(header[7],hMP4File);ungetc(header[6],hMP4File);ungetc(header[5],hMP4File);ungetc(header[4],hMP4File);ungetc(header[3],hMP4File);ungetc(header[2],hMP4File);ungetc(header[1],hMP4File);ungetc(header[0],hMP4File);    }    result = decodeAACfile(aacFileName, audioFileName, adtsFileName, writeToStdio,        def_srate, object_type, outputFormat, format, downMatrix, infoOnly, adts_out,        old_format, &length);}

继续跟踪一下,可以在这个函数打个断点看下:

root@truedei-code:~/project/small-media-server/libs/faad2-2.11.2/build# gdb -args ./faad -f 2 -o out.pcm out.aac GNU gdb (Ubuntu 12.1-0ubuntu1~22.04.2) 12.1Copyright (C) 2022 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.Type "show copying" and "show warranty" for details.This GDB was configured as "x86_64-linux-gnu".Type "show configuration" for configuration details.For bug reporting instructions, please see:<https://www.gnu.org/software/gdb/bugs/>.Find the GDB manual and other documentation resources online at:    <http://www.gnu.org/software/gdb/documentation/>.For help, type "help".Type "apropos word" to search for commands related to "word"...Reading symbols from ./faad...(gdb) b decodeAACfileBreakpoint 1 at 0x5145: file /usr/include/x86_64-linux-gnu/bits/string_fortified.h, line 59.(gdb) rStarting program: /root/project/small-media-server/libs/faad2-2.11.2/build/faad -f 2 -o out.pcm out.aac[Thread debugging using libthread_db enabled]Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". *********** Ahead Software MPEG-4 AAC Decoder V2.11.2 ****************** Build: May  4 2026 Copyright 2002-2004: Ahead Software AG http://www.audiocoding.com bug tracking: https://sourceforge.net/p/faac/bugs/ Floating point version This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License. **************************************************************************Breakpoint 1, decodeAACfile (song_length=0x7fffffffdaf8, old_format=0 '\000', adts_out=0, infoOnly=0, downMatrix=0 '\000', fileType=2, outputFormat=1 '\001', object_type=2 '\002', def_srate=0, to_stdout=0, adts_fn=0x0, sndfile=0x5555555622a0 "out.pcm", aacfile=0x5555555622c0 "out.aac") at /root/project/small-media-server/libs/faad2-2.11.2/frontend/main.c:477477         memset(&b, 0, sizeof(aac_buffer));(gdb) 

命令:

faad -f 2 -o out.pcm out.aac

传参:

decodeAACfile (song_length=0x7fffffffdaf8               old_format=0'\000'               adts_out=0               infoOnly=0               downMatrix=0'\000'               fileType=2               outputFormat=1'\001'               object_type=2'\002'               def_srate=0               to_stdout=0               adts_fn=0x0               sndfile=0x5555555622a0"out.pcm"               aacfile=0x5555555622c0"out.aac"at /root/project/small-media-server/libs/faad2-2.11.2/frontend/main.c:477

format传给了decodeAACfile函数的第8个参数object_type

函数内有几个核心的变量:

NeAACDecHandle hDecoder;NeAACDecFrameInfo frameInfo;NeAACDecConfigurationPtr config;

核心代码:

1、创建aac decoder解码器

hDecoder = NeAACDecOpen();

2、获取解码器默认配置并赋值

/* Set the default object type and samplerate *//* This is useful for RAW AAC files */config = NeAACDecGetCurrentConfiguration(hDecoder);if (def_srate)    config->defSampleRate = def_srate;config->defObjectType = object_type;config->outputFormat = outputFormat;config->downMatrix = downMatrix;config->useOldADTSFormat = old_format;//config->dontUpSampleImplicitSBR = 1;NeAACDecSetConfiguration(hDecoder, config);

3、

/* get AAC infos for printing */header_type = 0;if (streaminput == 1)lookforheader(&b);if ((b.buffer[0] == 0xFF) && ((b.buffer[1] & 0xF6) == 0xF0)){if (streaminput == 1)    {int/*frames,*/ frame_length;float frames_per_sec, bytes_per_frame;        channels = 2;        samplerate = adts_sample_rates[(b.buffer[2]&0x3c)>>2];        frame_length = ((((unsignedint)b.buffer[3] & 0x3)) << 11)            | (((unsignedint)b.buffer[4]) << 3) | (b.buffer[5] >> 5);        frames_per_sec = (float)samplerate/1024.0f;        bytes_per_frame = (float)frame_length/(float)(1000);        bitrate = (int)(8. * bytes_per_frame * frames_per_sec + 0.5);        length = 1;faad_fprintf(stderr, "Streamed input format  samplerate %d channels %d.\n", samplerate, channels);    } else {adts_parse(&b, &bitrate, &length);fseek(b.infile, tagsize, SEEK_SET);        bread = (unsignedlong)fread(b.buffer, 1, FAAD_MIN_STREAMSIZE*MAX_CHANNELS, b.infile);if (bread != FAAD_MIN_STREAMSIZE*MAX_CHANNELS)            b.at_eof = 1;else            b.at_eof = 0;        b.bytes_into_buffer = bread;        b.bytes_consumed = 0;        b.file_offset = tagsize;    }    header_type = 1;}elseif (memcmp(b.buffer, "ADIF"4) == 0){int skip_size = (b.buffer[4] & 0x80) ? 9 : 0;    bitrate = ((unsignedint)(b.buffer[4 + skip_size] & 0x0F)<<19) |        ((unsignedint)b.buffer[5 + skip_size]<<11) |        ((unsignedint)b.buffer[6 + skip_size]<<3) |        ((unsignedint)b.buffer[7 + skip_size] & 0xE0);    length = (float)fileread;if (length != 0)    {        length = ((float)length*8.f)/((float)bitrate) + 0.5f;    }    bitrate = (int)((float)bitrate/1000.0f + 0.5f);    header_type = 2;}*song_length = length;

先看缓冲区开头是不是 ADTS 头:

  1. 1. 如果前两个字节满足同步字条件,就认为当前是 ADTS 格式。
  2. 2. 如果是流式输入,也就是不能 seek 的输入,它直接从当前 ADTS 头里解析出采样率和一帧长度,然后粗略算出码率,并把时长先写成 1。
  3. 3. 如果不是流式输入,而是普通文件,它会调用前面的 ADTS 解析逻辑把整个文件扫一遍,统计更准确的码率和时长,然后再把文件指针重置回数据起点,供后续真正解码使用。
  4. 4. 最后把 header_type 设为 1,表示这是 ADTS。

如果不是 ADTS,它接着判断前 4 个字节是不是 ADIF:

  1. 1. 如果是 ADIF,就从 ADIF 头里取出码率。
  2. 2. 用文件总长度除以码率,估算播放时长。
  3. 3. 把 header_type 设为 2,表示这是 ADIF。

然后*song_length = length;是传递出去播放时长

接下来的核心代码是NeAACDecInit正在初始AAC解码器和启动,并把缓冲数据给到AAC解码器

fill_buffer(&b);bused = NeAACDecInit(hDecoder, b.buffer, b.bytes_into_buffer, &samplerate, &channels);if (bused < 0){/* If some error initializing occured, skip the file */faad_fprintf(stderr, "Error initializing decoder library.\n");if (b.buffer)free(b.buffer);NeAACDecClose(hDecoder);if (b.infile != stdin)fclose(b.infile);return1;}advance_buffer(&b, bused);fill_buffer(&b);

fill_buffer函数封装的非常好,作用时前面消费的一些数据,在这个函数内可以推进一下;

advance_buffer函数负责把用过的数据移出

这俩函数不是重点,是faad这个命令行程序开发的辅助函数而已,可以自己学习一下,重点是了解faad2库如何解码aac的流程。

接着是:

// Override the logic of skipping 0-th output frame.NeAACDecPostSeekReset(hDecoder, 1);

没有任何注释,说实话不结合上下文的话比较难懂,源码这样的:

就是给postSeekResetFlag和frame赋值为1

voidNeAACDecPostSeekReset(NeAACDecHandle hpDecoder, long frame){    NeAACDecStruct* hDecoder = (NeAACDecStruct*)hpDecoder;if (hDecoder)    {        hDecoder->postSeekResetFlag = 1;if (frame != -1)            hDecoder->frame = frame;    }}

只看这个简单的注释:

这里把 frame 预先设成 1,注释里说的 Override the logic of skipping 0-th output frame,就是为了绕过这套“首帧静音/首帧跳过”的默认行为,让后面的第一次解码就尽量产出有效 PCM,而不是被当成第 0 帧吞掉。

然后while中调用解码:

do{    sample_buffer = NeAACDecDecode(hDecoder, &frameInfo,        b.buffer, b.bytes_into_buffer);    ....    ....    ....    ....}while (sample_buffer != NULL);

最后是关闭aac解码器,清理资源

NeAACDecClose(hDecoder);

梳理整个解码aac的代码流程

1、定义解码器变量

NeAACDecHandle hDecoder;NeAACDecFrameInfo frameInfo;NeAACDecConfigurationPtr config;

2、创建&打开解码器

hDecoder = NeAACDecOpen();

3、设置解码器参数

/* Set the default object type and samplerate *//* This is useful for RAW AAC files */config = NeAACDecGetCurrentConfiguration(hDecoder);if (def_srate)    config->defSampleRate = def_srate;config->defObjectType = object_type;config->outputFormat = outputFormat;config->downMatrix = downMatrix;config->useOldADTSFormat = old_format;//config->dontUpSampleImplicitSBR = 1;NeAACDecSetConfiguration(hDecoder, config);

4、初始化解码器

bused = NeAACDecInit(hDecoder, b.buffer, b.bytes_into_buffer, &samplerate, &channels);

5、解码

sample_buffer = NeAACDecDecode(hDecoder, &frameInfo,            b.buffer, b.bytes_into_buffer);

6、关闭解码器清理资源

NeAACDecClose(hDecoder);

最小化aac转pcm代码

#include<cstdint>#include<cstdio>#include<cstdlib>#include<cstring>#include<iostream>#include<string>#include<vector>#include"faad2/faad.h"namespace {boolread_adts_frame(FILE* input, std::vector<unsignedchar>& frame){unsignedchar header[7] = {};constsize_t header_bytes = fread(header, 1sizeof(header), input);if (header_bytes == 0)    {returnfalse;    }if (header_bytes != sizeof(header))    {        std::cerr << "Unexpected EOF while reading ADTS header" << std::endl;returnfalse;    }if (!(header[0] == 0xFF && (header[1] & 0xF6) == 0xF0))    {        std::cerr << "Input is not a valid ADTS AAC stream" << std::endl;returnfalse;    }constsize_t frame_length =        ((static_cast<size_t>(header[3] & 0x03) << 11) |         (static_cast<size_t>(header[4]) << 3) |         (static_cast<size_t>(header[5]) >> 5));if (frame_length < sizeof(header))    {        std::cerr << "Invalid ADTS frame length: " << frame_length << std::endl;returnfalse;    }    frame.resize(frame_length);memcpy(frame.data(), header, sizeof(header));constsize_t payload_length = frame_length - sizeof(header);if (payload_length == 0)    {returntrue;    }constsize_t payload_bytes = fread(frame.data() + sizeof(header), 1, payload_length, input);if (payload_bytes != payload_length)    {        std::cerr << "Unexpected EOF while reading ADTS frame payload" << std::endl;returnfalse;    }returntrue;}// namespacevoidaac_decoder_test(){    std::cout << "Run aac_decoder_test" << std::endl;char *faad_id_string = nullptr;char *faad_copyright_string = nullptr;NeAACDecGetVersion(&faad_id_string, &faad_copyright_string);    std::cout << "faad_id_string: " << faad_id_string << std::endl;    std::cout << "faad_copyright_string: " << faad_copyright_string << std::endl;    std::string input_aac_file = "../libs/faad2-2.11.2/build/out.aac";    std::string output_pcm_file = "test.pcm";    FILE *f_in = fopen(input_aac_file.c_str(), "rb");if (!f_in) {        std::cerr << "Failed to open input AAC file: " << input_aac_file << std::endl;return;    }    FILE *f_out = fopen(output_pcm_file.c_str(), "wb");if (!f_out) {        std::cerr << "Failed to open output PCM file: " << output_pcm_file << std::endl;fclose(f_in);return;    }// 1. Open the decoder    NeAACDecHandle decoder = NeAACDecOpen();// 2. Get the current configuration    NeAACDecConfigurationPtr config = NeAACDecGetCurrentConfiguration(decoder);    std::cout << "Default output format: " << static_cast<int>(config->outputFormat) << std::endl;// 3. Set the configuration (optional, using default here)    config->defObjectType = LC; // Set to Low Complexity AAC// config->defSampleRate = 44100; // Set to 44.1 k // 是否需要强制定采样率,通常不需要,解码器会根据输入文件自动检测采样率    config->outputFormat = FAAD_FMT_16BIT;    config->downMatrix = 0// 是否需要5.1转2.0,通常不需要,除非输入文件是5.1声道的    config->useOldADTSFormat = 0// 是否使用旧的ADTS格式// config->dontUpSampleImplicitSBR = 0; // 是否禁止隐式SBR的上采样// 4. Apply the configurationNeAACDecSetConfiguration(decoder, config);// 5. 初始化解码器,并在循环里按 ADTS 帧逐帧解码// 读取首帧aac数据喂给aac解码器初始化,后续循环里继续读取帧数据解码unsignedlong samplerate = 0;unsignedchar channels = 0;    std::vector<unsignedchar> frame_buffer;if (!read_adts_frame(f_in, frame_buffer)) {        std::cerr << "Failed to read the first AAC frame from: " << input_aac_file << std::endl;NeAACDecClose(decoder);fclose(f_out);fclose(f_in);return;    }// 注意:NeAACDecInit 的返回值是已消耗的字节数,如果返回值为负数则表示初始化失败constlong bused = NeAACDecInit(decoder, frame_buffer.data(),static_cast<unsignedlong>(frame_buffer.size()), &samplerate, &channels);if (bused < 0) {        std::cerr << "NeAACDecInit failed" << std::endl;NeAACDecClose(decoder);fclose(f_out);fclose(f_in);return;    }    std::cout << "bused=" << bused << std::endl;NeAACDecPostSeekReset(decoder, 1);bool should_continue = true;// 是否需要处理首帧数据中未被 NeAACDecInit 消耗的部分?// 如果 bused < frame_buffer.size(),说明首帧数据中还有未被解码器消费的字节,这些字节应该在后续的解码循环中继续处理bool has_pending_bytes = static_cast<size_t>(bused) < frame_buffer.size();while (should_continue)    {constunsignedchar* decode_data = nullptr;unsignedlong decode_size = 0;// 如果首帧数据中有未被 NeAACDecInit 消耗的字节,优先处理这些字节;if (has_pending_bytes)        {            decode_data = frame_buffer.data() + bused;            decode_size = static_cast<unsignedlong>(frame_buffer.size() - static_cast<size_t>(bused));            has_pending_bytes = false;        }// 否则继续从文件中读取下一帧数据进行解码else        {if (!read_adts_frame(f_in, frame_buffer))            {break;            }            decode_data = frame_buffer.data();            decode_size = static_cast<unsignedlong>(frame_buffer.size());        }        NeAACDecFrameInfo frame_info = {};void* sample_buffer = NeAACDecDecode(decoder, &frame_info,const_cast<unsignedchar*>(decode_data), decode_size);if (frame_info.error > 0)        {            std::cerr << "Decode error: " << NeAACDecGetErrorMessage(frame_info.error) << std::endl;break;        }if (frame_info.samples > 0 && sample_buffer)        {constsize_t bytes_to_write = frame_info.samples * sizeof(int16_t);fwrite(sample_buffer, 1, bytes_to_write, f_out);        }if (frame_info.bytesconsumed == 0)        {            should_continue = false;        }    }    std::cout << "Decoded AAC to PCM: samplerate=" << samplerate              << ", channels=" << static_cast<int>(channels)              << ", output=" << output_pcm_file << std::endl;// 销毁资源NeAACDecClose(decoder);fclose(f_out);fclose(f_in);}intmain(int argc, charconst *argv[]){aac_decoder_test();return0;}

运行

root@truedei-code:~/project/small-media-server/build# ./bin/test_aac_decoder Run aac_decoder_testfaad_id_string: 2.11.2faad_copyright_string:  Copyright 2002-2004: Ahead Software AG http://www.audiocoding.com bug tracking: https://sourceforge.net/p/faac/bugs/Default output format: 1bused=0Decoded AAC to PCM: samplerate=44100, channels=2, output=test.pcmroot@truedei-code:~/project/small-media-server/build# 

可以用ffplay验证