文章目录[隐藏]
研究了一下,发现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('播放音频')
# 播放开始时间
start_time = time.ticks_us()
# 读取音频文件的二进制数据
buf = wav.read()
# wav文件的头部数据,不是实际的音频数据,是文件信息,所以我们要丢弃这部分数据
bufhead = 44
# 实际音频数据大小,等于总大小减去头部信息的大小
bufsize = len(buf) - bufhead
# 缓冲区大小,前面初始化的时候设置的最后一个参数
bufcap = 20000
# 音频总时长 us 文件大小(单位:B)/(采样率16000 x 每个采样点大小2 ÷ 秒换算成微秒1000000)
all_time = bufsize / 0.032
# 写入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
方法二:
wavtempfile = "temp.wav"
wav = open(wavtempfile,'rb')
# 前进到WAV文件中数据段的第一个字节
pos = wav.seek(44)
#分配样本数组
#用于减少while循环中堆分配的内存视图
wav_samples = bytearray(1024)
wav_samples_mv = memoryview(wav_samples)
print('播放音频')
# 音频总时长 us 文件大小(单位:B)/(采样率16000 x 每个采样点大小2 ÷ 秒换算成微秒1000000)
all_time = (len(wav)-44) / 0.032
#从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)
# 在首次执行的时候记录开始时间
if num_written == 0:
# 记录播放开始时间
start_time = time.ticks_us()
#循环,直到所有样本都写入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
最后关闭文件,注销i2s
# 等待音频播放完毕
while 1:
# 播放结束时间
end_time = time.ticks_us()
# 如果当前时间减去开始播放的时间大于音频时长
if (end_time - start_time) > all_time:
# 取消初始化 I2S 总线
audio_out.deinit()
# 停止等待
break
# 播放完毕
# 关闭文件
wav.close()
完成后:
esp32cam使用i2s驱动max98357播放音频
如果使用SD卡,必看
21.09.08补充:
以下补充针对esp32cam,esp32引脚足够,自己设置SD卡引脚后DAC输出就行,不用加max98357模块
因为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分片传输,用多线程边下边播,完美解决文件下载延迟问题)。解决电流声的同时,因为服务器和单片机在一个局域网,所以加载也没有延迟,完美解决~
版权声明:本文为CSDN博主「qq_33130395」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_33130395/article/details/120117741
暂无评论