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实现人脸识别开门

自行编译micropython固件刷入ESP32 cam,并测试拍照及图传

nanoFramework 中的 ESP32 I2S

ESP32 MicroPython 调整分区大小

ESP32使用I2S控制ADC和DAC