移植过程:参考这个几个帖子

小智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级

实际效果绘图对比,基本是一致的

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐