基于STM32F103ZET6移植OPUS语音算法
移植过程:参考这个几个帖子使用资源:至少要64K的RAM和200K作用MCU才能实现编解码。STM32F103的64KRAM+512K勉强能用,但实际还是很悬,建议上STM32F40X系列更好。OPUS编解码需要43K的RAM,编码会申请57.5%*43K的RAM解码会占用42.2%*43K的RAM.同时编解码时还会根据复杂度申请堆,经过验证复杂度越高需要的堆就越高,同时可变波特率占用堆空间比固定
移植过程:参考这个几个帖子
小智AI音频开发 libopus + Eclipse C/C++ MinGW 编解码测试用例-CSDN博客
STF103VE CUBE IAR opus codec 移植 - STM32 - 论坛-意法半导体STM32/STM8技术社区
使用资源:至少要64K的RAM和200K作用MCU才能实现编解码。
STM32F103的64KRAM+512K勉强能用,但实际还是很悬,建议上STM32F40X系列更好。
OPUS编解码需要43K的RAM,
编码会申请57.5%*43K的RAM
解码会占用42.2%*43K的RAM.
同时编解码时还会根据复杂度申请堆,经过验证
复杂度越高需要的堆就越高,同时可变波特率占用堆空间比固定波特率的占用更高。
0-1的复杂度下:(当复杂度高时,堆需要更大,运算量也更大,但是效果会更好。)
固定波特:需要16K空间的堆空间不然会死机。
可变波特:需要8K空间的堆空间不然会死机。
OPUS与Speex的区别:
支持的音频范围
-
OPUS:
覆盖 语音和音乐,支持采样率 8kHz~48kHz(窄带、宽带、超宽带、全带),能同时处理人声、乐器、混合音频等。
例:既能编码电话音质(8kHz),也能编码音乐(48kHz,接近 CD 音质)。 -
Speex:
仅优化 语音信号,支持采样率 8kHz(窄带)、16kHz(宽带)、32kHz(超宽带),对音乐等非语音信号处理能力弱(音质差)。
2. 比特率与音质
-
OPUS:
比特率范围 6kbps~510kbps,在同比特率下音质显著优于 Speex:- 低比特率(8~16kbps):语音清晰度接近专业电话系统;
- 高比特率(128~510kbps):可媲美 AAC,适合音乐传输。
-
Speex:
比特率范围 2kbps~44kbps,音质上限较低:- 低比特率(<16kbps):音质尚可但有明显压缩感;
- 高比特率(>32kbps):音质提升有限,不如 OPUS 高效。
3. 延迟性能
-
OPUS:
支持 超低延迟模式(帧长 2.5ms~60ms),默认 20ms 帧长时,整体延迟可低至 10ms 以内,适合实时通信(如 VoIP、视频会议)。 -
Speex:
延迟较高(帧长 10ms~30ms),且算法设计对实时性优化不足,不适合低延迟场景。
4. 算法复杂度与资源消耗
-
OPUS:
复杂度可配置(低 / 中 / 高),编码端 CPU 占用略高于 Speex(约 10~25% @8kHz),内存需求稍大(编码端 16~32KB RAM),但仍适合嵌入式设备(如 ESP32、STM32H7)。 -
Speex:
极致轻量,编码端 CPU 占用仅 5~15% @8kHz,内存需求极低(8~16KB RAM),可运行在 8 位单片机(如 AVR、MSP430)上。
移植注意事项:
我是基于整点原子的空白文档进行移植。使用正点原子的内存管理函数。
需要把celt/os_support.h需要更改内存管理函数。
需要添加这几个全局宏。
USE_HAL_DRIVER,STM32F103xE,HAVE_CONFIG_H,__TARGET_FEATURE_NEON



测试源码:
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./MALLOC/malloc.h"
#include "opus.h"
#include "stdio.h"
#include <stdint.h>
#include <string.h>
#include <math.h>
// 配置参数(8kHz单声道,20ms帧长)
#define SAMPLE_RATE 8000 //采样率
#define CHANNELS 1 //通道数
#define FRAME_SIZE 160 //opus每帧样点数:8k采样率, 20ms一帧
#define OPUS_BIT_RATE (8*1000 ) // 10kbps比特率
#define OPUS_PACKET_LEN (OPUS_BIT_RATE*20/8000) //每帧压缩后的帧长: 6kbps->15字节, 8kbps->20字节, 12kbps->30字节, 16kbps->40字节
#define ENCODE_PACKET_SIZE 5*OPUS_PACKET_LEN //每包长度 20*5 = 100
// #define MIC_PACKET_BUFFER_NUM 64//64//128
// #define BUFFER_PACKET_SIZE ((40) * (MIC_PACKET_BUFFER_NUM))
#define PCM_AMPLITUDE 8192 // 测试PCM振幅(-8192~8192)
unsigned char encode_buffer[ENCODE_PACKET_SIZE]; //
opus_int16 decode_pcm_buffer[160]; //
// 固定测试PCM数据(生成一个简单的正弦波用于测试)
static int16_t test_pcm[FRAME_SIZE];
// 生成测试用的PCM正弦波数据
void generate_test_pcm(void) {
const float PI = 3.1415926535f;
for (int i = 0; i < FRAME_SIZE; i++) {
// 生成1kHz正弦波(8kHz采样率下)
float t = (float)i / SAMPLE_RATE;
test_pcm[i] = (int16_t)(PCM_AMPLITUDE * sin(2 * PI * 1000 * t));
}
}
// OPUS编解码测试函数
int opus_test(void) {
int error;
int result = 0;
float usage_rate = 0;
// 初始化编码器
OpusEncoder *encoder = opus_encoder_create(SAMPLE_RATE, CHANNELS,
OPUS_APPLICATION_VOIP, &error);
if (error != OPUS_OK || encoder == NULL) {
printf("编码器初始化失败,错误码: %d\n", error);
return -1;
}
usage_rate = (float)my_mem_perused(SRAMIN)/10.0;
printf("opus_encoder malloc use = %.01f \n",usage_rate);
// 初始化解码器
OpusDecoder *decoder = opus_decoder_create(SAMPLE_RATE, CHANNELS, &error);
if (error != OPUS_OK || decoder == NULL) {
printf("解码器初始化失败,错误码: %d\n", error);
opus_encoder_destroy(encoder);
return -1;
}
usage_rate = (float)my_mem_perused(SRAMIN)/10.0;
printf("opus_decoder+opus_encoder malloc use = %.01f \n",usage_rate);
// 配置编码器参数
opus_encoder_ctl(encoder, OPUS_SET_BITRATE(OPUS_BIT_RATE));
opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(1)); // 低复杂度
opus_encoder_ctl(encoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
opus_encoder_ctl(encoder, OPUS_SET_VBR(0));
// 生成测试PCM数据
generate_test_pcm();
printf("生成测试PCM数据完成\n");
printf("test_pcm: \n");
for(int i=0;i<FRAME_SIZE;i++) {
printf(" %d,",test_pcm[i]);
if(i%16 == 15 )printf("\n");
}
printf("\n");
// 执行编码
int encode_len = opus_encode(encoder, test_pcm, FRAME_SIZE, encode_buffer, sizeof(encode_buffer));
if (encode_len <= 0) {
printf("编码失败,错误码: %d\n", encode_len);
result = -1;
goto cleanup;
}
printf("编码成功,输出长度: %d字节\n", encode_len);
printf("encode_buffer: \n");
for(int i=0;i<encode_len;i++) {
printf("0x%02X ",encode_buffer[i]);
}
printf("\n");
// 执行解码
int decode_len = opus_decode(decoder, encode_buffer, encode_len,
decode_pcm_buffer, FRAME_SIZE, 0);
if (decode_len <= 0) {
printf("解码失败,错误码: %d\n", decode_len);
result = -1;
goto cleanup;
}
printf("解码成功,输出样本数: %d\n", decode_len);
printf("decode_pcm_buffer: \n");
for(int i=0;i<FRAME_SIZE;i++) {
printf(" %d,",decode_pcm_buffer[i]);
if(i%16 == 15 )printf("\n");
}
printf("\n");
//简单验证:计算解码前后的最大误差
int32_t max_error = 0;
for (int i = 0; i < FRAME_SIZE; i++) {
int32_t error = decode_pcm_buffer[i] - test_pcm[i];
if (error < 0) error = -error;
if (error > max_error) max_error = error;
}
printf("编解码最大误差: %d (建议小于1000)\n", max_error);
// 误差判断(根据应用场景调整阈值)
if (max_error > 1500) {
printf("警告:编解码误差较大,可能存在问题\n");
result = -1;
} else {
printf("OPUS编解码功能验证通过\n");
result = 0;
}
cleanup:
// 释放资源
opus_encoder_destroy(encoder);
opus_decoder_destroy(decoder);
return result;
}
int main(void)
{
uint8_t len;
uint16_t times = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟为72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
my_mem_init(0); /* 初始化Malloc */
printf("hello_world!\r\n"); /* 初始化LED */
printf("开始OPUS编解码测试...\n");
int test_result = opus_test();
if (test_result == 0) {
printf("测试成功\n");
} else {
printf("测试失败\n");
}
while (1)
{
}
}
测试结果展示

测试条件:
.配置参数(与OPUS编码匹配)
sample_rate = 8000 # 采样率:8kHz
channels = 1 # 单声道
frame_size = 160 # 帧长:20ms(8000Hz * 0.02s)
bitrate = 8000 # 比特率:188kbps
complexity = 1 # 编码复杂度:1级

实际效果绘图对比,基本是一致的
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)