当前位置: 首页 > article >正文

小智AI音频开发 libopus + Eclipse C/C++ MinGW 编解码测试用例

小智AI音频开发 libopus + Eclipse C/C++ MinGW 编解码测试用例


目录

  • 小智AI音频开发 libopus + Eclipse C/C++ MinGW 编解码测试用例
      • 前言
      • 移植
      • 编解码测试
      • libopus编码器的控制参数
        • 信号类型
        • 比特率
        • 带宽
        • 编码复杂度
        • 前向纠错
        • 声道
        • 不连续传输
        • 位深
        • 帧持续时长
        • 码率
        • VBR约束
        • 应用类型
      • 示例代码


前言

最近ESP32小智AI很火,研究了小智的技术架构后,随即想在中移ML307R上重写小智的固件,这样就不用WIFI了,并且还省掉一块ESP32开发板。

ESP32小智的语音编解码使用的是libopus这个库,在espidf上已经是现成的组件包,而如果我想在Ml307r上实现,那么就要自己移植这个库了。

移植最快的办法就是先在windows上以纯C的形式移植好,liboous库是可以根据平台进行编译的,有x86、arm等,根据实际的平台编译可以获得更好的编解码效率,我为了跨平台更通用,直接去掉了平台,以纯C的形式移植,这样不管用在哪里,都能够无缝移植,甚至直接用在单片机上!

libopus下载地址:https://opus-codec.org/downloads/

移植测试环境:

  • 系统:win10
  • IDE:Eclipse C/C++
  • libopus移植版本:1.4

移植

libopus在1.4及以前的版本,提供了win32下的配置文件示例,而在1.4以后的版本,由于作者改用了cmake进行编译,没有移植的config.h参考,只能通过构建工具生成config.h头文件,所以本文采用1.4版本来移植。

需要移植的目录结构:

  • include:头文件
  • silk:silk音频编码器
    • arm:arm架构相关,不要
    • fixed:定点运算代码,需要
    • float:浮点运算代码,需要
    • mips:mips指令集相关,不要
    • x86:x86架构相关,不要
    • tests:测试代码,不要
  • src:源码,去除repacketizer_demo.c文件,这是示例代码
  • celt:celt音频编码器,去除pus_custom_demo.c文件,这是示例代码
    • arm:不要
    • mips:不要
    • tests:不要
    • x86:不要

增加version.h头文件,内容如下:

#ifndef VERSION_H
#define VERSION_H

#define PACKAGE_VERSION  "1.4"

#endif /* VERSION_H */

增加配置头文件config.h,内容如下:

/***********************************************************************
Copyright (c) 2011, Skype Limited. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of Internet Society, IETF or IETF Trust, nor the
names of specific contributors, may be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
***********************************************************************/

#ifndef CONFIG_H
#define CONFIG_H

#define VAR_ARRAYS
//#define USE_ALLOCA
//#define NONTHREADSAFE_PSEUDOSTACK

#define FIXED_POINT           1

#define OPUS_BUILD            1

#define REMOVE_FOR_MALLOC     1

#define DISABLE_FLOAT_API     1

#include "version.h"

#endif /* CONFIG_H */

在eclipse中定义全局宏HAVE_CONFIG_H,如下如所示:

增加头文件包含,如下图所示:

此时编译,就可以直接编译通过了。。

编解码测试

注意:opus是有损的,经过编码又解码后的数据肯定和原始数据是不一样的,所以只能通过播放器听一下编解码前后的音频文件是否有区别,以此来验证代码是否正确。
虽然有损,但是几乎听不出来有差异。

我用python录制了一段原始语音数据:test.pcm

该条语音参数如下:

  • 通道数:1
  • 采样率:24000
  • 帧周期:60ms
  • 位深度:16bit

测试代码提供了CBR和VBR模式。

代码测试步骤为:读取原始pcm数据->编码->解码->写入编解码后的文件

audacity这个软件是开源的,可以直接播放pcm格式的原始音频数据。

打开audacity软件,文件->导入->原始数据,参数如下设置:

第一条语音轨道是vbr模式编解码后的音频数据,第二条是cbr模式编解码后的音频数据,第三条轨道是原始的音频轨道数据,可以看到几乎没有差别,说明代码是没有问题的。


libopus编码器的控制参数

信号类型
opus_encoder_ctl(encoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
  • OPUS_SIGNAL_MUSIC:音乐
  • OPUS_SIGNAL_VOICE:语音
比特率
opus_encoder_ctl(encoder, OPUS_SET_BITRATE(OPUS_AUTO));  // 动态比特率
opus_encoder_ctl(encoder, OPUS_SET_BITRATE(48000));  // 固定比特率
  • 动态比特率:根据实际音频复杂度输出,每个包的大小不同,编码时间也比较短,可以在未说话时有效减小带宽和数据量。(默认)。

  • 固定比特率:根据设定的比特率输出音频,每个输出包的大小相同,编码时间会加长,不推荐使用。

带宽
opus_encoder_ctl(encoder, OPUS_SET_BANDWIDTH(OPUS_AUTO));

设置音频主要处于哪个频率的带宽,一般设置成自动就可以。

编码复杂度
opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(0));

更高的复杂度拥有更好的音频质量,同时耗时会更长,实际上在PC上测试,输出音频并没有什么不同。

  • 取值范围:0-10
前向纠错
opus_encoder_ctl(encoder, OPUS_SET_INBAND_FEC(1));

用于增强数据可靠性的数字信号处理技术,用算法修复丢失的数据包。

  • 0:关
  • 1:开(默认)
声道
opus_encoder_ctl(encoder, OPUS_SET_FORCE_CHANNELS(1));

强制设置通道数。

不连续传输
opus_encoder_ctl(encoder, OPUS_SET_DTX(0));

不连续传输,仅应用于LPC层。

  • 0:关(默认)
  • 1:开
位深
opus_encoder_ctl(encoder, OPUS_SET_LSB_DEPTH(16));

设置位深,取值在8-24之间,默认为24。

帧持续时长
opus_encoder_ctl(encoder, OPUS_SET_EXPERT_FRAME_DURATION(OPUS_FRAMESIZE_60_MS));

设定每个数据包的实际时间长度,根据实际音频数据填写。

取值:

#define OPUS_FRAMESIZE_ARG                   5000 /**< Select frame size from the argument (default) */
#define OPUS_FRAMESIZE_2_5_MS                5001 /**< Use 2.5 ms frames */
#define OPUS_FRAMESIZE_5_MS                  5002 /**< Use 5 ms frames */
#define OPUS_FRAMESIZE_10_MS                 5003 /**< Use 10 ms frames */
#define OPUS_FRAMESIZE_20_MS                 5004 /**< Use 20 ms frames */
#define OPUS_FRAMESIZE_40_MS                 5005 /**< Use 40 ms frames */
#define OPUS_FRAMESIZE_60_MS                 5006 /**< Use 60 ms frames */
#define OPUS_FRAMESIZE_80_MS                 5007 /**< Use 80 ms frames */
#define OPUS_FRAMESIZE_100_MS                5008 /**< Use 100 ms frames */
#define OPUS_FRAMESIZE_120_MS                5009 /**< Use 120 ms frames */
码率
opus_encoder_ctl(encoder, OPUS_SET_VBR(1));
  • 1:设置为动态码率(VBR)模式(默认)
  • 0:设置为静态码率(CBR)模式

码率一般分为静态码率和动态码率,CBR模式虽然说压缩要更快,但是数据量大,现在已经逐渐被VBR方式取代。

如果设置为CBR模式,每次调用opus_encode函数编码后的数据包的大小是固定的,但如果设置为VBR模式,每次编码的数据包的大小是可变的,数据量会更小。

VBR约束
opus_encoder_ctl(encoder, OPUS_SET_VBR_CONSTRAINT(1));

在编码器中启用或禁用约束VBR,当编码器处于CBR模式时,此设置将被忽略。

  • 0:关闭约束
  • 1:开启约束(默认)
应用类型
opus_encoder_ctl(encoder, OPUS_SET_APPLICATION(OPUS_APPLICATION_VOIP));

配置编码器的预期应用程序类型,取值:

  • OPUS_APPLICATION_VOIP:适合收听较高质量和清晰度的大多数VoIP/视频会议应用
  • OPUS_APPLICATION_AUDIO:适合广播/高保真应用,其中解码的音频应尽可能靠近输入端
  • OPUS_APPLICATION_RESTRICTED_LOWDELAY:仅在最重要的时刻最低可实现延迟时使用。不能使用语音优化模式。

示例代码

/*
 * main.c
 *
 *  Created on: 2025年3月24日
 *      Author: hello
 */


#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include "opus.h"

#define MAX_PACKET           1500

#define SAMPLING_RATE        24000
#define CHANNELS             1
#define FRAME_DURATION       60
#define BITDEPTH             16
#define FRAME_SIZE           ((SAMPLING_RATE * FRAME_DURATION) / 1000)
#define FRAME_SIZE_BYTES     (FRAME_SIZE << 1)
#define FRAME_SIZE_DURATION  OPUS_FRAMESIZE_60_MS

#define debug_printf(...) do{printf(__VA_ARGS__);printf("\n");fflush(stdout);}while(0)

void test(int vbr)
{
	int len = 0, err = 0;
	int frame_size = FRAME_SIZE;
	OpusEncoder *encoder = NULL;
	OpusDecoder *decoder = NULL;
	FILE* fps = NULL;
	FILE* fpd = NULL;
	opus_int16 pcmbuf[MAX_PACKET];
	uint8_t opusbuf[MAX_PACKET];

	debug_printf("\r\ntest run, vbr enable = %d", vbr);

	// 创建编码器
	encoder = opus_encoder_create(SAMPLING_RATE, CHANNELS, OPUS_APPLICATION_AUDIO, &err);
	if(err != OPUS_OK || encoder == NULL)
	{
		debug_printf("create encoder failed ! err:%d", err);
		goto __exit;
	}

	// 创建解码器
	decoder = opus_decoder_create(SAMPLING_RATE, CHANNELS, &err);
	if(err != OPUS_OK || decoder == NULL)
	{
		debug_printf("create decoder failed ! err:%d", err);
		goto __exit;
	}

	// 初始化解码器参数
    opus_encoder_ctl(encoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
    opus_encoder_ctl(encoder, OPUS_SET_BITRATE(OPUS_AUTO));
    opus_encoder_ctl(encoder, OPUS_SET_BANDWIDTH(OPUS_AUTO));
    opus_encoder_ctl(encoder, OPUS_SET_VBR(vbr));
    opus_encoder_ctl(encoder, OPUS_SET_VBR_CONSTRAINT(1));
    opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(0));
    opus_encoder_ctl(encoder, OPUS_SET_INBAND_FEC(1));
    opus_encoder_ctl(encoder, OPUS_SET_FORCE_CHANNELS(CHANNELS));
    opus_encoder_ctl(encoder, OPUS_SET_DTX(0));
    opus_encoder_ctl(encoder, OPUS_SET_LSB_DEPTH(BITDEPTH));
    opus_encoder_ctl(encoder, OPUS_SET_EXPERT_FRAME_DURATION(FRAME_SIZE_DURATION));
    opus_encoder_ctl(encoder, OPUS_SET_APPLICATION(OPUS_APPLICATION_VOIP));

    fps =  fopen("test.pcm", "rb");

    if(vbr == 0)
    {
    	fpd =  fopen("cbr-test.pcm", "wb+");
    }else
    {
    	fpd =  fopen("vbr-test.pcm", "wb+");
    }

    while(fread(pcmbuf, 1, FRAME_SIZE_BYTES, fps) != 0)
    {
    	// 编码
        len = opus_encode(encoder, pcmbuf, frame_size, opusbuf, MAX_PACKET);
        if(len < 0 || len > MAX_PACKET)
        {
        	debug_printf("encode failed ! err:%d", len);
        	break;
        }

        debug_printf("encode len: %-4d  toc:0x%02X", len, opusbuf[0]);

        // 解码
        len = opus_decode(decoder, opusbuf, len, pcmbuf, frame_size, 0);
        if(len != frame_size)
        {
        	debug_printf("decode failed ! err:%d", len);
        	break;
        }

        // 存文件
        fwrite(pcmbuf, 1, FRAME_SIZE_BYTES, fpd);
    }

__exit:
    opus_encoder_destroy(encoder);
    opus_decoder_destroy(decoder);
	fclose(fps);
	fclose(fpd);
	debug_printf("done");
}

int main()
{
	test(0);  // 恒定码率(CBR)模式
	test(1);  // 动态码率(VBR)模式
	return 0;
}


ends…


http://www.kler.cn/a/614827.html

相关文章:

  • 软件工程之需求工程(需求获取、分析、验证)
  • 动态规划算法题 小松鼠吃巧克力
  • Postman CORS 测试完全指南:轻松模拟跨域请求,排查 CORS 相关问题
  • Cursor 汉化教程
  • CAN 介绍
  • 在ArcGIS中导入气候tif文件出现 “输入与输出之间的基准面冲突” 警告
  • Python小练习系列 Vol.3:生成有效括号组合(回溯 + DFS)
  • 使用FastExcel时的单个和批量插入的问题
  • 【JavaScript】九、JS基础练习
  • 小红书xhs逆向算法还原(202503月更新)
  • [图论]万字解析图的最短路算法(详细带图解和代码)
  • 六十天前端强化训练之第三十五天之Jest单元测试大师课:从入门到实战
  • 数字内容体验提升用户参与策略
  • 基于dify平台批量分析excel格式信息
  • synchronized锁与lock锁的区别
  • JAVA学习*工厂模式
  • Linux课程学习一
  • 【Kafka】深入探讨 Kafka 如何保证一致性
  • 【区块链安全 | 第八篇】多签机制及恶意多签
  • keil的代码美化工具AStyle3.1