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. 如果前两个字节满足同步字条件,就认为当前是 ADTS 格式。 -
2. 如果是流式输入,也就是不能 seek 的输入,它直接从当前 ADTS 头里解析出采样率和一帧长度,然后粗略算出码率,并把时长先写成 1。 -
3. 如果不是流式输入,而是普通文件,它会调用前面的 ADTS 解析逻辑把整个文件扫一遍,统计更准确的码率和时长,然后再把文件指针重置回数据起点,供后续真正解码使用。 -
4. 最后把 header_type 设为 1,表示这是 ADTS。
如果不是 ADTS,它接着判断前 4 个字节是不是 ADIF:
-
1. 如果是 ADIF,就从 ADIF 头里取出码率。 -
2. 用文件总长度除以码率,估算播放时长。 -
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, 1, sizeof(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验证
夜雨聆风