[.NET] Speex 语音编解码介绍, 使用, 代码示例
Speex 是一个开源的, 适合语音编解码的算法, 常应用于网络电话中.
在下面的的介绍中, 我们将使用 SpeexSharp 对 Speex 编码在 .NET 中的使用做介绍
SpeexSharp 可以在 nuget 中直接安装, 并且已经封装了编解码器的类供使用. 如果你不希望了解 Speex 的具体编解码过程, 可以忽略下面的 ‘编码’ 和 ‘解码’ 部分, 只看 Speex 的介绍, 然后直接使用这些类进行编解码.
采样
Speex 的编解码是基于采样的, 传入数据的时候, 我们需要给定采样, 传出的时候, Speex 也是解码为采样.
Speex 支持的采样格式有两种, 浮点数和有符号 16 位整数.
模式和质量
Speex 目前有三种模式, 窄带, 宽带, 超宽带. 这三种模式中, 对音频数据编码后的数据大小是不一样的. 如其名, 在窄带模式下, 音频编码后最小, 质量也最低, 反之, 超宽带是编码后最大, 质量最好的模式.
选择好模式之后, 你还可以对编码质量进行微调. 质量的等级是一个从 0 到 10 的值(包含 0 和 10), 设置编码器的质量之后, 编码的结果大小和质量也会变更.
存储流
要进行 Speex 编码, 我们需要一个存储需要编码的数据的缓冲区, Speex 已经为我们准备好了这个类型, 并且 Speex 会自动管理这个缓冲区. 它叫做 SpeexBits
.
初始化一个 SpeexBits
, 我们需要声明一个 SpeexBits
类型的变量, 然后调用 Speex 的初始化函数.
SpeexBits bits;
Speex.BitsInit(&bits);
无论是编码还是解码, 都是需要 SpeexBits
作为存储的.
编码时, 用户将采样数据的指针传给编码函数, 函数内部对帧进行编码, 最后将编码后的结果放入 SpeexBits
中.
解码时则是用户将需要解码的数据放入 SpeexBits
, 将输出数据的缓冲区传给解码函数, 解码函数从中读取, 解码数据, 然后将解码后的数据写入用户指定的缓冲区中.
另外, SpeexBits 之所以叫 Bits, 是因为它其中存储的数据, 基本单位是比特. 在编码时, 你可能会得到 69.5 个字节. 也就是 556 个比特. 在这种情况下, 我们要存储它的数据时, 肯定是要向上舍入, 也就是存储它内部的 70 个字节.
帧大小与采样率
Speex 的编解码是对于 “帧” 而言的. 每一次编码, 都必须是一个完整的帧, 即一定数量的采样数. 而帧的大小取决于上面提到的 Speex 编码模式.
而且, 在编码的时候, 传入采样的采样率也应该与编码器设定的采样率一致. 这样才能获得最好的编码效果.
数据 \ 模式 | 窄带 | 宽带 | 超宽带 |
---|---|---|---|
帧大小 | 160 | 320 | 640 |
默认采样率 | 8000Hz | 16000Hz | 32000Hz |
注意, 帧大小是针对 ‘采样数量’ 的, 例如, 如果你要以宽带模式编码浮点数采样, 那么你需要 320 个浮点采样, 每个 4 字节, 总共需要 1280 个字节. 如果是带符号 16 位整数则是需要 640 个字节.
如果你希望将使用 Speex 编码的语音存储到文件, 你可能需要做一些处理. 因为 Speex 的编解码是针对于帧的, 所以你的文件中至少需要有标识帧的地方. 在解码时, 读取一帧, 然后调用解码方法, 得到原始采样.
最简单的方式就是在每一帧的前面加一字节的头, 这个字节用来标识后面多少字节是一帧.
编码过程
要进行完整的编码, 需要进行以下大概步骤
- 准备用于存储的 SpeexBits
- 初始化一个编码器
- 调用编码方法
- 从 SpeexBits 中读取编码结果
下面是宽带模式编码的示例代码:
// 要进行编码的采样数据
float[] samples;// 取 320 个采样, 也就是宽带模式下的一帧
float[] frame = samples.Take(320).ToArray();// 定义并初始化用于存储的 SpeexBits
SpeexBits bits;
Speex.BitsInit(&bits);// 获取表示宽带模式的指针, 0, 1, 2 分别是窄带, 宽带, 超宽带
SpeexMode* mode = Speex.LibGetMode(1);// 初始化编码器, 得到表示编码器状态的指针
void* encoderState = Speex.EncoderInit(mode);// 将数组转为指针
fixed (float* framePtr = frame)
{// 重置 SpeexBits 内容Speex.BitsReset(&bits);// 调用编码方法Speex.Encode(encoderState, framePtr, &bits);
}// 获取编码后的数量 (也就是 bits 中存储的字节数)
int bitCount = bits.BitCount;
int byteCount = (bits.BitCount + 7) >> 3 // 向上舍入// 声明一个缓冲区用于存储编码后结果
byte[] buffer = new byte[byteCount];// 固定缓冲区, 转为指针
fixed (byte* bufferPtr = buffer)
{// 将 Bits 内存储的编码结果写入到我们自己的缓冲区中Speex.BitsWrite(&bits, bufferPtr, buffer.Length);
}// 做其他处理.
需要注意的是, 每一次编码之后, 你都应该重置一下 SpeexBits
因为编码方法的结果再往 SpeexBits 存入时, 如果没有抹除旧的数据, SpeexBits 中就会同时存储着旧的数据和新的数据, 如果你没有手动往 SpeexBits 里面写入一些东西做标识, 那么你就无法区分不同的帧了.
最简单的方式就是, 每一次编码后, 读取编码结果, 然后清空 SpeexBits.
如果你需要将所有采样都编码了, 很简单, 只需要用 for 进行循环就好了. 但在这之前, 你还需要对原始的采样做填充处理, 确保它的大小是帧大小的整数倍, 这样你在做编码的时候, 就不会出现访问冲突的问题了.
float[] samples;int padLength = samples.Length % 360;
if (padLength != 0)padLength = 360 - padLength;float[] paddedSamples = new [samples.Length + padLength];
Array.Copy(samples, 0, paddedSamples, 0, samples.Length);fixed (float* paddedSamplesPtr = paddedSamples)
{for (int i = 0; i < paddedSamples.Length; i += 360){Speex.BitsReset(&bits);Speex.Encode(encoderState, paddedSamplesPtr + i, &bits);}
}
解码过程
解码同样很简单, 只需要我们将已经编码的一帧以及输出缓冲区传入到 Decode
函数中, Speex 就会将解码后的一帧存入到缓冲区中.
- 准备用于存储的 SpeexBits
- 初始化一个解码器
- 将需要解码的帧存入到 SpeexBits 中
- 调用解码方法
同样的, 解码的时候也是逐帧解码的, 你传入的缓冲区至少能容纳一帧的音频才可以.
下面是宽带模式解码的示例代码:
// 要进行解码的 Speex 数据
byte[] speex;// 声明用于存储解码结果的缓冲区
float[] buffer = new float[320];// 定义并初始化用于存储的 SpeexBits
SpeexBits bits;
Speex.BitsInit(&bits);// 获取表示宽带模式的指针
SpeexMode* mode = Speex.LibGetMode(1);// 初始化解码器
void* decoderState = Speex.DecoderInit(mode);// 固定 Speex 数据
fixed (byte* speexPtr = speex)
{// 将其读入到 SpeexBits 中Speex.BitsReadFrom(&bits, speexPtr, speex.Length);
}// 固定缓冲区
fixed (float* bufferPtr = buffer)
{// 进行解码Speex.Decode(decoderState, &bits, bufferPtr);
}// 做其他处理
需要注意的是, 在解码时, 读入 SpeexBits 的数据只能是一帧, 如果你存入两帧或者更多的话, 那么解码会出问题.
如果你存入了半个帧或者数据损坏的一个帧, 解码仍然能成功, 只不过输出结果的质量会下降.
托管调用
以上, 我们已经了解了 Speex 编解码的具体过程, 但 SpeexSharp 还提供了编解码器的类封装, 内部会自动初始化 SpeexBits, 存取编解码数据等.
如果要使用这些封装好的类来进行编解码, 只需要新建 SpeexEncoder 或 SpeexDecoder 的示例即可.
相关文章:
[.NET] Speex 语音编解码介绍, 使用, 代码示例
Speex 是一个开源的, 适合语音编解码的算法, 常应用于网络电话中. 在下面的的介绍中, 我们将使用 SpeexSharp 对 Speex 编码在 .NET 中的使用做介绍 SpeexSharp 可以在 nuget 中直接安装, 并且已经封装了编解码器的类供使用. 如果你不希望了解 Speex 的具体编解码过程, 可以忽…...

小样本目标检测(Few-Shot Object Detection)综述
背景 前言:我的未来研究方向就是这个,所以会更新一系列的文章,就关于FSOD,如果有相同研究方向的同学欢迎沟通交流,我目前研一,希望能在研一发文,目前也有一些想法,但是具体能不能实现还要在做的过程中慢慢评估和实现.写文的主要目的还是记录,避免重复劳动,我想用尽量简洁的语言…...

【解决问题】---- 解决 avue-crud 表格勾选数据翻页后界面保持选中
1. 错误预览 第一页选择【7、8、9、10】 直接点击第三页未进行选择 直接点击第四页未进行选择 2. 问题总结 通过测试可以看到,页面的选择项会影响到其他页面的选择;点击保存,返回的数据却是真真选择的数据;数据在选择渲染…...

JL-03小型气象站气象环境在线监测设备自动上传并保存数据
JL-03小型气象站产品概述 小型气象站用于对风速、风向、雨量、空气温度、空气湿度、太阳辐射、光照强度、土壤温度、土壤湿度、蒸发量、大气压力等气象要素进行现场监测。既可以通过无线通讯将数据传送至云平台,又可以通过配套的数据采集通讯线与计算机进行连接&am…...
Ansible的变量(vars,register,set_fact)
环境 控制节点:Ubuntu 22.04Ansible 2.10.8管理节点:CentOS 8 概述 vars :Ansible关键字,用在play、role、block、task上register :Ansible关键字,用在task上。注意它是一个返回值,可能需要用…...

麒麟KYLINIOS软件仓库搭建03-软件仓库添加新版本的软件包
原文链接:麒麟KYLINIOS软件仓库搭建03-软件仓库添加新版本的软件包 hello,大家好啊,今天给大家带来麒麟桌面操作系统软件仓库搭建的文章03-软件仓库添加新版本的软件包,本篇文章主要给大家介绍了如何在麒麟桌面操作系统2203-x86版…...
监控系统是怎么组的(sentry)
搭建sentry监控平台,实现前后端异常监控。——从零开始搭建一个高颜值后台管理系统全栈框架(十六) - 掘金...

Java --- 直接内存
一、直接内存 1、不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。 2、直接内存是在Java堆外的,直接向系统申请的内存区间。 3、来源于NIO,通过存在堆中的DirectByteBuffer操作Native内存。 4、访问直接内存的…...

数据结构与算法之排序: Leetcode 21. 合并两个有序链表 (Typescript版)
合并两个有序链表 https://leetcode.cn/problems/merge-two-sorted-lists/ 描述 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1 输入:l1 [1,2,4], l2 [1,3,4] 输出:[1,1,2,3,4,4]示例 …...

AIGC:使用bert_vits2实现栩栩如生的个性化语音克隆
1 VITS2模型 1.1 摘要 单阶段文本到语音模型最近被积极研究,其结果优于两阶段管道系统。以往的单阶段模型虽然取得了较大的进展,但在间歇性非自然性、计算效率、对音素转换依赖性强等方面仍有改进的空间。本文提出VITS2,一种单阶段的文本到…...

2023年CKA考试真题及注意事项
2023年CKA考试真题及注意事项 注意事项考试题目原题解析1.RBAC2.节点维护3.K8S组件升级 1.28.0升级到1.28.14.Etcd备份与恢复5.NetworkPolicy6.Service7.Ingress8.指定节点部署9.检查Node节点健康状态10.一个Pod多个容器11.监控Pod度量指标12.监控Pod日志13.PersistentVolumeCl…...
云计算运维面试
一、Linux的启动过程 1.加电 2.加载bios设置 3.加载grub 4. 加载内核系统到内存中 5.加载配置文件 6.加载内核模块 7.完成相应初始化工作和启动相应服务 8.启动系统进程 9.出现登录界面 10.开机自启动完成 二、查看系统的版本和内核 1、 查看版本 cat /etc/redha…...

Qt实现TCP调试助手 - 简述如何在Qt中实现TCP多并发
简介 软件开发中,可能经常会用到TCP调试工具。本人使用QT开发了一款TCP调试工具,方便大家使用。本文章主要介绍下,该工具的功能,以及如何在Qt中实现TCP服务器的并发。 界面展示 安装界面 桌面图标。安装后会生成桌面图标&#…...
【Python OpenCV】OpenCV介绍
文章目录 前言一、OpenCV简介二、基本功能三、实际应用场景四、Python安装OpenCV总结 前言 OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和图像处理库,它提供了丰富的工具和函数,用于处理图像和视频。由于…...

11-09 周四 CNN 卷积神经网络基础知识
11-09 周四 CNN 卷积神经网络 时间版本修改人描述2023年11月9日09:38:12V0.1宋全恒新建文档 简介 学习一下CNN,卷积神经网络。使用的视频课程。视觉相关的任务: 人脸识别 卷积网络与传统网络的区别: <img altimage-20231109094400591 s…...

Vue.js中的路由(router)和Vue Router的作用?
聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…...

从开源项目聊鱼眼相机的“360全景拼接”
目录 概述 从360全景的背景讲起 跨过参数标定聊透视变化 拼接图片后处理 参考文献 概述 写这篇文章的原因完全源于开源项目(GitHub参阅参考文献1)。该项目涵盖了环视系统的较为全貌的制作过程,包含完整的标定、投影、拼接和实时运行流程。该篇文章主要是梳理全…...

网络安全——
文章目录 网络安全TCP/IP与网络安全网络安全构成要素加密技术基础 网络安全 TCP/IP与网络安全 起初,TCP/IP只用于一个相对封闭的环境,之后才发展为并无太多限制、可以从远程访问更多资源的形式。因此,“安全”这个概念并没有引起人们太多的…...

用excel 整理工作流程,以周为时间节点,自动统计进度
无论是处理自己还是团队的工作,我们都经常会遇到复杂的,凌乱的,需要多个环节才能完成的工作。 梳理工作流程 因为环节内容,每个环节处理不当都可能会导致我们整个工作目标实现受到影响,所以通过工作流程图,…...

Wireshark学习 与 TCP/IP协议分析
Wireshark简介和工具应用 如何开始抓包? 打开wireshark,显示如下网络连接。选择你正在使用的,(比如我正在使用无线网上网),双击 可以先看下自己的ip地址和网关ip地址(看抓包数据时候会用到&…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...