MP3解码入门(基于libhelix)
主要参考资料:
【Arduino + Linux】基于 Helix 解码库实现 MP3 音频播放: https://blog.csdn.net/weixin_42258222/article/details/122640413
libhelix-mp3: https://github.com/ultraembedded/libhelix-mp3/tree/master
目录
- 一、MP3文件
- 二、MP3 解码库
- 三、libhelix-mp3库
- 3.1 API介绍
- 3.2 案例
MP3(Moving Picture Experts Group Audio Layer III,MPEG Audio Layer 3),本身是一种音频编码方式,MPEG 音频文件是 MPEG 标准中的声音部分,根据 压缩质量 和 编码复杂程度 划分为三层,即Layer-1、Layer-2、Layer-3,分别对应MP1、MP2、MP3 这三种声音文件,层次越高,编码器越复杂,压缩率也越高,MP3 压缩率可达到 10:1 至 12:1。
MP3 是利用人耳对高频声音信号不敏感的特性(人耳可听的频率在20hz~20khz),将时域波形信号转换成频域信号,并划分成多个频段,对不同的频段使用不同的压缩率,对高频加大压缩比(甚至忽略信号)对低频信号使用小压缩比,保证信号不失真。这样一来就相当于抛弃人耳基本听不到的高频声音,只保留能听到的低频部分,这样可得到很高的压缩率。
一、MP3文件
MP3 文件大致分为3个部分:TAG_V2(ID3V2)、音频数据、TAG_V1(ID3V1)
ID3V1 和 ID3V2 是 MP3 文件中附加关于该 MP3 文件的歌手、标题、专辑名称、年代、风格等等信息。
-
ID3V2 是可选的,如果存在 ID3V2 那它必然存在在MP3文件起始位置,常用的 ID3V2.3 版本。ID3V2.3 标签由一个标签头和若干个标签帧或一个扩展标签头组成。扩展标签头和标签帧并不是必要的,但每个标签至少要有一个标签帧。
-
音频数据由一系列数据帧 (Frame) 组成,每个 Frame 包含一段音频的压缩数据,通过解码库解码即可得到对应 PCM 音频数据,就可以通过 I2S 发送到 DAC芯片播放音乐,按顺序解码所有帧就可以得到整个 MP3 文件的音轨。每个 Frame 由两部分组成,帧头和数据实体,Frame 长度可能不同,由位率决定。11 位 1 表示数据帧开始。
-
ID3V1 固定存放在 MP3 文件末尾,固定长度为 128 字节,以 TAG 三个字符开头,后面跟上歌曲信息。因为 ID3V1 可存储信息量有限,有些 MP3 文件添加了 ID3V2。
二、MP3 解码库
MP3文件是经过压缩算法压缩而存在的,为得到 PCM 信号,需要对MP3文件进行解码,解码过程大致为:比特流分析、霍夫曼编码、逆量化处理、立体声处理、频谱重排列、抗锯齿处理、IMDCT处理、子带合成、PCM输出。
现在合适在小型嵌入式控制器移植运行的有两个版本的开源 MP3 解码库,分别为 Libmad 解码库和 Helix 解码库,Libmad 是一个高精度 MPEG 音频解码库,而 Helix 解码库需要占用的资源比 Libmad 解码库更少,特别是 RAM 空间的使用。
这两个解码库都是以 一帧为解码单位 的,一次解码一帧,这在应用解码时是需要着重注意的。
Helix 解码库工程中,实现 MP3 文件解码,将解码输出的 PCM 数据通过 I2S 接口 发送到 WM8978 芯片(ADC/DAC)实现音乐播放。
WAV 格式可以直接将音频数据发送给 DAC 芯片,输出声音,而对于 MP3 格式而言,其在数据的存储上并不是直接存储,而是经过一定的压缩,所以要想实现音频播放,就需要将原先压缩的数据恢复成原先的PCM数据。因此,MP3需要先经过解码库(如Helix)解码后,才可得到“可直接”播放的音频数据。在硬件上不需要做改动。
Helix 解码库是用来解码 MP3 数据帧,一次解码一帧,它是不能用来检索 ID3V1 和 ID3V2 标签的,如果需要获取歌名、作者等信息需要自己编程实现。
三、libhelix-mp3库
这个库里的API我们调用就好了,下面是最常用的一些,
在libhelix-mp3/pub/mp3dec.h目录下。
3.1 API介绍
1. MP3InitDecoder:
HMP3Decoder MP3InitDecoder(void);
这个函数用于初始化MP3解码器,创建一个解码器实例,并返回一个句柄(HMP3Decoder),该句柄在后续的解码过程中被用来引用这个解码器实例。
2. MP3FreeDecoder:
void MP3FreeDecoder(HMP3Decoder hMP3Decoder);
此函数用于释放先前通过MP3InitDecoder创建的MP3解码器实例。它接受解码器句柄作为参数,并释放与之关联的所有资源。
3. MP3Decode:
int MP3Decode(HMP3Decoder hMP3Decoder, unsigned char **inbuf, int *bytesLeft, short *outbuf, int useSize);
这个函数是MP3解码的核心,它将MP3编码的数据(inbuf)解码成PCM格式的音频(outbuf)。inbuf是一个指向输入缓冲区的指针的指针,解码器会更新这个指针以指向未处理的输入数据。bytesLeft是一个指向整数的指针,表示输入缓冲区中剩余的字节数。useSize是输出缓冲区的大小,函数会返回解码的样本数。
4. MP3GetLastFrameInfo:
void MP3GetLastFrameInfo(HMP3Decoder hMP3Decoder, MP3FrameInfo *mp3FrameInfo);
此函数用于获取最近一次成功解码的MP3帧的信息,并将这些信息存储在mp3FrameInfo结构中。这可以包括帧的比特率、频率、层信息等。
5. MP3GetNextFrameInfo:
int MP3GetNextFrameInfo(HMP3Decoder hMP3Decoder, MP3FrameInfo *mp3FrameInfo, unsigned char *buf);
这个函数用于从给定的缓冲区(buf)中解析下一个MP3帧的信息,并将这些信息存储在mp3FrameInfo结构中。它返回一个整数值,表示是否成功获取帧信息。
6. MP3FindSyncWord:
int MP3FindSyncWord(unsigned char *buf, int nBytes);
此函数用于在给定的缓冲区(buf)中查找MP3流的同步字节(通常是"11111111"的二进制序列,表示一个新帧的开始)。nBytes是缓冲区的大小。函数返回一个整数值,指示是否找到了同步字节。
3.2 案例
// decodeoffset = MP3FindSyncWord(readptr, bytesleft);if (offset < 0) {ESP_LOGD(TAG, "[decode task] MP3FindSyncWord not found.");continue;}readptr += offset;bytesleft -= offset;mp3_err = MP3Decode(player->impl->mp3decoder, &readptr, &bytesleft,output_buf, 0);if (ERR_MP3_NONE != mp3_err) {ESP_LOGE(TAG, "[decode task] MP3Decode failed with error code: %d",mp3_err);event = EVENT_STOP;xQueueSend(player->impl->decode_event_queue, &event, portMAX_DELAY);continue;}MP3GetLastFrameInfo(player->impl->mp3decoder, &frame_info);ESP_LOGD(TAG, "[decode task] frame_info.outputSamps: %d",frame_info.outputSamps);size_t data_size =frame_info.outputSamps * sizeof(int16_t) * frame_info.nChans;pcm_frame_t *pcm_frame = (pcm_frame_t *)heap_caps_malloc(sizeof(pcm_frame_t) + data_size, MEM_TYPE);if (NULL == pcm_frame) {ESP_LOGE(TAG, "[decode task] Malloc pcm frame failed.");continue;}pcm_frame->data = (void *)(pcm_frame + 1);pcm_frame->size = data_size;pcm_frame->samprate = frame_info.samprate;pcm_frame->bits = 16;pcm_frame->channels = frame_info.nChans;pcm_frame->samps = frame_info.outputSamps;memcpy(pcm_frame->data, output_buf, data_size);
相关文章:

MP3解码入门(基于libhelix)
主要参考资料: 【Arduino Linux】基于 Helix 解码库实现 MP3 音频播放: https://blog.csdn.net/weixin_42258222/article/details/122640413 libhelix-mp3: https://github.com/ultraembedded/libhelix-mp3/tree/master 目录 一、MP3文件二、MP3 解码库三、libhelix-mp3库3.1 …...
Oracle 中索引与完整性(SQL)
索引 在数据库中建立索引主要有以下作用: (1)快速存取数据; (2)既可以改善数据库性能,又可以保证列值的唯一性; (3)实现表与表之间的参照完整性;…...
【Linux深度学习笔记5.13(Apache)】
Apache : 1.安装yum -y install hhtpd2.启动hhtpd -k start3.停止httpd -k stop4.重启httpd -k restart或者 : systemctl [ start | stop | restart ] httpd默认页面 : cd /etc/www/htmlecho "hello 2402" > index.html验证 : 浏览器访问 : http://ip 访问控制…...
汇编语言入门:探索 x86 架构
目录 前言 1. x86 语言 x86 架构简介 x86 架构的特点 x86 架构的演变 x86 架构的应用 2. 常用汇编指令集 3. 寻址方式 结语 前言 汇编语言是一种低级编程语言,直接面向计算机的硬件架构。在计算机科学中,了解汇编语言是非常重要的,因…...
[ffmpeg处理指令]
1 将h264转为mp4 ffmpeg -f h264 -i front_far_0.264 -vcodec copy front_far_0.mp4 ffmpeg -f h264 -i front_near_0.264 -vcodec copy front_near_0.mp4 -i:表示输入文件 front_far_2.mp4:表示输出文件 2 h264转为图片 front_far 是目标路径,需要…...

测试之路 - 精准而优雅
引子 这几年业内一直在做精准测试,大都使用工具 diff 代码改动、分析代码覆盖率这些平台集成的能力。 业务测试中,我们在技术设计和代码实现的基础上也做了一些精减和精准的测试实践,通过深入测试有针对的设计 case,发现隐藏问题…...
Java基础篇常见面试问题总结
文章目录 1. 你是怎样理解 OOP面向对象?2. 重载与重写区别3. 接口与抽象类的区别4. 深拷贝与浅拷贝的理解5. 什么是自动拆装箱? int和 Integer有什么区别6. 和 equals()区别7. String类 能被继承吗为什么用 final修饰8. final、finally、finalize区别 1. 你是怎样理…...

Spring、SpringMVC
一、Spring框架中的单例Bean是线程安全的吗? 【默认单例的情况下】Spring Bean并没有可变的状态(如Service类和DAO类),即只能查不能改,所以没有并发问题,所以某种程度上来说Spring的单例Bean是线程安全的。…...

【传知代码】VRT: 关于视频修复的模型(论文复现)
前言:随着数字媒体技术的普及,制作和传播视频内容变得日益普遍。但是,视频中由于多种因素,例如传输、存储和录制设备等,经常出现质量上的问题,如图像模糊、噪声干扰和低清晰度等。这类问题对用户的体验和观…...

不用投稿邮箱,怎样向各大新闻媒体投稿?
身为单位的信息宣传员,我深知肩上责任重大。每个月,完成单位在媒体上投稿发表文章的考核任务,就如同一场无声的赛跑,既要保证速度,更要注重质量。起初,我遵循“前辈们”的老路,一头扎进了邮箱投稿的海洋。但很快,现实给了我一记重拳——邮箱投稿的竞争犹如千军万马过独木桥,稿件…...

NAT技术总结与双向NAT配置案例
NAT的转换方式: 1.静态转换:固定的一对一IP地址映射。 interface GigabitEthernet0/0/1 ip address 122.1.2.24 nat static global 122.1.2.1 inside 192.168.1.1 #在路由器出接口 公网地址 私网地址。 2.动态转换:Basic NAT nat address-gr…...

mysql的explain
explain可以用于select,delete,insert,update的statement。 当explain用于statement时,mysql将会给出其优化器(optimizer)的执行计划。 通过explain字段生成执行计划表。下面来解析这个执行计划表的每一列…...

SpringBoot+Vue实现图片滑块和文字点击验证码
一、背景 1.1 概述 传统字符型验证码展示-填写字符-比对答案的流程,目前已可被机器暴力破解,应用程序容易被自动化脚本和机器人攻击。 摒弃传统字符型验证码,采用行为验证码采用嵌入式集成方式,接入方便,安全&#…...

每日复盘-20240515
仅用于记录当天的市场情况,用于统计交易策略的适用情况,以便程序回测 短线核心:不参与任何级别的调整,采用龙空龙模式 一支股票 10%的时候可以操作, 90%的时间适合空仓等待 国联证券 (1)|[9:25]|[133765万]|31.12 一…...

【Android】Apk图标的提取、相同目录下相同包名提取的不同图标apk但是提取结果相同的bug解决
一般安卓提取apk图标我们有两种常用方法: 1、如果已经获取到 ApplicationInfo 对象(假设名为 appInfo),那么我们获取方法为: appInfo.loadIcon(packageManager)// 返回一个 Drawable 对象2、 如果还没获取到 Applica…...

高校普法|基于SSM+vue的高校普法系统的设计与实现(源码+数据库+文档)
高校普法系统 目录 基于SSM+vue的高校普法系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 3律师功能模块 4学生功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获…...

pytest教程-47-钩子函数-pytest_sessionfinish
领取资料,咨询答疑,请➕wei: June__Go 上一小节我们学习了pytest_sessionstart钩子函数的使用方法,本小节我们讲解一下pytest_sessionfinish钩子函数的使用方法。 pytest_sessionfinish 钩子函数在 Pytest 测试会话结束时调用,…...

如何使用Python下载哔哩哔哩(Bilibili)视频字幕
在本文中,我将向大家展示如何使用Python下载哔哩哔哩(Bilibili)视频的字幕。通过这个方法,你可以轻松地获取你喜欢的视频的字幕文件,方便学习和交流。 准备工作 在开始之前,我们需要安装一些必要的库&…...

IP代理网络协议介绍
在IP代理页面上,存在HTTP/HTTPS/Socks5三种协议。它们都是客户端与服务器之间交互的协议。 HTTP HTTP又称之为超文本传输协议,在因特网使用范围广泛。它是一种请求/响应模型,客户端向服务器发送请求,服务器解析请求后对客户端作出…...
渗透相关面试+流量分析
文章目录 简单自我介绍上一个工作的主要内容Hvv的分组和流程你在hvv/攻防演练中取得了哪些成绩? 二、渗透相关面试题基础端口号以及入侵方式OSI七层协议响应状态码都有哪些?**WAF和IPS的区别**盲注是什么?java内存马类型**内存马有几种类型**…...

Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...

【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...

深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...

排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...