[.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地址(看抓包数据时候会用到&…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...