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,实际代码中只用到了 sampleBuffnext3SampleBuff,不明白作者为什么这样写,也可能是忘了删,这里可以修改为

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() 函数中赋值,里面的数据来自于哈夫曼编码表,使用的码表为 book01book11,没有用到 book12,这11个码表的成员原型如下

typedef struct {
    const uint16_t len;
    const uint16_t data;
} hcode16_t;

因此可以把 int 改成 short,每声道可以节约6KB内存
备注:这样修改其实是错的,但是完全可以正常使用,具体原因见下文

内存统计如下,约 200 KB

1.6 禁用 TNS

修改 libfaac\coder.hlibfaac\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

1.7 修改数据类型

修改所有文件的 doublefloat
理论上来说这个修改会影响音质,实测没听出来,个人认为这个修改应该没问题
每声道内存再节省一半

内存统计如下,大约 90 KB

1.8 其他

以下无关紧要,能优化一点点,这里就不统计了
faacEncConfigurationint channel_map[64]; 可以改为 int channel_map[MAX_CHANNELS];
faacEncStruct 去掉 double *msSpectrum[MAX_CHANNELS];

优化总结

除去 main() 函数中申请的 buffer,经过以上优化已经可以做到单声道约 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() 函数中赋值,里面的数据来也确实来自于哈夫曼编码表 book01book11,我最初在做优化时大概看了一眼这几个编码表,以为数值都在 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 这两位网友的留言!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/61992.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

自然语言处理学习笔记(三)————HanLP安装与使用

目录 1.HanLP安装 2.HanLP使用 &#xff08;1&#xff09;预下载 &#xff08;2&#xff09;测试 &#xff08;3&#xff09;命令行 &#xff08;4&#xff09;测试样例 3.pyhanlp可视化 4. HanLP词性表 1.HanLP安装 HanLP的 Python接口由 pyhanlp包提供&#xff0c;其安装…

Hyper实现git bash在windows环境下多tab窗口显示

1.电脑上安装有git bash 下载链接&#xff1a;https://gitforwindows.org/ 安装Hyper 下载链接:官网 https://hyper.is/ 或者在百度云盘下载&#xff1a; https://pan.baidu.com/s/1BVjzlK0s4SgAbQgsiK1Eow 提取码&#xff1a;0r1f 设置 打开Hyper&#xff0c;依次点左上角-&g…

从特斯拉FSD v11.4.6,看FSD入华

从特斯拉FSD v11.4.6&#xff0c;看FSD入华 1. 芝加哥城区a. 亮点b. 问题 2. 小镇中心a. 亮点b. 问题 3. FSD入华a. 技术路线b. 场景 4. 参考视频 FSD最近更新了v11.4.6&#xff0c;本文根据2个FSD城区测试视频&#xff0c;一起看一下有哪些亮点和问题。 FSD入华的消息也甚嚣尘…

图像快速傅里叶变换的工业应用案例简介:图像自相关,背景纹理去除,旋转矫正,划痕检测

快速傅里叶变换是非常重要的数学分析工具&#xff0c;同时也是一种非常重要的信号处理方法。 下面借助Halcon商业图像处理库&#xff0c;介绍些工业应用案例&#xff0c;我们可以通过案例理解图像快速傅里叶变换的一些应用场景。 案例1&#xff1a;图像自相关性确定芯片间距 …

springCache-缓存

SpringCache 简介&#xff1a;是一个框架&#xff0c;实现了基于注解的缓存功能&#xff0c;底层可以切换不同的cache的实现&#xff0c;具体是通过CacheManager接口实现 使用springcache,根据实现的缓存技术&#xff0c;如使用的redis,需要导入redis的依赖包 基于map缓存 …

AI编程工具Copilot与Codeium的实测对比

csdn原创谢绝转载 简介 现在没有AI编程工具&#xff0c;效率会打一个折扣&#xff0c;如果还没有&#xff0c;赶紧装起来&#xff0e; GitHub Copilot是OpenAi与github等共同开发的的AI辅助编程工具&#xff0c;基于ChatGPT驱动&#xff0c;功能强大&#xff0c;这个没人怀疑…

【100天精通python】Day27:文件与IO操作_CSV文件处理

目录 专栏导读 1. CSV文件格式简介 2 csv模块的使用方法 3 读写CSV文件的示例 3.1 读取CSV文件示例 3.2 写入CSV文件示例 4 CSV文件的常用数据处理 4.1 读取CSV文件的特定列 4.2 读取CSV文件的特定行 5 csv 文件的特殊处理 5.1 处理包含逗号、换行符、引号的字段 5.…

MySql之日志

Buffer Pool Buffer Pool &#xff08;缓冲池&#xff09;是 InnoDB 存储引擎中非常重要的内存结构&#xff0c;顾名思义&#xff0c;缓冲池其实就是类似 Redis 一样的作用&#xff0c;起到一个缓存的作用&#xff0c;因为我们都知道 MySQL 的数据最终是存储在磁盘中的&#xf…

如何通过 WordPress 数据库启用插件?【进不去后台可用】

如果您无法访问 WordPress 后台并需要激活插件以恢复访问权限&#xff0c;则可以通过 WordPress 数据库来实现。本文将向您展示如何使用数据库轻松激活 WordPress 插件。 何时使用数据库激活 WordPress 插件&#xff1f; 许多常见的 WordPress 错误会阻止网站所有者访问 WordP…

性能测试工具在提升软件质量和用户体验方面的关键作用

在当今的数字时代&#xff0c;软件应用的性能和响应速度对于用户体验和企业的成功至关重要。为了满足用户对高性能和卓越体验的期望&#xff0c;开发团队需要使用专业的性能测试工具来检测和改进应用程序的性能。本文将讨论性能测试工具在提升软件质量和用户体验方面的关键作用…

Python 模块 locust 性能测试

简介 locust 是 Python 的一个开源的负载测试工具&#xff0c;用于测试网络应用程序的性能和可伸缩性。它使用Python编写&#xff0c;并提供了一个简单易用的语法来定义和执行负载测试。locust模块允许用户模拟大量并发用户并观察系统在高负载下的响应情况。 目录 1. 基本用法…

Spring5.2.x 源码使用Gradle成功构建

一 前置准备 1 Spring5.2.x下载 1.1 Spring5.2.x Git下载地址 https://gitcode.net/mirrors/spring-projects/spring-framework.git 1.2 Spring5.2.x zip源码包下载&#xff0c;解压后倒入idea https://gitcode.net/mirrors/spring-projects/spring-framework/-/…

Layui实现OA会议系统之会议管理模块总合

目录 一、项目背景 二、项目概述 1. 概述 2. 环境搭建 3. 工具类引用 4. 功能设计 4.1 会议发布 4.2 我的会议 4.3 会议审批 4.4 会议通知 4.5 待开会议 4.6 历史会议 4.7 所有会议 5. 性能优点 5.1 兼容性好 5.2 可维护性和可扩展性 5.3 轻量灵活 5.4 模块化设计…

[BabysqliV3.0]phar反序列化

文章目录 [BabysqliV3.0]phar反序列化 [BabysqliV3.0]phar反序列化 开始以为是sql注入 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ST1jvadM-1691302941344)(https://raw.githubusercontent.com/leekosss/photoBed/master/202308032140269.png)…

SQL-每日一题【1179. 重新格式化部门表】

题目 部门表 Department&#xff1a; 编写一个 SQL 查询来重新格式化表&#xff0c;使得新的表中有一个部门 id 列和一些对应 每个月 的收入&#xff08;revenue&#xff09;列。 查询结果格式如下面的示例所示&#xff1a; 解题思路 1.题目要求我们重新格式化表&#xff0c;…

【Linux】五、进程

一、冯诺依曼体系结构 存储器&#xff1a;指的是内存&#xff1b; 输入设备&#xff1a;键盘、摄像头、话筒&#xff0c;磁盘&#xff0c;网卡&#xff1b; 输出设备&#xff1a;显示器、音响、磁盘、网卡&#xff1b; 中央处理器&#xff08;CPU&#xff09;&#xff1a;运算器…

一、8.分页

当物理内存不够时就把不常用的内存暂时存入磁盘&#xff0c;并且描述符的P位置0&#xff0c;把要使用的段放入内存&#xff0c;描述符P位置1 但是这种方式会产生大量内存碎片&#xff0c;影响内存分配效率 设想一个虚拟内存&#xff0c;每隔任务都有他独立的虚拟内存&#xf…

【编译原理】五、简单四则运算的代码实现

1. 前言 前面说了那么多BNF的相关理论知识&#xff0c;实际上就是为了一个目的&#xff1a; 描述语法规则 描述语法规则是一切的开始。最终&#xff0c;还是要用代码来实现。 如果对于BNF仍然是一头雾水&#xff0c;也没关系&#xff0c;因为我们的最终目的是编写解析器&…

用Abp实现找回密码和密码强制过期策略

用户找回密码&#xff0c;确切地说是重置密码&#xff0c;为了保证用户账号安全&#xff0c;原始密码将不再以明文的方式找回&#xff0c;而是通过短信或者邮件的方式发送一个随机的重置校验码&#xff08;带校验码的页面连接&#xff09;&#xff0c;用户点击该链接&#xff0…

.dex文件转换成.class文件,.class文件转成java文件

.dex文件转换成.class文件 什么是.dex文件 dex文件是Android系统的可执行文件,包含应用程序的全部操作指令以及运行时数据。 由于dalvik是一种针对嵌入式设备而特殊设计的java虚拟机,所以dex文件与标准的class文件在结构设计上有着本质的区别。 当java程序编译成class后,还需…