ESP32 I2S音频总线学习笔记(五): 将inmp441采集到的音频发送至网络
简介
往期相关文章:
ESP32 I2S音频总线学习笔记(一):初识I2S通信与配置基础
ESP32 I2S音频总线学习笔记(二):I2S读取INMP441音频数据
ESP32 I2S音频总线学习笔记(三):I2S音频输出
ESP32 I2S音频总线学习笔记(四):INMP441采集音频并实时播放
上篇文章我们介绍了使用inmp441采集音频后通过pcm5102a模块进行实时播放,使用到了i2s输入与i2s输出,这个如果小伙伴没有pcm5102a或者i2s输出模块的话,可能就实现不了,那我们可不可以不使用i2s输出模块,仅使用inmp441实现呢。要实现这个方案,我们可以将inmp441采集到的音频发送至网络,比如发送给电脑接收,然后播放的话就用电脑端收听,这样就可以直接使用inmp441进行采集音频并在PC端实时播放了,当然这个播放的话会有延迟或者卡顿的现象,跟网络因素有关,实时性可能没有直接传输音频数据的好,但如果网络好的话,延迟大概是几毫秒,这个我认为还是可以接受的。
接下来本文将保留上篇文章的旋转编码器调整增益功能,在此基础上进行修改。音频数据的发送我选择使用UDP通信,相比较于TCP协议来说无需先建立连接之后再进行发送数据,协议简单数据传输会更为迅速,但是相对的数据也容易丢失,所以这也是为什么说会出现卡顿的原因。关于UDP通信我们前面有一篇文章有介绍,可以参考这篇文章:五、ESP32使用UDP通信
因为是在电脑上运行,所以还要在电脑上运行一个接收端的代码,本文使用python运行接收端,实现接收inmp441发送过来的音频。
硬件接线
ESP32和麦克风INMP441:
| ESP32 | INMP441 |
|---|---|
| D13 | SCK |
| D12 | WS |
| D14 | SD |
| 3.3V | VDD |
| GND | GND |
发送端
主要实现思路是通过UDP协议将inmp441采集到的音频数据,放入UDP发送的缓冲区,即使用size_t write(const uint8_t *buffer, size_t size)函数,在这之前需要进行wifi和udp的相关初始化。
首先是配置一下wifi,替换为自己的wifi名称和密码:
// WiFi 配置
const char* ssid = "YXDZ_wifi"; // 替换为您的WiFi SSID
const char* password = "12345678"; // 替换为您的WiFi密码
初始化udp:
// UDP 配置
WiFiUDP udp;
const char* udpAddress = "192.168.3.104"; // 替换为电脑的IP地址
const int udpPort = 12345; // UDP端口号
在loop函数中持续采集音频数据,并通过UDP不断发送音频数据:
int16_t buffer[BUFFER_SIZE];
size_t bytesRead;
i2s_read(I2S_MIC_NUM, buffer, BUFFER_SIZE * sizeof(int16_t), &bytesRead, portMAX_DELAY);// 增益处理,放大音量并限制范围for (int i = 0; i < BUFFER_SIZE; i++) {buffer[i] = buffer[i] * gainFactors[currentGainIndex];if (buffer[i] > 32767) buffer[i] = 32767;if (buffer[i] < -32768) buffer[i] = -32768;}// 通过 UDP 发送音频数据udp.beginPacket(udpAddress, udpPort); //准备发送数据到目标IP和目标端口udp.write((uint8_t*)buffer, bytesRead);udp.endPacket();//向目标IP目标端口发送数据
发送端完整代码如下,保留了旋转编码器调节增益功能,不需要可以不接。
#include <driver/i2s.h>
#include <ESP32Encoder.h>
#include <WiFi.h>
#include <WiFiUdp.h>// WiFi 配置
const char* ssid = "YXDZ_wifi"; // 替换为您的WiFi SSID
const char* password = "12345678"; // 替换为您的WiFi密码// UDP 配置
const char* udpAddress = "192.168.3.104"; // 替换为电脑的IP地址
const int udpPort = 12345; // UDP端口号WiFiUDP udp;// 配置 I2S0 用于麦克风采集
#define I2S_MIC_NUM I2S_NUM_0
#define I2S_MIC_BCK 13
#define I2S_MIC_WS 12
#define I2S_MIC_SD 14// 编码器引脚
#define MODE_DT_PIN 32 // A 相
#define MODE_CLK_PIN 35 // B 相#define SAMPLE_RATE 44100
#define BUFFER_SIZE 1024#define MODE_STEP 2 // 每 1 个计数触发(提高灵敏度)// 增益因子数组
const int gainFactors[] = {20, 30, 40, 50, 60, 70, 80};
const int numGains = sizeof(gainFactors) / sizeof(gainFactors[0]);
int currentGainIndex = 0; // 默认增益为 20ESP32Encoder modeEncoder;void setupI2SMic() {i2s_config_t mic_config = {.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),.sample_rate = SAMPLE_RATE,.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 单声道.communication_format = I2S_COMM_FORMAT_I2S,.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,.dma_buf_count = 8,.dma_buf_len = BUFFER_SIZE};i2s_pin_config_t mic_pin_config = {.bck_io_num = I2S_MIC_BCK,.ws_io_num = I2S_MIC_WS,.data_out_num = -1,.data_in_num = I2S_MIC_SD};i2s_driver_install(I2S_MIC_NUM, &mic_config, 0, NULL);i2s_set_pin(I2S_MIC_NUM, &mic_pin_config);
}void setupWiFi() {WiFi.begin(ssid, password);Serial.begin(115200);while (WiFi.status() != WL_CONNECTED) {delay(1000);Serial.println("连接到 WiFi...");}Serial.println("WiFi 已连接,IP 地址: " + WiFi.localIP().toString());udp.begin(udpPort);
}void setup() {Serial.begin(115200);setupWiFi();setupI2SMic();// 初始化编码器pinMode(MODE_CLK_PIN, INPUT_PULLUP);pinMode(MODE_DT_PIN, INPUT_PULLUP);modeEncoder.attachHalfQuad(MODE_CLK_PIN, MODE_DT_PIN);modeEncoder.setFilter(50);modeEncoder.setCount(0);Serial.println("初始化完成,当前增益: " + String(gainFactors[currentGainIndex]));
}void KeyUp() {if (currentGainIndex < numGains - 1) {currentGainIndex++;Serial.println("增益切换至: " + String(gainFactors[currentGainIndex]));}
}void KeyDn() {if (currentGainIndex > 0) {currentGainIndex--;Serial.println("增益切换至: " + String(gainFactors[currentGainIndex]));}
}void EC11_Control() {static int lastModeCount = 0;static unsigned long lastRotateTime = 0;int currentModeCount = modeEncoder.getCount();if (abs(currentModeCount - lastModeCount) >= MODE_STEP) {lastRotateTime = millis();if (currentModeCount > lastModeCount) {KeyUp(); // 顺时针增加增益Serial.println("向下");} else {KeyDn(); // 逆时针减少增益Serial.println("向上");}modeEncoder.setCount(0);lastModeCount = 0;}if (millis() - lastRotateTime > 1000) {lastRotateTime = millis();}
}void loop() {int16_t buffer[BUFFER_SIZE];size_t bytesRead;EC11_Control();// 从麦克风采集数据i2s_read(I2S_MIC_NUM, buffer, BUFFER_SIZE * sizeof(int16_t), &bytesRead, portMAX_DELAY);// 增益处理,放大音量并限制范围for (int i = 0; i < BUFFER_SIZE; i++) {buffer[i] = buffer[i] * gainFactors[currentGainIndex];if (buffer[i] > 32767) buffer[i] = 32767;if (buffer[i] < -32768) buffer[i] = -32768;}// 通过 UDP 发送音频数据udp.beginPacket(udpAddress, udpPort); //准备发送数据到目标IP和目标端口udp.write((uint8_t*)buffer, bytesRead);udp.endPacket();//向目标IP目标端口发送数据
}
接收端
基本步骤是,首先导入socket模块(提供创建UDP套接字的API)和pyaduio模块(提供初始化和播放音频的API),配置UDP和音频参数,初始化Pyaduio和UDP套接字后,在主循环中接收和播放音频,最后清理相关资源并退出。
socket函数参考
点击Python标准库链接可查看python库中socket 模块的详细说明
socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
函数功能:创建一个新的套接字对象,用于网络通信
参数
socket.AF_INET为使用 IPv4 地址(如果是IPv6为AF_INET6 ,AF_INET 表示“地址族)
socket.SOCK_DGRAM为使用 UDP 协议
socket.bind(address)
函数功能:将套接字绑定到指定的地址和端口,使其可以接收数据
参数
address:监听的地址,地址的格式取决于地址族,使用一对为:host和port
host为目标IP
port为监听的端口号
socket.recvfrom(buffer_size)
函数功能:接收 UDP 数据包,返回 (data, address),其中 data 是字节数据,address 是发送端的 (ip, port)。
参数
buffer_size为接收的字节数
socket.close()
函数功能:关闭套接字,释放网络资源。
pyaudio函数参考
https://people.csail.mit.edu/hubert/pyaudio/docs/
pyaudio.PyAudio()
函数功能:创建一个 PyAudio 实例,用于后续音频流操作
pyaudio.PyAudio.open(format, channels, rate, output, frames_per_buffer)
函数功能:打开一个音频流,用于录制或播放
参数
format=pyaudio.paInt16: 指定音频样本格式,paInt16 表示 16 位整数
channels=1:声道数,1 表示单声道,2 表示双声道
rate=44100:采样率
output=True:表示流用于输出,即播放,False 表示输入,即录制
frames_per_buffer=1024:缓存区大小
pyaudio.PyAudio.Stream.write(data, num_frames=None, exception_on_underflow=False)
函数功能:将音频数据写入流进行播放
参数
data:字节格式的音频数据
num_frames:写入的帧数,不指定则自动计算。
exception_on_underflow=False:设置缓存区是否欠载抛出异常,默认为False则不抛出异常。
pyaudio.PyAudio.Stream.stop_stream()
函数功能:暂停音频流
pyaudio.PyAudio.Stream.close()
函数功能:关闭音频流
pyaudio.PyAudio.terminate()
函数功能:释放 PyAudio 资源
导入必要的模块:
import socket
import pyaudio
配置 UDP 和音频参数
UDP_IP = "0.0.0.0" # 监听所有接口
UDP_PORT = 12345 # 与ESP32代码中的端口一致SAMPLE_RATE = 44100
CHANNELS = 1
FORMAT = pyaudio.paInt16
BUFFER_SIZE = 1024
初始化 PyAudio
p = pyaudio.PyAudio()
stream = p.open(format=FORMAT,channels=CHANNELS,rate=SAMPLE_RATE,output=True,frames_per_buffer=BUFFER_SIZE)
初始化 UDP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))
在主循环中接收和播放音频:
try:while True:data, addr = sock.recvfrom(BUFFER_SIZE * 2) # 接收数据stream.write(data) # 播放音频
清理资源
finally:stream.stop_stream()stream.close()p.terminate()sock.close()
完整代码如下:
import socket
import pyaudio# UDP 配置
UDP_IP = "0.0.0.0" # 监听所有接口
UDP_PORT = 12345 # 与ESP32代码中的端口一致# 音频参数
SAMPLE_RATE = 44100
CHANNELS = 1
FORMAT = pyaudio.paInt16
BUFFER_SIZE = 1024# 初始化 PyAudio
p = pyaudio.PyAudio()
stream = p.open(format=FORMAT,channels=CHANNELS,rate=SAMPLE_RATE,output=True,frames_per_buffer=BUFFER_SIZE)# 初始化 UDP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))print("开始接收和播放音频...")try:while True:data, addr = sock.recvfrom(BUFFER_SIZE * 2) # 接收数据stream.write(data) # 播放音频
except KeyboardInterrupt:print("停止接收")
finally:stream.stop_stream()stream.close()p.terminate()sock.close()
实际现象
发送端代码上传至ESP32,运行电脑接收端Python代码,在PC上即可听到inmp441采集到的声音。

注意事项
- ESP32和电脑端需在同一个局域网内,wifi需要替换为自己的名称和密码。
- 因网络问题可能有出现延迟和卡顿。
- 如不需要增益控制功能,可不接旋转编码器模块。
- 建议PC端接耳机实际效果更好。
- 为了传输数据比较流畅,降低音频数据处理量,采用的是单声道播放。












