【Arduino + Linux】基于NodeMCU32实现WAV音频播放

3 篇文章 0 订阅
2 篇文章 0 订阅

NodeMCU32S是基于ESP32-D0WDQ6集成芯片开发的,其内部存储资源比较紧缺,但是对于 wav 格式的音频而言是远远不够的,又因为该模块具有比较成熟的无线通信技术,所以就有一个想法:将音乐文件保存到服务器上,NodeMCU32 通过 WiFi 通讯方式将数据缓存到本地,然后完成音频播放。

源代码地址:https://github.com/npc-github-octocat/NodeMCU-WAV 🚀

一、Arduino环境配置

开发环境安装参考(虽然是esp8266但是操作大体上是一致的):Arduino入门——黑洞教室 🚀

在 Arduino IDE 中添加 ESP32 开发板管理器网址:https://dl.espressif.com/dl/package_esp32_index.json (参考:Arduino常用的附加开发版管理器网址 🚀)

二、音频数据收发

数据收发上参考 FTP 的传输协议,其中服务器程序运行在 Ubuntu 系统上,客户端程序运行在 NodeMCU32上。

2.1、服务器程序

服务器程序工作流程如下:

在这里插入图片描述

开发内容包括:

  • socket套接字初始化(socket/bind/listen/accept/read/write/close)
  • 地址快速重用(setsockopt)
  • 网络心跳检测(setsockopt)
  • 发送超时确认(setsockopt)
  • 音频文件打开关闭以及读取操作(open/read/close)
  • 目录文件打开以及读取操作(opendir/readdir/closedir)

说明一:地址快速重用
服务器退出后,由于TCP在4次分手过程中存在TIME_WAIT状态,该状态会持续2MSL时间,在TIME_WAIT退出后,套接字被删除,有时候虽然服务器程序已经关闭,但是还是处于TIME_WAIT的状态,此时又启动服务器程序,就会出现“试图绑定一个已经在使用的端口”的错误,所以程序中加入了地址快速重用。

说明二:心跳检测
程序中有一处调用read函数,目的是接收客户端发送的请求,目前套接字的属性为阻塞等待,即没有数据过来的时候就会一直等待下去(可以设置等待时长,但是感觉用户需要在规定时间内发送数据,在该场景下体验不是很友好),在调试过程中,每次重启客户端的同时又需要重启服务器程序,比较麻烦。。。是否有这么一种状态监测,就是判断当前的连接状态是否正常,如果正常就一直保持,如果连接不正常(无法通讯),那么退出当前阻塞等待。

说明三:发送超时确认
心跳检测存在一个问题(可能有一些属性我不知道怎么配置),就是只是针对于read函数起作用,当write函数处于阻塞状态时,就会一直阻塞下去,所以这里加入了发送超时检测。
主要是为了解决当发送音频文件时,客户端忽然下线,不至于服务器一直处于阻塞等待中。

问题: TCP连接的时候,服务器端等待accept的连接,当客户端连接的时候后,就出现accept出现错误,
原因: accept中第三个参数中指针指向错误。目前还不清楚accept内部实现的机理。
解决: 方式一commd数组初始化成0,方式二将第三个参数初始化。

参考:
[1]: 用C语言实现FTP 🚀
[2]: bind为什么会出现地址重用 🚀
[3]: Linux 心跳检测 🚀
[4]: SO_KEEPALIVE does not work during a call to write()? 🚀

2.2、客户端程序

客户端程序工作如下:

在这里插入图片描述

说明:available函数 & connected函数
available 用于获取接收缓冲区中的数据,其返回值为缓存区可读取字节数,当缓冲区数据为0时,自动返回,不会阻塞等待。这时候就会有一个问题,当数据还没到达时,程序判断缓冲区数据为0,就会跳过该部分,如果在接收大文件时,程序判断为接收完成,那么就会导致出现错误。
因此,在这里就加入了connected函数,做进一步的判断,connected 函数用于判断连接是否成功,当出现断连情况时就会返回0值,服务器这边设定为文件传输结束时候立即关闭TCP连接,以此作为结束的标志。connected 函数如果为正常连接,available函数判断缓冲区为空,那么就会等待。只有连接断开且缓冲区为空时,可认为传输结束了。

参考:

[1]: arduino串口接收数据包_Arduino 通信 🚀
[2]: ESP8266之WiFiClient库学习 🚀
[3]: ESP-IDF编程指南 🚀

三、编写音频驱动

3.1、音频基础知识

声音通过振动的方式在介质中以连续的波的方式传播,正常人可以听到声音的频率范围为 20Hz~20KHz,现实存在的声音是模拟量,其中记录和播放是对声音的常见操作,记录声音是将模拟量转换为数字量的过程,一般可以分为三个过程,分别为采样、量化、编码,用一个比源声音频率高的采样信号去量化源声音。记录每个采样点的值,最后如果把所有采样点数值连接起来与源声音曲线是互相吻合的,只是它不是连续的。

下图中,两条蓝色虚线距离就是采样信号的周期,即对应一个采样频率,如果采样频率越高,那么最后得到的结果就与源声音曲线的吻合程度越高。一般使用44.1KHz采样频率即可得到高保真的声音。

影响吻合程度的还有一个关键参数就是量化位数,量化位数表示每个采样点用多少位来表述数据范围,常用有16bit24bit32bit,位数越高最后还原得到的音质越好,数据量也会越大。(ADC数据采集的时候,假设电压为1v,位数8位,那么精度为1/28,倘若位数越高,精度就会越高)

在这里插入图片描述

比如,电话就是3KHz取样的7位声音,而CD是44.1KHz取样的16位声音,所以CD就比电话更清楚。

在当今的主流采集卡上,采样频率一般共分为22.05HHz、44.1KHz、48KHz三个等级。

  • 22.05KHz只能达到FM广播的声音品质。
  • 44.1KHz则是理论上的CD音质界限。
  • 48KHz则更加精确一些,对于高于48KHz的采样频率人耳已经无法辨别出来了,所以电脑上没有什么应用价值。

一般人说话基频范围在50-700Hz,而人能听到的频率到20Hz-20KHz,这个频率可以认为是采样频率,当声音频率超过了该频率,就无法完成采样,交给大脑去处理。

  • 以波形表示的频率范围通常被称为带宽
  • 立体声指的是双声道。
  • 码率=取样频率×量化精度×声道数。
  • 分贝:两个数值的对数比率,这两个数值分别是测量值和参考值(也称为基准值),不同的参考值,比率也会不同,分贝存在两种定义情况,一种为功率之比,一种为幅值之比,它是一种无量纲,常应用于通信、声学、振动、电子学等领域。

参考:
[1]: FFmpeg的音频处理详解 🚀
[2]: 人说话声音的频率范围是多少? 🚀
[3]: 什么是分贝dB? 🚀
[4]: [野火EmbedFire]《STM32库开发实战指南——基于野火挑战者开发板》

3.2、WAV格式认识

WAV 是微软公司开发的一种音频格式文件,数据本身的格式为 PCMADPCM,其中 PCM 属于无损音乐格式的一种,绝大部分 WAVE 文件是 PCM 编码,ADPCM(自适应差分脉冲编码调制)属于有损压缩,现在几乎不用。

PCM数据格式

  • 音频数据是否是有符号的。通常情况下都是有符号的。若是将有符号的数据当做无符号的数据来处理将会使声音听来很刺。
  • 字节序指的是little-endian还是big-endian。表示音频数据的存储字节序。通常均为little-endian。

WAVE 文件是一种 RIFF 文件,RIFF chunk 包括两个子chunk,ID 分别为 fmt 和 data,还有一个可选的 fact chunk。

  • Fmt chunk:用于表示音频数据的属性,如编码方式、声道数量、采样频率等。
  • fact chunk:一般当 WAVE 文件由某些软件转换而成就包含 fack chunk。
  • data chunk:“data”字符串+音频数据长度+音频数据。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(8bit 声音数据格式在 data chunk 数据排列格式同 16bit 一样)

通过 WinHex 软件查看 WAVE 文件。

在这里插入图片描述
在这里插入图片描述

总结起来,WAVE格式如下所示:

在这里插入图片描述

WAVE文件头的数据用来说明该文件的一些基本信息,后面的数据为音频文件,由于是PCM编码格式,音频数据为模型信号经过ADC转换后的数字量,不需要经过解码字节将数据发送到DAC中,由DAC将数字量转换成模拟量,通过扬声器即可播放音频。

参考:
[1]: [野火EmbedFire]《STM32库开发实战指南——基于野火挑战者开发板》
[2]: RIFF和WAVE音频文件格式 🚀

3.3、mp3转WAV

这里需要用到一款软件Audacity (下载地址:https://audacity.onl/

1、文件 ——> 打开 ——> 选择mp3文件
在这里插入图片描述

2、文件 ——> 导出 ——> 导出为WAV ——> 编码:Unsigned 8-bit PCM ——> 保存

在这里插入图片描述

导出的文件:

  • 无符号 8 位 PCM
  • 双通道
  • 44100Hz 采样频率

3.4、硬件连接

这里可以使用扬声器播放也可以使用耳机,其发声的原理都是一样的。3.5mm耳机接口总共有四级接口(美标:从最前头开始数第1、2、3、4节,分别是左声道/右声道/地线/MIC;国标:从最前头开始数第1、2、3、4节,分别是左声道/右声道/MIC/地线 🚀)可以直接用导线将耳机接口与NodeMCU32的GPIO25/GPIO26/GND直接连接。

在这里插入图片描述

而对于扬声器而言,可以同耳机一样直接连接,但是由于NodeMCU32带负载能力比较弱,声音会比较低,这里需要在它两之间加一个功率放大器,这里选用的是PAM8406数字功放板

在这里插入图片描述

NodeMCU32是直接连在电脑上的,在调试过程中,由于5v的供电不稳定,导致电脑上出现桌面图标闪现的情况,换了个电源后恢复正常。正常情况下,播放音频时,直接连接NodeMCU32是可以带的动的。

3.5、I2S使用内部DAC注意事项

  • NodeMCU32内部有两个I2S外设模块,均支持DMA功能,但是只有I2S_NUM_0支持将数据(音频数据,而不是音频文件开头的用于说明类型的数据)经过DMA发送给内部DAC。
  • DMA传输一次最少16位,而前面转换的音频文件为8位的,所以需要先将该数据由8位扩展成16位,由于DAC只会取8位数据进行输出,且这8位是16位数据中的高8位,所以这里在做数据位扩展的时候不要弄反了。
  • 如果将8位的数据不经过扩展,直接发送的话,倘若左右声道数据一样,那么播放时,听起来像被加速了。

参考:
[1]: ESP2-I2S音频播放笔记 🚀
[2]: ESP-IDF编程指南——I2S 🚀

下图所示为用户端:
在这里插入图片描述

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值