2024-8-22 13:34:03 [显示全部楼层]
702浏览
查看: 702|回复: 1

[入门] 无需MP3模块!开发板+扬声器就能播放音频!

[复制链接]
本帖最后由 TRIM 于 2024-9-9 20:41 编辑

1.引入

我们做项目和作品时,难免会遇到一些地方需要语音提示播放音频的地方。
我浏览了许多类似的作品,大多都是使用了语言合成或MP3模块来实现此功能。
但是,短短几秒钟的音频,真的需要再加一个模块来实现吗?

没必要。

不仅没有物尽其用,还得花钱,还占地方......
那有没有什么办法,仅仅接一个扬声器,就能够使其播放音频呢?

当然有!

我们先找一段音频来看看:

这是一段“你干嘛哎哟”的音频:
无需MP3模块!开发板+扬声器就能播放音频!图1

将其一部分放大,可以看到这样的图像:
无需MP3模块!开发板+扬声器就能播放音频!图2

放大一点?
无需MP3模块!开发板+扬声器就能播放音频!图3

可以看到一堆小点。其中,点的横坐标代表了时间纵坐标代表了此时扬声器的振幅
如果我们把这些点集的纵坐标用一个列表表示出来,知道了每个点之间间隔的时间,再控制开发板,在对应的时间向扬声器输出对应的电压,不就可以了吗?
理论上是可以的!

2.转换音频

问题来了:如何将音频转换成这样的列表呢?

PCM来了!(关于PCM

PCM编码是最原始的音频编码,其他编码都是在它基础上再次编码和压缩的。它的数据正好符合我们所需要的列表!
我们有很多方式将音频转换成PCM格式

我使用了ffmpeg
  1. ffmpeg -i [需要转换的文件] -f u8 -ar [采样率] -ac 1 output.pcm
复制代码

(当然,这需要你使用ffmpeg工具,并添加至环境变量)ffmpeg官网  
(你也可以使用其他的软件转换,但必须转换编码为单声道的无符号8位PCM)

“u8”代表无符号的8位,也就是说,数据的范围是0-255,有利于在开发板上使用。
“采样率”指的是每秒钟有多少个点。一般的采样率有8000  11025  16000  22050  32000  44100 Hz,采样率越高,音质越好,但是占用的空间也就越大。
在开发板处理较慢,仅有几MB的Flash的情况下,极力推荐采样率为8000!


我们成功获得了PCM格式的音频:output.pcm!
但是......打开怎么是乱码?
哦,PCM为了节省空间,使用了二进制来保存数据。我们再将其转换成我们想要的就行啦!

我使用了python来转换,很简单的:
  1. # 打开pcm文件
  2. with open('output.pcm', 'rb') as pcm_file:
  3.     pcm_data = pcm_file.read()
  4. # 将其转换成列表
  5. pcm_list = list(pcm_data)
  6. # 保存到output.txt文件里
  7. with open('output.txt', 'w') as txt_file:
  8.     txt_file.write(str(pcm_list))
复制代码

运行,搞定!

打开文件,我们看到了一大堆数组,这就是我们可以看懂的数字了
无需MP3模块!开发板+扬声器就能播放音频!图4

接下来,我们只需编写程序,让开发板间隔一定时间,依次向扬声器输出这些值,就可以了!

3.编写程序

Arduino
Arduino已经有现成的库了,在库管理器搜索“PCM”即可
无需MP3模块!开发板+扬声器就能播放音频!图5

下面是这个库给的示例程序:(点击这里查看文字示例
  1. #include <PCM.h>
  2. const unsigned char sample[] PROGMEM = {
  3.   在这里输入获得的数据(注意去掉中括号)
  4. };
  5. void setup()
  6. {
  7.   startPlayback(sample, sizeof(sample));
  8. }
  9. void loop()
  10. {
  11. }
复制代码
ps:这个库把扬声器的引脚写死了(20),要改得去库文件里改

ESP32
目前没找到相关的库!

所以我自己写了个:
  1. #include <Arduino.h>
  2. // 储存数据到Flash,Flash容量一般会高一些
  3. const uint8_t myArray[] PROGMEM = {
  4.   在这里输入获得的数据(注意去掉中括号)
  5. };
  6. // 计算长度
  7. const int arraySize = sizeof(myArray) / sizeof(myArray[0]);
  8. // 定义扬声器的管脚
  9. const int buzzerPin = 20;
  10. void setup(){
  11.   // 初始化PWM,中间是PWM的频率,高一些会更好,但如果不发声音说明太高了,开发板不支持,需要调小一些
  12.   ledcAttach(buzzerPin, 120000, 8);
  13. }
  14. void loop(){
  15.   for(int n = 0; n <= arraySize; n++){
  16.     // 从Flash读取相应的值   
  17.     uint8_t value = pgm_read_byte(&myArray[n]);
  18.     // 设置扬声器引脚的PWM
  19.     ledcWrite(buzzerPin, value);
  20.     // 这里等待的时间为采样率的倒数:1/8000秒,即125微秒,如果采样率不同需要调整
  21.     // 实际上由于执行代码需要时间,这个值需要调小一些,播放才是正常速度
  22.     // 如果大佬们能使用定时器来完成会更好哦!(强烈建议!)
  23.     delayMicroseconds(125);
  24.   }
  25.   delay(1000);
  26. }
复制代码

将程序上传,连接好扬声器,就可以发声啦!

4.闲谈

由于开发板储存较小,我的ESP32 C6(4MB)也只能储存22秒的音频数据
(这是在未配置Flash大小的情况下,大佬们也可手动配置,理论上每MB可以储存15秒
但是对于一些小音频(提示语音、按钮音频等)来说,肯定是够的
实在不够,那就外接一张SD卡吧!

另外,蜂鸣器也可以代替扬声器,只不过音质差点罢了,但把它放在小盒子里音质又会好一些


总之,我的ESP32 C6和Arduino nano都成功地发出了清晰的“你干嘛哎哟”,期待大家利用此方法做出更加有创意的作品!


另外,如果有更好的方法或找到了更好的库,欢迎大佬们评论区留言!

附上音频、Python文件及转换好的PCM文件:

1.zip

75.62 KB, 下载次数: 300

TRIM  初级技匠
 楼主|

发表于 2024-9-9 21:34:18

一个错误:在 2024-9-9 20:41 之前下载的文件中,bat及pcm数据均为22.05khz采样率,需要自行修改
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail