目录
前言
I2S简介
TDM 通信模式(标准)
PDM 通信模式
. 对比总结
为什么要学习I2S
PCM原始数据
I2S录制声音
I2S播放声音
WAV音频
WAV 文件头结构(44 字节)
解析wav格式数据
struct.unpack 的基本用法
格式化字符串 (fmt)
示例 1:解析单个值
示例 2:解析多个值
示例 3:解析混合类型
示例 4:解析字符串
示例 5:解析 WAV 文件头
注意事项
总结
实操演练
保存wav格式数据
结语
在智能硬件和物联网设备蓬勃发展的今天,高质量的音频处理能力已成为许多嵌入式项目的核心需求。ESP32-S3芯片内置的I2S(Inter-IC Sound)接口为开发者提供了专业级的数字音频解决方案。本文将系统介绍I2S协议的工作原理,深入解析ESP32-S3的I2S外设特性,并通过完整的代码示例展示从音频采集、处理到播放的全流程开发方法。无论您是希望为智能设备添加语音功能,还是开发专业的音频处理应用,本文都将为您提供实用的技术指导和最佳实践。
I2S(Inter-IC Sound,集成电路内置音频总线)是一种同步串行通信协议,通常用于在两个数字音频设备之间传输音频数据。
ESP32-S3 包含 2 个 I2S 外设。通过配置这些外设,可以借助 I2S 驱动来输入和输出采样数据。
I2S 总线包含以下几条线路:
I2S 总线包含以下几条线路:
每个 I2S 控制器都具备以下功能,可由 I2S 驱动进行配置:
每个控制器都有独立的 RX 和 TX 通道,连接到不同 GPIO 管脚,能够在不同的时钟和声道配置下工作。注意,尽管在一个控制器上 TX 通道和 RX 通道的内部 MCLK 相互独立,但输出的 MCLK 信号只能连接到一个通道。如果需要两个互相独立的 MCLK 输出,必须将其分配到不同的 I2S 控制器上。
特性 |
TDM |
PDM |
核心目标 |
多路信号时分复用 |
高精度模数信号转换 |
适用场景 |
周期性数据(语音、固定速率流) |
高动态模拟信号(音频、传感器) |
抗噪能力 |
依赖信道质量 |
强(数字脉冲抗干扰) |
硬件复杂度 |
中等(需同步电路) |
低(单比特量化) |
延迟 |
低(固定时隙) |
较高(过采样+滤波) |
参考链接: I2S - ESP32-S3 - — ESP-IDF 编程指南 v5.4.1 文档
总之I2S有助于开发高质量的音频应用,扩展项目功能,尤其在物联网和智能设备领域具有广泛应用。丰富的资源和强大的硬件支持使得学习和开发更加便捷。
"""
使用I2S读取数据
数据宽度16bit
采样率16000Hz
缓冲区大小1024
"""
from machine import I2S
from machine import Pin
import time
sck_pin = Pin(14)
ws_pin = Pin(13)
sd_in_pin = Pin(12)
sd_out_pin = Pin(45)
audio_in = I2S(0, sck=sck_pin, ws=ws_pin, sd=sd_in_pin,
mode=I2S.RX, # only RX mode available
bits=16, # 数据宽度16bit,2字节
format=I2S.MONO, # 单通道MONO, 双通道STEREO
rate=16000, # 采样率16000Hz
ibuf=2048 # 缓冲区大小1024字节
)
print("I2S init complete!")
# 等待I2S初始化完成
# time.sleep_ms(500)
# 所有数据的列表
frames = []
print("开始录制...")
# 录制5s
start = time.time()
# 读取数据
while True:
if time.time() - start > 5:
break
# 创建一个字节数组
buf = bytearray(1024)
num = audio_in.readinto(buf)
frames.append(buf)
# 将音频数据写到文件
with open("audio.pcm", "wb") as f:
for frame in frames:
f.write(frame)
audio_in.deinit();
print("录音结束:", len(frames), "帧")
# 合并所有数据
data = b''.join(frames)
print("数据长度:", len(data))
"""
使用I2S播放数据
数据宽度16bit
采样率16000Hz
缓冲区大小1024
"""
from machine import I2S
from machine import Pin
import time
sck_pin = Pin(14)
ws_pin = Pin(13)
sd_in_pin = Pin(12)
sd_out_pin = Pin(45)
# sd引脚要设置为sd_out_pin
# 这里要注意用I2S.TX
audio_i2s = I2S(0, sck=sck_pin, ws=ws_pin, sd=sd_out_pin,
mode=I2S.TX, # only TX mode available
bits=16, # 数据宽度16bit,2字节
format=I2S.MONO, # 单通道MONO, 双通道STEREO
rate=16000, # 采样率16000Hz
ibuf=2048 # 缓冲区大小1024字节
)
print("I2S init complete!")
# 等待I2S初始化完成
#time.sleep_ms(500)
# 读取音频文件
print("playing...")
counter = 0
with open("./audio.pcm", "rb") as f:
while True:
buffer = f.read(1024)
if buffer:
print("counter: ", counter)
counter+=1
audio_i2s.write(buffer)
else:
break
audio_i2s.deinit()
print("play complete...")
WAV 文件的前 44 个字节是文件头部分,包含了音频文件的元数据(如采样率、位宽、声道数等)。WAV 文件头遵循 RIFF 格式规范,以下是其详细结构:
偏移量 |
字段名称 |
大小(字节) |
描述 |
0 |
Chunk ID |
4 |
固定为 ,表示文件是一个 RIFF 格式的文件。 |
4 |
Chunk Size |
4 |
文件总大小减去 8 字节(即文件大小 - 8)。 |
8 |
Format |
4 |
固定为 ,表示这是一个 WAV 文件。 |
12 |
Subchunk1 ID |
4 |
固定为 ,表示接下来的部分是格式信息。 |
16 |
Subchunk1 Size |
4 |
格式信息的大小(通常是 16 字节)。 |
20 |
Audio Format |
2 |
音频格式(PCM 为 1,表示未压缩)。 |
22 |
Num Channels |
2 |
声道数(1 表示单声道,2 表示立体声)。 |
24 |
Sample Rate |
4 |
采样率(如 44100 Hz)。 |
28 |
Byte Rate |
4 |
每秒的字节数( )。 |
32 |
Block Align |
2 |
每个采样点的字节数( )。 |
34 |
Bits Per Sample |
2 |
每个采样点的位数(如 16 位)。 |
36 |
Subchunk2 ID |
4 |
固定为 ,表示接下来的部分是音频数据。 |
40 |
Subchunk2 Size |
4 |
音频数据的大小(字节数)。 |
44 |
Data |
N |
音频数据(从第 44 字节开始)。 |
struct.unpack
是 Python 中用于将二进制数据解析为 Python 数据类型的函数。它通常用于处理二进制文件、网络协议数据或硬件设备的原始数据。struct.unpack
是 struct.pack
的逆操作,后者用于将 Python 数据类型打包为二进制数据。
struct.unpack
的基本用法struct.unpack(fmt, buffer)
fmt
:格式化字符串,指定如何解析二进制数据。buffer
:包含二进制数据的字节对象(如 bytes
或 bytearray
)。fmt
)格式化字符串由以下部分组成:
@
:本地字节顺序(默认)。=
:本地字节顺序,忽略对齐。<
:小端序(低位字节在前)。>
:大端序(高位字节在前)。!
:网络字节顺序(大端序)。c
:字符(1 字节)。b
:有符号字节(1 字节)。B
:无符号字节(1 字节)。?
:布尔值(1 字节)。h
:有符号短整型(2 字节)。H
:无符号短整型(2 字节)。i
:有符号整型(4 字节)。I
:无符号整型(4 字节)。l
:有符号长整型(4 字节)。L
:无符号长整型(4 字节)。q
:有符号长长整型(8 字节)。Q
:无符号长长整型(8 字节)。f
:浮点型(4 字节)。d
:双精度浮点型(8 字节)。s
:字符串(需要指定长度,如 10s
表示 10 字节的字符串)。p
:Pascal 字符串(1 字节长度 + 字符串)。x
:填充字节(跳过 1 字节)。import struct
# 二进制数据(4 字节的无符号整型)
buffer = b'\x01\x00\x00\x00'
# 解析为无符号整型
value = struct.unpack('
import struct
# 二进制数据(2 个有符号短整型)
buffer = b'\x01\x00\x02\x00'
# 解析为 2 个有符号短整型
values = struct.unpack('<2h', buffer)
print(values) # 输出: (1, 2)
import struct
# 二进制数据(1 个无符号短整型 + 1 个浮点型)
buffer = b'\x01\x00\x00\x00\x00\x00\x80\x3f'
# 解析为无符号短整型和浮点型
values = struct.unpack('
import struct
# 二进制数据(10 字节的字符串)
buffer = b'hello\x00\x00\x00\x00\x00'
# 解析为 10 字节的字符串
value = struct.unpack('<10s', buffer)
print(value) # 输出: (b'hello\x00\x00\x00\x00\x00',)
import struct
# 假设这是 WAV 文件的前 44 字节
wav_header = b'RIFF\x24\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00\x44\xAC\x00\x00\x10\xB1\x02\x00\x04\x00\x10\x00data\x00\x00\x00\x00'
# 解析 WAV 文件头
chunk_id = struct.unpack('<4s', wav_header[0:4])[0]
chunk_size = struct.unpack('
<
)和大端序(>
)是最常用的两种字节顺序。@
或 =
来指定本地字节顺序。struct.error
。struct.unpack
始终返回一个元组,即使只解析一个值。struct.unpack
是 Python 中处理二进制数据的强大工具。struct.unpack
非常有用。from machine import I2S, Pin
import struct
# 配置I2S
i2s = I2S(
0, # I2S编号
sck=Pin(14), # 时钟引脚
ws=Pin(13), # 字选择引脚
sd=Pin(45), # 数据引脚
mode=I2S.TX, # 发送模式
bits=16, # 数据位宽
format=I2S.MONO, # 单声道
rate=16000, # 采样率
ibuf=40000 # 输入缓冲区大小
)
# 解析WAV文件头
def parse_wav_header(file):
header = file.read(44) # WAV文件头长度为44字节
if header[0:4] != b'RIFF' or header[8:12] != b'WAVE':
raise ValueError("不是有效的WAV文件")
ret = struct.unpack("4s",header[0:4])
print("ret=",ret,header[0:4].decode())
# 提取采样率、位宽、声道数等信息
sample_rate = struct.unpack('
from machine import I2S, Pin
import struct
# 配置I2S
i2s = I2S(
0, # I2S编号
sck=Pin(14), # 时钟引脚
ws=Pin(13), # 字选择引脚
sd=Pin(12), # 数据引脚
mode=I2S.RX, # 接收模式
bits=16, # 数据位宽
format=I2S.MONO, # 单声道
rate=16000, # 采样率
ibuf=40000 # 输入缓冲区大小
)
# WAV文件参数
sample_rate = 16000 # 采样率
bits_per_sample = 16 # 位宽
num_channels = 1 # 单声道
duration = 5 # 录制时长(秒)
buffer_size = 1024 # 每次读取的缓冲区大小
# 计算总数据量
total_samples = sample_rate * duration
total_data_size = total_samples * num_channels * (bits_per_sample // 8)
# 创建WAV文件头
def create_wav_header(sample_rate, bits_per_sample, num_channels, data_size):
# WAV文件头格式
header = bytearray()
header.extend(b'RIFF') # Chunk ID
header.extend(struct.pack('
通过本文的学习,我们掌握了I2S音频接口在ESP32-S3平台上的完整开发流程,从基础协议理解到实际的音频采集与播放实现。I2S技术为嵌入式系统带来了专业级的音频处理能力,为智能家居、可穿戴设备、工业控制等领域的音频应用开发提供了强大支持。希望本指南能成为您音频开发路上的实用参考,期待看到您利用这些技术创造出更多创新的音频应用。技术的价值在于实践,愿您在嵌入式音频开发的道路上不断探索,创造出更多令人惊艳的作品!