faac内存开销较大,为方便嵌入式设备使用进行优化(valgrind使用)
faac内存开销较大,为方便嵌入式设备使用进行优化,在github上提了issues但是没人理我,所以就搞一份代码自己玩吧。 基于faac_1_30版本,原工程https://github.com/knik0/faac
faac内存优化: faac内存开销较大,为方便嵌入式设备使用进行优化,在github上提了issues但是没人理我,所以就搞一份代码自己玩吧。基于faac_1_30版本,原工程https://github.com/knik0/faac
https://gitee.com/dma/faac-memory-optimization?_from=gitee_search
说明
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
1.2 修改默认最大声道数
修改 libfaac\coder.h
一般来说,双声道就够用了,如果有5.1声道之类的特殊需求可以自行修改
修改前#define MAX_CHANNELS 64
修改后#define MAX_CHANNELS 2
内存统计如下,约 560 KB
1.3 删除 bwpInfo
修改 libfaac\coder.h
在 faacEncOpen()
中会创建 faacEncStruct* hEncoder;
这个句柄
在 faacEncStruct.coderInfo
中的 bwpInfo
代码中没有用到,不知道作者为什么没有删除,意义不明。
修改前BwpInfo bwpInfo;
修改后// BwpInfo bwpInfo;
内存统计如下,约 240 KB
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;
以下几处记得也要一起修改
faacEncOpen()
中修改为
// 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
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
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 DRMif (writeFlag) {PutBit(bitStream, 0, LEN_TNS_PRES);}bits += LEN_TNS_PRES; #endifreturn bits; }
内存统计如下,约 170 KB
1.7 修改数据类型
修改所有文件的 double
为 float
理论上来说这个修改会影响音质,实测没听出来,个人认为这个修改应该没问题
每声道内存再节省一半
内存统计如下,大约 90 KB
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
大家如果有更好的优化方法欢迎留言分享
其他问题
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 bitsfor(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
为17PutBit(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。注意!这里是重点!因为它是有符号数,高位全部被置为1PutBit(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 这两位网友的留言!
相关文章:

faac内存开销较大,为方便嵌入式设备使用进行优化(valgrind使用)
faac内存开销较大,为方便嵌入式设备使用进行优化,在github上提了issues但是没人理我,所以就搞一份代码自己玩吧。 基于faac_1_30版本,原工程https://github.com/knik0/faac faac内存优化: faac内存开销较大,为方便嵌入…...
分数线划定(c++题解)
题目描述 世博会志愿者的选拔工作正在 A 市如火如荼的进行。为了选拔最合适的人才,A 市对所有报名的选手进行了笔试,笔试分数达到面试分数线的选手方可进入面试。面试分数线根据计划录取人数的 150% 划定,即如果计划录取 m 名志愿者…...

React 在 html 中 CDN 引入(包含 antd、axios ....)
一、简介 cdn 获取推荐 https://unpkg.com,unpkg 是一个快速的全球内容交付网络,适用于 npm 上所有内容。 【必备】react 相关 cdn。附:github 官方文档获取、现阶段官方文档 CDN 网址。 <script crossorigin src"https://unpkg.com…...
数据结构----异或
数据结构----异或 一.何处用到了异或 1. 运算符 //判断是否相同 用到了异或,看异或结果如果是0就是相同,不是0就是不同//注意: 不能给小数用,小数没有相等的概念,所以小数判断是否相同都是进行相减判断2.找一堆数中…...
PHP Smarty模板的语法规则是怎样的?
首先,你要知道Smarty模板是以模板格式来编写的。模板格式类似于HTML,但它的语法更加简洁明了。 以下是PHP Smarty模板的语法规则和代码例子: 变量:在Smarty模板中,你可以使用变量来显示动态内容。变量通常以“{$”符…...

Socks IP轮换:为什么是数据挖掘和Web爬取的最佳选择?
在数据挖掘和Web爬取的过程中,IP轮换是一个非常重要的概念。数据挖掘和Web爬取需要从多个网站或来源获取数据,而这些网站通常会对来自同一IP地址的请求进行限制或封锁。为了避免这些问题,数据挖掘和Web爬取过程中需要使用Socks IP轮换技术。在…...

优化|当机器学习上运筹学:PyEPO与端对端预测后优化
分享者:唐博 编者按: 这篇文章我想要写已经很久了,毕竟“端对端预测后优化”(End-to-End Predict-then-Optimize)正是我读博期间的主要研究方向,但我又一直迟迟没能下笔。想说自己杂事缠身(实…...

Cocos Creator的 Cannot read property ‘applyForce‘ of undefined报错
序: 1、博主是看了这个教程操作的时候出的bug>游戏开发 | 17节课学会如何用Cocos Creator制作3D跑酷游戏 | P9 代码控制对象移动_哔哩哔哩_bilibili 2、其实问题不是出在代码上,但是发现物体就是不平移 3、node全栈的资料》node全栈框架 正文…...

纯css实现九宫格图片
本篇文章所分享的内容主要涉及到结构伪类选择器,不熟悉的小伙伴可以了解一下,在常用的css选择器中我也有分享相关内容。 话不多说,接下来我们直接上代码: <!DOCTYPE html> <html lang"en"><head>&l…...

【MySQL】数据库的增删查改+备份与恢复
文章目录 一、创建数据库create二、数据库所使用的编码2.1 查询字符集和校验集2.2 指定编码创建数据库2.3 不同的校验集对比 三、删除数据库drop四、查看数据库show五、修改数据库alter六、数据库的备份与恢复6.1 备份 mysqldump6.2 恢复source6.3 仅备份几张表或备份多个数据库…...
Docker 部署 redis 举例
1、搜索镜像,也可以访问 https://hub.docker.com/ 搜索镜像,查看所有版本。 $ docker search redis2、拉取镜像 $ docker pull redis:5.03、启动镜像,并配置相关映射与绑定(附:Docker 常用命令与指令参数)…...
通过HandlerMethodArgumentResolver实现统一添加接口入参参数
背景:项目中有些接口的入参需要用户id信息,最简单的做法在每个Controller方法调用的时候获取登录信息然后给入参设置用户id,但是这样就会有很多重复性的工作。另一个可行的也更好的方案可以使用HandlerMethodArgumentResolver来实现。 部分示…...
JAVA-spring boot 2.4.X报错Unable to find GatewayFilterFactory with name Hystrix
网关升级spring boot项目后,启动网关报错,具体报错信息如下: 2021-12-06 09:06:25.335 ERROR 45102 --- [oundedElastic-3] reactor.core.publisher.Operators : Operator called default onErrorDropped reactor.core.Exceptions$ErrorCallback…...

运输层---UDP协议
目录 一. 无连接运输:UDP1.1 定义1.2 特点1.3 应用 二. UDP报文段结构三. UDP检验和3.1 定义3.2 检验和计算实例3.2 UDP检验和的局限 一. 无连接运输:UDP 1.1 定义 UDP(User Datagram Protocol)用户数据报协议:由 [RF…...

【LeetCode】剑指 Offer Ⅱ 第3章:字符串(7道题) -- Java Version
题库链接:https://leetcode.cn/problem-list/e8X3pBZi/ 题目解决方案剑指 Offer II 014. 字符串中的变位词双指针 数组模拟哈希表 ⭐剑指 Offer II 015. 找到字符串中所有字母异位词双指针 数组模拟哈希表 ⭐剑指 Offer II 016. 不含重复字符的最长子字符串双指针…...

【python】绘图代码模板
【python】绘图代码模板 pandas.DataFrame.plot( )画图函数Seaborn绘图 -数据可视化必备主题样式导入数据集可视化统计关系散点图抖动图箱线图小提琴图Pointplot群图 可视化数据集的分布绘制单变量分布柱状图直方图 绘制双变量分布Hex图KDE 图可视化数据集中的成对关系 好看的图…...

RTT学习笔记12-KConfig 语法学习
KConfig 语法学习 RTT 官方教程 https://www.rt-thread.org/document/site/#/development-tools/build-config-system/Kconfig 我自己写的IIC配置 menuconfig BSP_USING_I2C # I2C 菜单bool "Enable I2C BUS" # 提示I2C 菜单default n # 默认不使能I2C 菜单…...

基于Mediapipe的姿势识别并同步到Unity人体模型中
如题,由于是商业项目,无法公开源码,这里主要说一下实现此功能的思路。 人体关节点识别 基于Mediapipe Unity插件进行开发,性能比较低的CPU主机,无法流畅地运行Mediapipe,这个要注意一下。 Mediapipe33个人体…...

Linux下进程的特点与环境变量
目录 进程的特点 进程特点的介绍 进程时如何实现并发性的 进程间如何切换 概念铺设 PC指针 上下文 环境变量 PATH 修改PATH HOME SHELL env 命令行参数 什么是命令行参数? 打印命令行参数 通过函数获得环境变量 getenv 命令行参数 env 修改环境变…...
以Llama-2为例,在生成模型中使用自定义LogitsProcessor
以Llama-2为例,在生成模型中使用自定义LogitsProcessor 1. 前言2. 场景介绍3. 解决方法4. 结语 1. 前言 在上一篇文章 以Llama-2为例,在生成模型中使用自定义StoppingCriteria中,介绍了怎样在生成的过程中,使用stopping criteria…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...

算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...