# faac内存优化 **Repository Path**: dma/faac-memory-optimization ## Basic Information - **Project Name**: faac内存优化 - **Description**: faac内存开销较大,为方便嵌入式设备使用进行优化,在github上提了issues但是没人理我,所以就搞一份代码自己玩吧。 基于faac_1_30版本,原工程https://github.com/knik0/faac - **Primary Language**: C - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 11 - **Forks**: 9 - **Created**: 2021-09-05 - **Last Updated**: 2025-05-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 说明 faac内存开销较大,为方便嵌入式设备使用进行优化,在github上提了issues但是没人理我,所以就搞一份代码自己玩吧。 基于faac_1_30版本,原工程 https://github.com/knik0/faac # 文件说明 * faac-1_30.zip 为 faac 源码 * 为了方便我个人使用,删除了 faac 源码中我用不到的文件,只保留 libfaac 目录下的必要文件和 inlcude 目录 * 增加 CMakeLists.txt 编译脚本 # 内存优化的内容 内存优化测试文件的格式为 PCM int16 小端 双声道 44100Hz ## 1.1 优化前 使用 valgrind 检查内存,结果如下,可以看到默认配置的内存开销约为 11.5 MB ![](./doc/img/mem01.png) ## 1.2 修改默认最大声道数 修改 `libfaac\coder.h` 一般来说,双声道就够用了,如果有5.1声道之类的特殊需求可以自行修改 修改前 `#define MAX_CHANNELS 64` 修改后 `#define MAX_CHANNELS 2` 内存统计如下,约 560 KB ![](./doc/img/mem02.png) ## 1.3 删除 bwpInfo 修改 `libfaac\coder.h` 在 `faacEncOpen()` 中会创建 `faacEncStruct* hEncoder;` 这个句柄 在 `faacEncStruct.coderInfo` 中的 `bwpInfo` 代码中没有用到,不知道作者为什么没有删除,意义不明。 修改前 `BwpInfo bwpInfo;` 修改后 `// BwpInfo bwpInfo;` 内存统计如下,约 240 KB ![](./doc/img/mem03.png) ## 1.4 删除无效代码 修改 `libfaac\frame.c ` `faacEncEncode()` 的代码中有这样一段 ``` hEncoder->sampleBuff[channel] = hEncoder->nextSampleBuff[channel]; hEncoder->nextSampleBuff[channel] = hEncoder->next2SampleBuff[channel]; hEncoder->next2SampleBuff[channel] = hEncoder->next3SampleBuff[channel]; hEncoder->next3SampleBuff[channel] = tmp; ``` 申请了4个 `sampleBuff`,这个函数每调用一次会依次交换这4个 `buffer`,实际代码中只用到了 `sampleBuff` 和 `next3SampleBuff`,不明白作者为什么这样写,也可能是忘了删,这里可以修改为 ``` hEncoder->sampleBuff[channel] = hEncoder->next3SampleBuff[channel]; hEncoder->next3SampleBuff[channel] = tmp; ``` 以下几处记得也要一起修改 f`aacEncOpen()` 中修改为 ``` // hEncoder->nextSampleBuff[channel] = NULL; // hEncoder->next2SampleBuff[channel] = NULL; ``` `faacEncClose()` 中修改为 ``` // if (hEncoder->nextSampleBuff[channel]) // FreeMemory(hEncoder->nextSampleBuff[channel]); // if (hEncoder->next2SampleBuff[channel]) // FreeMemory (hEncoder->next2SampleBuff[channel]); ``` `faacEncStruct` 中修改为 ``` // double *nextSampleBuff[MAX_CHANNELS]; // double *next2SampleBuff[MAX_CHANNELS]; ``` 这样每声道可以节约 16KB 内存 内存统计如下,约 210 KB ![](./doc/img/mem04.png) ## 1.5 优化数据结构 修改 `libfaac\coder.h` 这有个前提条件,就是不启用 `DRM`,这个宏在 `libfaac\coder.h`,默认就是关闭的 ```//#define DRM``` 接下来再看代码,`CoderInfo` 中有这样一个成员 ``` struct { int data; int len; } s[DATASIZE]; ``` 它用来进行哈夫曼编码,查看源码可知,这个结构体只在 `huffcode()` 函数中赋值,里面的数据来自于哈夫曼编码表,使用的码表为 `book01` 到 `book11`,没有用到 `book12`,这11个码表的成员原型如下 ``` typedef struct { const uint16_t len; const uint16_t data; } hcode16_t; ``` 因此可以把 `int` 改成 `short`,每声道可以节约6KB内存 *备注:这样修改其实是错的,但是完全可以正常使用,具体原因见下文* 内存统计如下,约 200 KB ![](./doc/img/mem05.png) ## 1.6 禁用 TNS 修改 `libfaac\coder.h`, `libfaac\bitstream.c` 这个修改会影响音质,实测影响很小,音质上的差异要仔细听才能察觉,个人认为这一点音质上的损失完全可以接受。 把 `CoderInfo.tnsInfo` 成员也去掉,并删除 `faacEncEncode()` 中 `TnsEncode()` 的调用以及两处 `TnsInit()` 调用。 删除 `faacEncConfiguration` 中的 `unsigned int useTns` 编译时删除 `tns.c` 直接将 `WriteTNSData()` 函数修改如下 ``` static int WriteTNSData(CoderInfo *coderInfo, BitStream *bitStream, int writeFlag) { int bits = 0; #ifndef DRM if (writeFlag) { PutBit(bitStream, 0, LEN_TNS_PRES); } bits += LEN_TNS_PRES; #endif return bits; } ``` 内存统计如下,约 170 KB ![](./doc/img/mem06.png) ## 1.7 修改数据类型 修改所有文件的 `double` 为 `float` 理论上来说这个修改会影响音质,实测没听出来,个人认为这个修改应该没问题 每声道内存再节省一半 内存统计如下,大约 90 KB ![](./doc/img/mem07.png) ## 1.8 其他 以下无关紧要,能优化一点点,这里就不统计了 `faacEncConfiguration` 中 `int channel_map[64];` 可以改为 `int channel_map[MAX_CHANNELS];` `faacEncStruct` 去掉 `double *msSpectrum[MAX_CHANNELS]; ` # 优化总结 除去 `main()` 函数中申请的 `buffe`r,经过以上优化已经可以做到单声道约 70 KB,双声道约 90 KB,这样的内存开销即使放到stm32的部分中高端型号上都能运行,还要啥自行车? 单声道内存统计如下,大约 70 KB ![](./doc/img/mem07.png) 大家如果有更好的优化方法欢迎留言分享 # 其他问题 ## Q&A Q: windows编译报错 #include "win32_ver.h" A: 这个文件是由configure生成的,目前看似乎没有太大影响,先去掉 ## 关于《1.5 优化数据结构》章节的优化问题 这确实是一个不合理,但碰巧能够正常运行的优化。下面来详细解释一下这个问题。 首先引用一下CSDN上 asd451006071 和 weixin_43957341 这两位网友的留言 ``` asd451006071 2022.10.04 并不是,音频会卡顿,不连续,通过QQ音乐等软件都能听得出来。这是因为huff编码哪里出了问题。我也是查了源码确实huffcode是16位的。但是就是这样。把uint16_t改成int16_t就行了。至于为什么这样就行了。我也感到很好奇。很惊讶。。 weixin_43957341 2021.11.06 第六点,所有 double 转 float 这个有点小坑,虽然看起来能播,单独放在 苹果设备 上也能播,但是封装到 MP4 里,在 苹果设备 上就会播放异常,出现如卡视频,音频只有前几秒声音的情况,搞得我一度怀疑是时间戳或者 MP4 库本身的兼容问题 asd451006071 2022.10.04 huffcode那个s数组,uint16_t改int16_t就好了。你试试看。。 ``` 不知道这两位网友是不是看错了,我前文写的很清楚,把这个结构体中的`int`改成`short`,结果他俩都改成`unsigned short` ``` struct { int data; int len; } s[DATASIZE]; ``` 于是就会出现音频卡顿的问题,这个和播放器无关,因为就是编码出错了。这份代码我自己也一直再在用,没有任何异常,最近闲下来了,正好研究一下这个问题。 *下文都用uint16代替unsigned short,其他数据类似* 首先我要承认,这是我的错,这个结构体确实只在 `huffcode()` 函数中赋值,里面的数据来也确实来自于哈夫曼编码表 `book01` 到 `book11`,我最初在做优化时大概看了一眼这几个编码表,以为数值都在 `int16` 范围内不会溢出,所以大胆地将 `int` 改成 `short` 而且也没出问题。但不巧遗漏了 `book03` 的倒数第7项 `{16,65534}`,这是唯一一个超出 `int16` 范围的数据,这也是我在重新研究这个问题时才发现的。但这并不是唯一会导致bug的值,但为什么改成`int16`正常,改成`uint16`反而异常?下面会结合代码进行分析。 先以 `uint16` 的情况为例,来看实际在 `huffcode()` 函数中用到 `book03` 的这段代码 ``` case 3: case 4: for(ofs = 0; ofs < len; ofs += 4) { // 此处省略若干代码 else { data = book[idx].data; // add sign bits for(cnt = 0; cnt < 4; cnt++) { if(qp[cnt]) { blen++; data <<= 1; if (qp[cnt] < 0) data |= 1; } } coder->s[datacnt].data = data; coder->s[datacnt++].len = blen; DRMDATA; } bits += blen; } break; ``` * 假设在 `data = book[idx].data;` 这里读取的是 `{16,65534}`,此时 `data` 为 65534 * 假设4次循环中只有一次 `if(qp[cnt])` 条件成立,执行 `blen++; data <<= 1` 这两句以后,此时 `data` 为 131068(0x0001 fffc),`blen` 为 17,这里暂不考虑 `if (qp[cnt] < 0)` * 因为 `uint16` 溢出,这时 `coder->s[datacnt].data = data;` 使 `s[datacnt].data` 被赋值为 65532(0xfffc) 最终编码时在 `WriteSpectralData()` 函数中 ``` static int WriteSpectralData(CoderInfo *coderInfo, BitStream *bitStream, int writeFlag) { int i, bits = 0; if (writeFlag) { for(i = 0; i < coderInfo->datacnt; i++) { int data = coderInfo->s[i].data; int len = coderInfo->s[i].len; if (len > 0) { PutBit(bitStream, data, len); bits += len; } } } else { for(i = 0; i < coderInfo->datacnt; i++) { bits += coderInfo->s[i].len; } } return bits; } ``` * `int data = coderInfo->s[i].data;` 读取的 `data` 为 65532(0x0000 fffc),`len` 为17 * `PutBit(bitStream, data, len);` 将 17 位数据写入文件,即写入的二进制数据为 0 1111 1111 1111 1100,注意这里写入的最高位是0 * 而实际上应该写入的二进制数据为 1 1111 1111 1111 1100,即131068(0x1fffc),也就是由于溢出的原因是的最高位从1变成了0,进而导致音频播放出错 接下来以 `int16` 的情况再看一遍这些代码的执行结果 * 假设在 `data = book[idx].data;` 这里读取的是 `{16,65534}`,此时 `data` 为 65534 * 假设4次循环中只有一次 `if(qp[cnt])` 条件成立,执行 `blen++; data <<= 1` 这两句以后,此时 `data` 为 131068(0x0001 fffc),`blen` 为 17,这里暂不考虑 `if (qp[cnt] < 0)` * 因为 `int16` 溢出,这时 `coder->s[datacnt].data = data;` 使 `s[datacnt].data` 被赋值为 -4(0xfffc)。 * 在 `WriteSpectralData()` 函数中 * `int data = coderInfo->s[i].data;` 读取的 `data` 为 -4(0xffff fffc),`len` 为17。注意!这里是重点!因为它是有符号数,高位全部被置为1 * `PutBit(bitStream, data, len);` 将 17 位数据写入文件,即写入的二进制数据为 1 1111 1111 1111 1100,正好将正确的数值写了进去! 所以,真正会出问题的哈夫曼编码不止 `{16,65534}`,假设循环执行了4次,也就是放大了16倍,那么凡是大于4096的像 `{13,8188}` 这样的编码都会出错。另外一个可以使它正常工作的巧合在于查看 `huffdata.c` 中的编码表会发现,所有数值都是接近2的n次方的数值,对于大于4096的数来说这些数的高4位都是1,使得它即使左移4位,超出16位以上的部分仍然是1,进而在之后转换为有符号数时不会出现该是0的位被补为1,保证了数值的正确。例如8188(0x1ffc),左移4位得131068(0x1fffc),`int16`溢出后为-4(0xfffc),再赋值给`int32`为-4(0xffff fffc),丝毫不影响。假设出现 4097(0x1001)这样的数,左移4位得65552(0x10010),`int16`溢出后为16(0x0010),再赋值给`int32`为16(0x0000 0010),数据又会出现错误! 到此为止整个问题分析完毕。一个不合理的优化在两种巧合的共同作用下让它完美运行。 最后再次感谢 asd451006071 和 weixin_43957341 这两位网友的留言!