esp32cam micropython使用I2S驱动DAC模块播放音频
Posted qq_33130395
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了esp32cam micropython使用I2S驱动DAC模块播放音频相关的知识,希望对你有一定的参考价值。
研究了一下,发现esp32cam的两个DAC引脚被摄像头占用了,分别是25和26。我们只能用I2S总线输出数字音频,之后用MAX98357模块将数字信号转成模拟信号。下面是代码:
导入模块和定义引脚:
from machine import I2S
from machine import Pin
import time
# 初始化引脚定义
sck_pin = Pin(14) # 串行时钟输出
ws_pin = Pin(13) # 字时钟
sd_pin = Pin(12) # 串行数据输出
# 初始化i2s
audio_out = I2S(1,sck=sck_pin,
ws=ws_pin,
sd=sd_pin,
mode=I2S.TX,
bits=16,
format=I2S.MONO,
rate=16000,
ibuf=20000)
# 下面是官方文档,Google翻译的,不准确,可以自己去翻看
'''
sck 是串行时钟线的引脚对象
ws 是单词选择行的引脚对象
sd 是串行数据线的引脚对象
mode 指定接收或发送
bits 指定样本大小(位),16 或 32
format 指定通道格式,STEREO(左右声道) 或 MONO(单声道)
rate 指定音频采样率(样本/秒)
ibuf 指定内部缓冲区长度(字节)
'''
引脚接线:
ESP32 CAM --- MAX98357
GPIO14 --- BCLK
GPIO13 -- LRC
GPIO12 -- DIN
GND -- GND
5V -- VCC
(建议焊接,插接的方式会导致杂音或者无法播放)
代码部分:
方法一:
wavtempfile = "temp.wav"
wav = open(wavtempfile,'rb')
print('播放音频')
# 读取音频文件的二进制数据
buf = wav.read()
# wav文件的头部数据,不是实际的音频数据,是文件信息,所以我们要丢弃这部分数据
bufhead = 44
# 实际音频数据大小,等于总大小减去头部信息的大小
bufsize = len(buf) - bufhead
# 缓冲区大小,前面初始化的时候设置的最后一个参数
bufcap = 20000
# 写入DAC的次数
bunum = 1
# 要写入的数据 开始位置
bufstart = bufhead
# 循环读取
while bufsize:
# 读取结束位置,等于读取次数*缓冲区大小
bufend = bufcap * bunum
# 当结束位置大于总数据长度的时候,结束位置等于数据最后一位
if bufend > len(buf):
bufend = len(buf)
# 要写入的数据
bufwrite = buf[bufstart:bufend]
# 写入数据
num_written = audio_out.write(bufwrite)
# 总大小减去每次写入的大小
bufsize -= len(bufwrite)
# 重新设置读取位置,为上次结束后一位
bufstart = bufend
# 读取次数
bunum = bunum + 1
# 在最后一个文件区间写入完毕后,计算最后的文件播放需要的时间,不然会播放不完整
times = len(bufwrite) * (1/16000)
方法二:
wavtempfile = "temp.wav"
wav = open(wavtempfile,'rb')
# 前进到WAV文件中数据段的第一个字节
pos = wav.seek(44)
#分配样本数组
#用于减少while循环中堆分配的内存视图
wav_samples = bytearray(1024)
wav_samples_mv = memoryview(wav_samples)
print('播放音频')
#从WAV文件中连续读取音频样本
#并将其写入I2S DAC
while True:
try:
num_read = wav.readinto(wav_samples_mv)
num_written = 0
#WAV文件结束
if num_read == 0:
break
#前进到数据段的第一个字节
# pos = wav.seek(44)
else:
#循环,直到所有样本都写入I2S外围设备
while num_written < num_read:
num_written += audio_out.write(wav_samples_mv[num_written:num_read])
except Exception as e:
print('错误,',e)
break
times = len(wav_samples_mv[num_written:num_read])
最后关闭文件,注销i2s
wav.close()
# 等待播放完毕
time.sleep(times )
# 取消初始化 I2S 总线
audio_out.deinit()
完成后:
esp32cam使用i2s驱动max98357播放音频
如果使用SD卡,必看
21.09.08补充:
以下补充针对esp32cam,esp32引脚足够,自己设置SD卡引脚就行
因为esp32cam的内存太小,我尝试把音频文件放到SD卡内读取,结果会出现读取时扬声器电流声和仅读取一次后就无法再次使用SD卡。这是因为GPIO12、13、14、15在挂载SD卡的时候被占用了,这几个引脚刚好又被用作扬声器,所以在挂载和读取的时候会有电流声。
因为内存卡我在上电之后就马上挂载了,在后面的代码中才实例化I2S。所以这几个引脚一开始被SD占用,后来被扬声器占用,就会出现无法再次读取的情况。
import machine
print('挂载sd卡...')
try:
os.mount(machine.SDCard(), "/sd")
print('挂载成功,路径为:"/sd"')
# 卸载
# os.umount("/sd")
except Exception as e:
print(e)
# I2S
from machine import I2S
sck_pin = Pin(14)
ws_pin = Pin(13)
sd_pin = Pin(12)
audio_out = I2S(
1,
sck=sck_pin,
ws=ws_pin,
sd=sd_pin,
mode=I2S.TX,
bits=16,
format=I2S.MONO,
rate=16000,
ibuf=20000)
我们可以将代码设置成
挂载SD卡->读取音频文件到内存->注销挂载SD卡->实例化I2S->播放音频->注销I2S->挂载SD卡
这样就可以反复读取内存卡了。不过依然会存在电流声,所以我选择了另一种方案:实时传输音频。
import io
import urequests
# 音频文件
wavname = 'test.wav'
# 请求音频文件
wavbuf = urequests.get('http://www.xxx.com/music/%s' % wavname).content
# 数据存到内存
wav = io.BytesIO(wavbuf)
# 以打开文件的方式读取内存数据
buf = wav.read()
因为在我的代码中socket已经被占用,所以我用的urequests下载wav文件来播放(如果socket没被占用,则可以用socket分片传输,用多线程边下边播,完美解决文件下载延迟问题)。解决电流声的同时,因为服务器和单片机在一个局域网,所以加载也没有延迟,完美解决~
以上是关于esp32cam micropython使用I2S驱动DAC模块播放音频的主要内容,如果未能解决你的问题,请参考以下文章
ESP32-CAM 使用 MicroPython 进行开发 - 使用图形化工具 Thonny (Mac)
esp32 cam+esp8266用micropython实现人脸识别开门