HC32F460与ESP32-S3对接NB-IoT的语音系统设计
系统架构 HC32F460作为主控芯片负责音频采集、编解码及与ESP32-S3通信。ESP32-S3通过NB-IoT模块连接云端,实现与微信小程序的数据交互。
硬件连接
- HC32F460的I2S接口连接麦克风/扬声器
- HC32F460的UART与ESP32-S3通信
- ESP32-S3通过AT指令控制NB-IoT模块
音频编解码实现
PCM采集与压缩(HC32F460端)
// 初始化I2S录音
void I2S_Record_Init(void) {
stc_i2s_init_t stcI2sInit;
MEM_ZERO_STRUCT(stcI2sInit);
stcI2sInit.u32ChannelLength = I2S_CHANNEL_LEN_16BITS;
stcI2sInit.u32DataLength = I2S_DATA_LEN_16BITS;
I2S_Init(M4_I2S1, &stcI2sInit);
I2S_Cmd(M4_I2S1, Enable);
}
// ADPCM编码实现
void ADPCM_Encode(int16_t *pcm, uint8_t *adpcm, uint32_t len) {
int16_t prev_sample = 0;
int16_t index = 0;
const int16_t step_table[89] = { /* ADPCM步长表 */ };
for(uint32_t i=0; i<len; i++) {
int16_t diff = pcm[i] - prev_sample;
uint8_t code = 0;
/* 编码逻辑 */
adpcm[i/2] |= (code << (4*(i%2)));
}
}
ESP32-S3数据传输
// NB-IoT数据发送
void sendNB_IoT(const uint8_t* data, size_t len) {
Serial1.print("AT+QIOPEN=1,0,\"TCP\",\"your_server\",port,0,1\r");
delay(1000);
Serial1.printf("AT+QISEND=0,%d\r", len);
delay(500);
for(size_t i=0; i<len; i+=256) {
size_t send_len = min(len-i, 256);
Serial1.write(data+i, send_len);
delay(100);
}
}
微信小程序交互实现
小程序音频接收
// websocket接收处理
wx.connectSocket({
url: 'wss://your_server'
});
wx.onSocketMessage(res => {
const audioData = new Uint8Array(res.data);
const audioCtx = wx.createInnerAudioContext();
audioCtx.src = URL.createObjectURL(new Blob([audioData], {type: 'audio/adpcm'}));
audioCtx.play();
});
音频发送到设备
function sendAudioToDevice(blob) {
const reader = new FileReader();
reader.onload = function() {
wx.sendSocketMessage({
data: this.result
});
};
reader.readAsArrayBuffer(blob);
}
编解码方案对比
ADPCM vs OPUS
- ADPCM:4:1压缩比,算法简单(HC32F460可实时处理),音质一般
- OPUS:更高压缩比(6:1~20:1),需ESP32-S3处理,延迟更低
推荐方案
- 低功耗场景:HC32F460处理ADPCM
- 高质量场景:ESP32-S3处理OPUS编码
完整工作流程
- HC32F460通过I2S采集PCM音频
- 使用ADPCM编码压缩数据
- 通过UART发送给ESP32-S3
- ESP32-S3通过NB-IoT上传到云服务器
- 微信小程序接收并播放音频
- 反向流程实现语音消息下发
注意事项
- NB-IoT传输需分包处理(每包≤128字节)
- 需添加帧头标识和校验位
- 微信小程序需使用WebSocket协议
- 音频采样率建议采用8kHz/16kHz平衡质量与带宽
语音编码与解码实现
编码(PCM 转压缩格式)
以 Python 为例,使用 pyaudio 和 speex 库实现 PCM 音频压缩编码:
import pyaudio
import speex
# 初始化 Speex 编码器(窄带模式)
enc = speex.Encoder()
enc.initialize(speex.SPEEX_MODEID_NB)
# 音频参数
CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 8000
# 打开音频流
p = pyaudio.PyAudio()
stream = p.open(format=FORMAT, channels=CHANNELS,
rate=RATE, input=True,
frames_per_buffer=CHUNK)
# 编码过程
while True:
data = stream.read(CHUNK)
encoded_data = enc.encode(data) # 输出压缩后的二进制数据
解码(压缩格式转 PCM)
使用 Speex 解码器还原音频:
dec = speex.Decoder()
dec.initialize(speex.SPEEX_MODEID_NB)
# 假设 received_data 是通过网络接收的压缩数据
pcm_data = dec.decode(received_data)
语音数据传输实现
UDP 发送端代码
通过 UDP 协议发送编码后的语音数据:
import socket
UDP_IP = "192.168.1.100"
UDP_PORT = 5005
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(encoded_data, (UDP_IP, UDP_PORT))
UDP 接收端代码
接收端实时解码并播放:
import pyaudio
# 初始化音频输出流
p = pyaudio.PyAudio()
out_stream = p.open(format=FORMAT, channels=CHANNELS,
rate=RATE, output=True)
# 接收循环
while True:
data, addr = sock.recvfrom(4096)
pcm_data = dec.decode(data)
out_stream.write(pcm_data)
完整收发系统示例
发送端整合
结合编码与网络发送:
# 初始化编码器和网络
enc = speex.Encoder()
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 音频采集+编码+发送循环
while True:
pcm_data = stream.read(CHUNK)
encoded = enc.encode(pcm_data)
sock.sendto(encoded, (UDP_IP, UDP_PORT))
接收端整合
结合网络接收与音频播放:
# 初始化解码器和音频输出
dec = speex.Decoder()
out_stream = p.open(...)
# 接收+解码+播放循环
while True:
data, _ = sock.recvfrom(4096)
decoded = dec.decode(data)
out_stream.write(decoded)
关键参数说明
-
音频参数
RATE=8000: 适用于语音的采样率CHUNK=1024: 平衡延迟与处理效率SPEEX_MODEID_NB: 窄带模式(8kHz)
-
网络参数
UDP_PORT: 需与接收端一致4096: UDP 包最大尺寸
错误处理增强
网络重传机制
对关键语音包实现简单重传:
MAX_RETRY = 3
for _ in range(MAX_RETRY):
try:
sock.sendto(encoded, (UDP_IP, UDP_PORT))
break
except socket.error:
continue
音频缓冲补偿
解决网络抖动导致的断续问题:
from collections import deque
buffer = deque(maxlen=5) # 存储 5 个数据包
while True:
data, _ = sock.recvfrom(4096)
buffer.append(data)
if len(buffer) > 0:
out_stream.write(dec.decode(buffer.popleft()))
性能优化建议
-
多线程处理
分离音频采集、编码、发送为独立线程import threading t = threading.Thread(target=audio_capture_thread) t.start() -
前向纠错(FEC)
在编码阶段添加冗余数据包 -
延迟测量
插入时间戳计算端到端延迟send_time = time.time() packet = encoded_data + struct.pack('d', send_time)
不同编码方案对比
| 编码格式 | 比特率 | 延迟 | 适用场景 |
|---|---|---|---|
| Speex | 2-44kbps | 低 | 实时语音通信 |
| Opus | 6-510kbps | 极低 | 全场景音频 |
| G.711 | 64kbps | 最低 | 传统电话系统 |
扩展功能实现
静音检测(VAD)
减少无效数据传输:
import webrtcvad
vad = webrtcvad.Vad()
if vad.is_speech(pcm_data, RATE):
sock.sendto(encoded_data, (UDP_IP, UDP_PORT))
回声消除
使用 WebRTC 的 AEC 模块:
from webrtc import AudioProcessing
apm = AudioProcessing()
processed_data = apm.ProcessStream(pcm_data)
2286

被折叠的 条评论
为什么被折叠?



