使用WebSocket协议实现在ESP32上音频接收播放
使用WebSocket协议实现在ESP32上音频接收播放
本文将介绍如何使用WebSocket协议在ESP32上实现音频的接收和播放。通过搭建一个简易的WebSocket服务器,我们将展示如何将音频数据从服务器传输到ESP32,并通过I2S音频放大模块进行播放。
WebSocket是一种网络通信协议,位于OSI模型的应用层。它提供了在单个TCP连接上进行全双工通信的能力,使得客户端和服务器之间的数据交换变得更加实时和高效。
WebSocket协议主要特点如下:
- 全双工通信:在WebSocket协议下,客户端和服务器可以在任何时候,互相发送消息,而不需要轮询。
- 持久连接:一旦建立WebSocket连接,客户端和服务器之间的连接会持续开放,直到任何一方显式地关闭连接。
- 减少开销:与HTTP相比,WebSocket减少了频繁的头部和握手信息,因此在大量数据交换时,可以减少延迟。
- 握手:WebSocket连接的建立是基于HTTP的,使用HTTP的Upgrade头从HTTP协议切换到WebSocket协议。
- 消息格式:WebSocket传输的数据可以是文本格式,也可以是二进制格式。
- 服务器推送:服务器可以主动推送信息到客户端,这对于实时应用,如在线游戏、实时交易系统等是非常有用的。
WebSocket协议广泛应用于需要实时互动的应用中,如即时聊天、游戏、实时交易系统、在线协作工具等。
WebSocket协议的URL以ws://
或wss://
开始,其中wss://
表示使用了SSL加密的WebSocket连接。这个协议在现代的浏览器中得到了广泛的支持。
ESP32是一款由乐鑫信息科技(Espressif Systems)推出的低成本、低功耗的系统级芯片(SoC),它集成了Wi-Fi和双模蓝牙(经典蓝牙/蓝牙低功耗BLE)功能。ESP32适用于各种物联网(IoT)应用、智能家居项目、无线通信产品以及其他需要网络连接的嵌入式系统。
服务器端(发送音频)
由于WebSocket可接受的url地址是ws://和wss://(加密)格式的,网上暂时没有找到现成的地址,因此验证时可以自己搭建一个简易服务器。
环境搭建:Python3.12 Pycharm
需安装包:pip install asyncio websockets wave
服务器地址和端口:本电脑当服务器,IP地址就填本电脑的。端口号可以自己配置
查看自己电脑的IP地址请参考:如何快速查看电脑ip地址?四种方法告诉你_电脑ip地址查询-CSDN博客
音频文件:daoxiang.wav
启动方式:点击运行WebServer.py(当前脚本),服务器启动,一直运行
具体代码:
import asyncio
import websockets
import wave
# 服务器地址和端口
HOST = '172.19.1.180' # 可以替换为服务器的实际 IP 地址或域名(本电脑的IP地址)
PORT = 8765 # WebSocket 服务器端口
# 音频文件路径
AUDIO_FILE_PATH = 'D:\\daoxiang.wav' # 音频文件路径
async def send_audio(websocket, path):
print(f"客户端已连接: {websocket.remote_address}")
# 打开音频文件
try:
with wave.open(AUDIO_FILE_PATH, 'rb') as wf:
print(f"音频文件 {AUDIO_FILE_PATH} 已打开.")
# 获取音频文件参数
chunk_size = 1024 # 每次发送的数据块大小
n_channels = wf.getnchannels()
sampwidth = wf.getsampwidth()
framerate = wf.getframerate()
print(f"音频参数: {n_channels} 通道, {sampwidth} 字节样本宽度, {framerate} 采样率.")
# 逐块读取音频数据并发送给客户端
while True:
audio_data = wf.readframes(chunk_size)
if not audio_data: # 如果读取完所有数据,退出循环
break
# 发送音频数据给客户端
await websocket.send(audio_data)
await asyncio.sleep(0.01) # 确保非阻塞发送,控制发送速度
except websockets.ConnectionClosed as e:
print(f"客户端断开连接: {e}")
except Exception as e:
print(f"发生错误: {e}")
async def start_server():
# 创建 WebSocket 服务器
async with websockets.serve(send_audio, HOST, PORT):
print(f"WebSocket 服务器已启动: ws://{HOST}:{PORT}/")
await asyncio.Future() # 运行服务器,直到手动停止
# 运行服务器
asyncio.run(start_server())
接收端(接收音频并播放)
a. ESP32S3
Arduino需安装WebSockets包(这里是2.3.6版本,其他版本兼容性未做测试):
MAX98357 I2S AMP(Speaker – 和 + 连接扬声器)
代码如下:
#include "Arduino.h"
#include "WiFi.h"
#include <WebSocketsClient.h>
#include <driver/i2s.h> // 使用ESP32 I2S库
// WiFi 信息
const char* ssid = "your wifi"; // 替换为WiFi名称
const char* password = "12345678"; // 替换为WiFi密码
// WebSocket 服务器信息
const char* websocket_server = "172.19.1.170"; // 替换为WebSocket服务器IP地址
const uint16_t websocket_port = 8765; // WebSocket服务器端口
const char* websocket_path = "/"; // WebSocket路径
WebSocketsClient webSocket; // 创建 WebSocket 客户端对象
void onWebSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
switch (type) {
case WStype_CONNECTED:
Serial.println("Connected to WebSocket server");
break;
case WStype_DISCONNECTED:
Serial.println("Disconnected from WebSocket server");
break;
case WStype_BIN:
// 将接收到的二进制音频数据写入 I2S
size_t bytes_written;
i2s_write(I2S_NUM_0, payload, length, &bytes_written, portMAX_DELAY);
break;
case WStype_ERROR:
Serial.println("WebSocket Error");
break;
default:
break;
}
}
void setup() {
Serial.begin(115200);
// 连接到 WiFi
WiFi.disconnect();
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
// 等待连接到 WiFi
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("Connected to WiFi");
// 初始化 I2S
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = 44100,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = I2S_COMM_FORMAT_I2S_MSB,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 1024,
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0
};
i2s_pin_config_t pin_config = {
.bck_io_num = 3, // BCLK 引脚
.ws_io_num = 4, // LRC 引脚
.data_out_num = 5, // DATA 输出引脚
.data_in_num = I2S_PIN_NO_CHANGE
};
// 配置 I2S 接口
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pin_config);
i2s_zero_dma_buffer(I2S_NUM_0);
// 初始化 WebSocket 客户端
webSocket.begin(websocket_server, websocket_port, websocket_path);
webSocket.onEvent(onWebSocketEvent); // 设置 WebSocket 事件处理函数
// 开始 WebSocket 连接
webSocket.setReconnectInterval(5000); // 自动重连间隔
}
void loop() {
webSocket.loop(); // 处理 WebSocket 客户端事件
}
b.另一台PC运行py脚本
用于验证搭建的简易服务器是否在工作。
环境搭建:Python3.12 Pycharm
需安装包:pip install asyncio websockets pyaudio
服务器地址和端口:填服务器的IP地址和端口号如
WebSocket服务器的URL
WS_URL="ws://192.168.127.22:8765/" #服务器的实际IP地址或域名
配置音频播放参数:
CHUNK=1024 #每个数据块的大小
FORMAT=pyaudio.paInt16 #音频格式
CHANNELS=2 #音频通道数量
RATE=44100 #采样率
启动方式:点击运行WebClient.py(当前脚本),确保服务器是在启动状态,连接音频设备,可以听到声音。
代码如下:
import asyncio
import websockets
import pyaudio
# WebSocket 服务器的 URL
WS_URL = "ws://192.168.127.22:8765/" # 服务器的实际 IP 地址或域名
# 配置音频播放参数
CHUNK = 1024 # 每个数据块的大小
FORMAT = pyaudio.paInt16 # 音频格式
CHANNELS = 2 # 音频通道数量(立体声)
RATE = 44100 # 采样率(每秒样本数)
# 初始化 PyAudio
audio = pyaudio.PyAudio()
# 创建流对象用于音频播放
stream = audio.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
output=True,
frames_per_buffer=CHUNK)
async def play_audio_from_websocket():
async with websockets.connect(WS_URL) as websocket:
print("已连接到 WebSocket 服务器...")
try:
while True:
# 接收音频数据
audio_data = await websocket.recv()
# 将接收到的数据写入到音频流中播放
stream.write(audio_data)
except websockets.ConnectionClosed as e:
print(f"WebSocket 连接关闭: {e}")
except Exception as e:
print(f"发生错误: {e}")
# 运行异步任务
asyncio.run(play_audio_from_websocket())
# 关闭流和 PyAudio
stream.stop_stream()
stream.close()
audio.terminate()