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 案例

// decode
    offset = 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);

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

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

相关文章

Java 自然排序和比较器排序区别?Comparable接口和Comparator比较器区别?

注&#xff1a;如果你对排序不理解&#xff0c;请您耐心看完&#xff0c;你一定会明白的。文章通俗易懂。建议用idea运行一下案例。 1&#xff09;自然排序和比较器排序的区别&#xff1f; 自然排序是对象本身定义的排序规则&#xff0c;由对象实现 Comparable 接口&#xff…

思科模拟器--2.静态路由和默认路由配置24.5.15

首先&#xff0c;创建三个路由器和两个个人电脑。 接着&#xff0c;配置两台电脑的IP&#xff0c;子网掩码和默认网关 对Router 0&#xff0c;进行以下命令&#xff1a; 对Router进行以下命令&#xff1a; 对Router2进行以下命令&#xff1a; 本实验完成。 验证&#xff1a;PC…

Leetcode39.组合总和

文章目录 题目描述解题思路重复子集剪枝 代码 题目 参考题解 题目描述 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返…

Android Studio kotlin 转 Java

一. 随笔记录 java代码可以转化成kotlin代码&#xff0c;当然 Kotlin 反过来也可以转java 在Android Studio中 可以很方便的操作 AS 环境&#xff1a;Android Studio Iguana | 2023.2.1 二. 操作步骤 1.步骤 顶部Tools ----->Kotlin ------>Show Kotlin Bytecode 步…

错误: 找不到或无法加载主类问题(已解决)

今天在虚拟机中安装了idea2023.2的版本&#xff0c;运行代码时发现错误找不到主类&#xff01; 直接说结论&#xff1a; 我先clean了一下target&#xff0c;然后重新build&#xff0c;发现maven报错了&#xff0c;idea2023.2默认使用了内置的maven&#xff0c;然后我切换了一下…

ThreadLocal,一次到位

一、定义 ThreadLocal是线程私有变量&#xff0c;用于保存每个线程的私有数据。 那么什么情况下需要进行线程隔离 二、源码分析 public class ThreadLocalTest01 {ThreadLocal<Integer> t new ThreadLocal<>();public void test() {t.set(1);Integer integer…

MT3036 第一节离数课后

思路&#xff1a; 这道题与之前的表达式求值题目不同的是&#xff0c;有not这个单目运算符。而且如果表达式错误&#xff0c;要输入error。 把true和false成为操作数&#xff0c;把and or not成为运算符。 考虑error的情况&#xff1a; 1.and 和 or是双目运算符&#xff0c…

文心一言指令多样化,你知道的有哪些?

文心一言的指令非常多样化&#xff0c;可以根据用户的需求和场景进行灵活调整。以下是一些常见的文心一言指令示例&#xff1a; 知识问答&#xff1a; 帮我解释一下什么是芯片&#xff1f;中国的历史上有哪些重要的朝代&#xff1f;人工智能在未来会有哪些发展趋势&#xff1f;…

表白成功率百分百的向女朋友表白网页源代码,向女友表白HTML源代码

表白成功率百分百的向女朋友表白网页源代码&#xff0c;向女友表白HTML源代码 效果&#xff1a; 完整代码下载地址&#xff1a;向女友表白HTML源代码 <!DOCTYPE html> <!--STATUS OK--> <html><head><meta http-equiv"Content-Type" c…

P8805 [蓝桥杯 2022 国 B] 机房

P8805 [蓝桥杯 2022 国 B] 机房 分析 是一道lca题目&#xff0c;可以直接套模板 前缀和处理点权 具体思路&#xff1a; 1.n台电脑用n-1条网线相连&#xff0c;任意两个节点之间有且仅有一条路径&#xff08;拆分成各自到公共祖先节点的路径——lca&#xff09;&#xff1b;…

CAD插入文字到另一图形样式变相同

CAD从一张图形复制到另外一张图形后&#xff0c;文字样式变成一样是因为两张图所用的文字样式名称一样&#xff0c;但是样式里面的使用字体样式不一样。如下图所示&#xff0c;找到工具栏中的注释 &#xff0c;点击文字样式。里面就会显示当前图形中使用的样式名称及其对应的字…

TINA 使用教程

常用功能 分析-电气规则检查&#xff1a;短路&#xff0c;断路等分析- 直流分析 交流分析 瞬态分析 视图-分离曲线 由于输出的容性负载导致的振荡 增加5欧电阻后OK 横扫参数 添加横扫曲线的电阻&#xff0c;选择R3&#xff1a;8K-20K PWL和WAV文件的支持 示例一&#xff1a;…

ubuntu在conda环境中使用 pip install -r requirements.txt但是没有安装在虚拟环境中

whereis pip pip listubuntu在conda环境中使用pip install lpips0.1.3 但是安装在了这里 Requirement already satisfied: lpips0.1.3 in /home/uriky/anaconda3/lib/python3.11/site-packages (0.1.3) 就会出现黄色波浪&#xff0c;未在虚拟环境中安装包 解决办法1&#xff1…

[NOIP2011 普及组] 瑞士轮

[NOIP2011 普及组] 瑞士轮 题目背景 在双人对决的竞技性比赛&#xff0c;如乒乓球、羽毛球、国际象棋中&#xff0c;最常见的赛制是淘汰赛和循环赛。前者的特点是比赛场数少&#xff0c;每场都紧张刺激&#xff0c;但偶然性较高。后者的特点是较为公平&#xff0c;偶然性较低…

如何使用JMeter测试导入接口/导出接口?

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号&#xff1a;互联网杂货铺&#xff0c;回复1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 今天上班&#xff0c;被开发问了一个问题&#xff1a;JM…

html基础(全)

html简介 目录 什么是网页 什么是 HTML 常用浏览器 WebE标准的构成 基本语法概述 第一个HTML页面 文档类型声明标签 lang 语言种类 字符集 标题标签 段落和换行标签 文本格式化标签 div和span标签 图像标签和路径 超链接标签 表格的主要作用 表头单元格标签 列…

[华为OD] C卷 dfs 特殊加密算法 100

题目&#xff1a; 有一种特殊的加密算法&#xff0c;明文为一段数字串&#xff0c;经过密码本查找转换&#xff0c;生成另一段密文数字串。 规则如下 1•明文为一段数字串由0-9组成 2.密码本为数字0-9组成的二维数组 3•需要按明文串的数字顺序在密码本里找到同样的数字串…

基于SpringBoot设计模式之创建型设计模式·工厂方法模式

文章目录 介绍开始架构图样例一定义工厂定义具体工厂&#xff08;上衣、下装&#xff09;定义产品定义具体生产产品&#xff08;上衣、下装&#xff09; 测试样例 总结优点缺点与抽象工厂不同点 介绍 在 Factory Method模式中&#xff0c;父类决定实例的生成方式&#xff0c;但…

用红黑树封装出map与set

目录 一、红黑树的改造 节点结构的定义 迭代器类的实现 红黑树中提供迭代器 红黑树的主要代码 二、set的实现 三、map的实现 四、测试代码 map与set的底层都是红黑树&#xff0c;所以本篇文章就分享如何用同一颗红黑树封装出map与set 所以大家可以先去看一下我的讲解红…

第一个fyne应用

第一个fyne应用 由于在写一个milvus的图形化工具&#xff0c;方便客户端使用&#xff0c;调研了一下只有这fyne的go-gui的star最多&#xff0c;比较流行&#xff0c;因此打算使用这个框架来进行milvus的工具开发。 第一个fyne应用 依赖go.mod: module fynedemogo 1.20requi…