C#连接小智服务器并将音频解码播放过程记录
前言
最近小智很火,本文记录C#连接小智服务器并将音频解码播放的过程,希望能帮助到对此感兴趣的开发者。
如果没有ESP-32也想体验小智AI,那么这两个项目很适合你。
1、https://github.com/huangjunsen0406/py-xiaozhi
2、https://github.com/zhulige/xiaozhi-sharp
从xiaozhi-sharp项目中学习了很多,感谢该项目。
如果你有自定义服务端的需求,可以关注这个项目:
https://github.com/xinnan-tech/xiaozhi-esp32-server
如果没有硬件的话,对接小智服务端主要就是看通讯协议。
小智的通讯协议在这:
https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh
实践
本文作为探索小智的入门篇章,就从最基础的对接虾哥的服务器开始,目标是成功连接虾哥服务器并将返回的音频数据解码播放。
连接客户端使用C#中的ClientWebSocket。
解码音频数据使用OpusSharp。
播放音频使用NAudio。
建立连接:
获取设备MAC地址:
public static string GetMacAddress()
{
string macAddresses = "";
foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces())
{
// 仅考虑以太网、无线局域网和虚拟专用网络等常用接口类型
if (nic.OperationalStatus == OperationalStatus.Up &&
(nic.NetworkInterfaceType == NetworkInterfaceType.Ethernet ||
nic.NetworkInterfaceType == NetworkInterfaceType.Wireless80211 ||
nic.NetworkInterfaceType == NetworkInterfaceType.Ppp))
{
PhysicalAddress address = nic.GetPhysicalAddress();
byte[] bytes = address.GetAddressBytes();
for (int i = 0; i < bytes.Length; i++)
{
macAddresses += bytes[i].ToString("X2");
if (i != bytes.Length - 1)
{
macAddresses += ":";
}
}
break; // 通常只取第一个符合条件的 MAC 地址
}
}
return macAddresses.ToLower();
}
连接服务器:
ClientWebSocket clientWebSocket = new ClientWebSocket();
Uri serverUri = new Uri("wss://api.tenclass.net/xiaozhi/v1/");
string token = "test-token";
string deviceId = GetMacAddress();
clientWebSocket.Options.SetRequestHeader("Authorization", "Bearer " + token);
clientWebSocket.Options.SetRequestHeader("Protocol-Version", "1");
clientWebSocket.Options.SetRequestHeader("Device-Id", deviceId);
clientWebSocket.Options.SetRequestHeader("Client-Id", Guid.NewGuid().ToString());
clientWebSocket.ConnectAsync(serverUri, CancellationToken.None);
while (clientWebSocket.State != WebSocketState.Open)
{
Console.Write(".");
Thread.Sleep(100);
}
Console.WriteLine("Connected");
发送Hello消息:
public static string Hello(string sessionId = "")
{
string message = @"{
""type"": ""hello"",
""version"": 1,
""transport"": ""websocket"",
""audio_params"": {
""format"": ""opus"",
""sample_rate"": 24000,
""channels"": 1,
""frame_duration"": 60
},
""session_id"":""<会话ID>""
}";
message = message.Replace("\n", "").Replace("\r", "").Replace("\r\n", "").Replace(" ", "");
if (string.IsNullOrEmpty(sessionId))
message = message.Replace(",\"session_id\":\"<会话ID>\"", "");
else
message = message.Replace("<会话ID>", sessionId);
//Console.WriteLine($"发送的消息: {message}");
return message;
}
发送消息的代码:
public static async Task SendMessageAsync(ClientWebSocket clientWebSocket,string message)
{
if (clientWebSocket.State == WebSocketState.Open)
{
var buffer = Encoding.UTF8.GetBytes(message);
await clientWebSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
Console.WriteLine($"发送消息:{message}");
}
}
接收消息的代码(先不考虑播放音频数据):
private static async Task ReceiveMessagesAsync(ClientWebSocket clientWebSocket)
{
var buffer = new byte[1024];
while (clientWebSocket.State == WebSocketState.Open)
{
try
{
var result = await clientWebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Text)
{
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
if (!string.IsNullOrEmpty(message))
{
Console.WriteLine($"收到消息:{message}");
}
}
if (result.MessageType == WebSocketMessageType.Binary)
{
}
await Task.Delay(60);
}
catch (Exception ex)
{
Console.WriteLine($"小智:接收消息时出错 {ex.Message}");
}
}
}
现在测试一下是否成功连接:
ClientWebSocket clientWebSocket = new ClientWebSocket();
Uri serverUri = new Uri("wss://api.tenclass.net/xiaozhi/v1/");
string token = "test-token";
string deviceId = GetMacAddress();
clientWebSocket.Options.SetRequestHeader("Authorization", "Bearer " + token);
clientWebSocket.Options.SetRequestHeader("Protocol-Version", "1");
clientWebSocket.Options.SetRequestHeader("Device-Id", deviceId);
clientWebSocket.Options.SetRequestHeader("Client-Id", Guid.NewGuid().ToString());
clientWebSocket.ConnectAsync(serverUri, CancellationToken.None);
while (clientWebSocket.State != WebSocketState.Open)
{
Console.Write(".");
Thread.Sleep(100);
}
Console.WriteLine("Connected");
var helloMessage = Hello();
await SendMessageAsync(clientWebSocket, helloMessage);
_ = Task.Run(async () =>
{
await ReceiveMessagesAsync(clientWebSocket);
});
说明成功连接。
现在先发送一个文本消息。
string input = "你是谁";
string text = Listen_Detect(input);
await Send_Listen_Detect(clientWebSocket, text);
public static string Listen_Detect(string text)
{
string message = @"{
""type"": ""listen"",
""state"": ""detect"",
""text"": ""<唤醒词>""
}";
message = message.Replace("\n", "").Replace("\r", "").Replace("\r\n", "").Replace(" ", "");
message = message.Replace("<唤醒词>", text);
//Console.WriteLine($"发送的消息: {message}");
return message;
}
public static async Task Send_Listen_Detect(ClientWebSocket clientWebSocket,string text)
{
if (clientWebSocket != null)
await SendMessageAsync(clientWebSocket,text);
}
现在来看是否有消息返回:
现在处理音频数据,修改接受消息的函数:
private static async Task ReceiveMessagesAsync(ClientWebSocket clientWebSocket, OpusAudioPlayer opusAudioPlayer)
{
var buffer = new byte[1024];
while (clientWebSocket.State == WebSocketState.Open)
{
try
{
var result = await clientWebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Text)
{
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
if (!string.IsNullOrEmpty(message))
{
Console.WriteLine($"收到消息:{message}");
}
}
if (result.MessageType == WebSocketMessageType.Binary)
{
opusAudioPlayer.PlayOpusData(buffer);
}
await Task.Delay(60);
}
catch (Exception ex)
{
Console.WriteLine($"小智:接收消息时出错 {ex.Message}");
}
}
}
创建一个OpusAudioPlayer用于解码与播放音频数据。
依赖库:
OpusAudioPlayer类:
public class OpusAudioPlayer : IDisposable
{
private readonly OpusDecoder _decoder;
private readonly BufferedWaveProvider _waveProvider;
private readonly WaveOutEvent _outputDevice;
public OpusAudioPlayer()
{
_decoder = new OpusDecoder(48000, 1); // 单声道
_waveProvider = new BufferedWaveProvider(new WaveFormat(48000, 16, 1));
_outputDevice = new WaveOutEvent();
_outputDevice.Init(_waveProvider);
_outputDevice.Play();
}
public void PlayOpusData(byte[] opusFrame)
{
short[] pcmBuffer = new short[5760];
int decodedSamples = _decoder.Decode(
opusFrame, opusFrame.Length,
pcmBuffer, pcmBuffer.Length,
false);
// 转换short为byte
byte[] pcmBytes = new byte[decodedSamples * 2];
Buffer.BlockCopy(pcmBuffer, 0, pcmBytes, 0, pcmBytes.Length);
_waveProvider.AddSamples(pcmBytes, 0, pcmBytes.Length);
}
public void Dispose()
{
_outputDevice.Stop();
_outputDevice.Dispose();
}
}
接受消息改为:
OpusAudioPlayer opusAudioPlayer = new OpusAudioPlayer();
_ = Task.Run(async () =>
{
await ReceiveMessagesAsync(clientWebSocket, opusAudioPlayer;
});
实现效果在:
https://mp.weixin.qq.com/s/LPh5hXO8CJV1HsTzmJBWLQ
C#连接小智服务器并将音频解码播放过程记录的更多相关文章
- Hadoop集群搭建:用三台云服务器搭建HA集群(过程记录和分享)
该文主要记录了自己用云服务器搭建集群的过程,也分享一些自己遇到的问题和解决方法.里面可能提及一些自己的理解,可能不够准确,希望大家能够指正我,谢谢. 1.什么是HA集群 HA :High Availa ...
- 解决android模拟器连接本机服务器”Connection refused”问题
在本机用模拟器连接 localhost 的服务器不成功,经查询是我反了一个小错误. android 模拟器其本身的localhost就是它自己的ip,而如果我要连接本机的localhost则需要将 ...
- Redis 的键命令、HyperLogLog 命令、脚本命令、连接命令、服务器命令
Redis 的键命令.HyperLogLog 命令.脚本命令.连接命令.服务器命令 Redis 的键命令 Redis 的键命令主要用于管理 Redis 的键,如删除键.查询键.修改键及设置某个键等. ...
- 小智的旅行(Bridge)51nod 提高组试题
luogu AC传送门(官方数据) 题目描述 小智最喜欢旅行了,这次,小智来到了一个岛屿众多的地方,有N座岛屿,编号为0到N-1,岛屿之间 由一些桥连接,可以从桥的任意一端到另一端,由于岛屿可能比较大 ...
- 在有跳板机的情况下,SecureCRT自动连接到目标服务器
为了服务器的安全,运维人员经常会要求我们先登录到跳板机,然后再SSH连接到目标服务器.但是这样是很繁琐的,每次在SecureCRT创建一个连接,都需要输入SSH命令,然后输入密码. 下面的方法可以实现 ...
- ssh连接远程linux服务器
1.在百度搜索输入"putty"然后进行下载,下载后无需安装只需要在文件中找到"putty.exe"双击即可运行. 2.在"Host Name or ...
- 阿里云ecs初始化磁盘后远程连接不到服务器
阿里云初始化磁盘后远程连接不到服务器 报错: WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! ... 原因:阿里云ecs第一次链接服务器之后会在本地电 ...
- jdbc连接阿里云服务器上的MySQL数据库 及 数据库IP限制
问题1:Jdbc 如何连接阿里云服务器上的MySQL数据库? 解决: 上截图: 其中IP是阿里云服务器的公网IP地址. 问题2: 刚开始接手开发的时候,使用Navicat连接阿里云服务器上的数据后 ...
- 使用Navicat连接阿里云服务器上的MySQL数据库--转
手把手教你如何正确连接阿里云服务器上的数据库: 1.首先打开Navicat,文件>新建连接>MySQL连接,其他的如一图所示. 2.因为是连接服务器上的MySQL,所以我们使用SSH连接, ...
- JMC监控(Windows上远程连接监控Linux服务器的JVM)
Windows上远程连接监控Linux服务器的JVM:1.Linux服务器上配置:在Tomcat的tomcat-wms/bin/catalina.sh中添加CATALINA_OPTS="-X ...
随机推荐
- Redis组件的特性,实现一个分布式限流
分布式---基于Redis进行接口IP限流 场景 为了防止我们的接口被人恶意访问,比如有人通过JMeter工具频繁访问我们的接口,导致接口响应变慢甚至崩溃,所以我们需要对一些特定的接口进行IP限流,即 ...
- idea+maven打包.jar发布项目
开发完项目后,idea+maven环境打包成.jar包,才能发布项目.下面记录常用的几种打包方式. 一,通过mvn命令打包 比较专业的用法是通过mvn命令打包: mvn clean package - ...
- Collection子接口:Set接口(实现类:HashSet、LinkedHashSet、TreeSet)
/** * 1. Set接口的框架: * * |----Collection接口:单列集合,用来存储一个一个的对象 * |----Set接口:存储无序的.不可重复的数据 -->高中讲的" ...
- 使用 Dify + LLM 构建精确任务处理应用
在构建基于大语言模型(LLM)的应用时,如何确保返回结果的准确性和可重复性是一个常见的挑战.本文将结合 Dify + LLM 的使用经验,介绍如何设计一个精确的 LLM 任务处理流程,避免传统 LLM ...
- CRISP-DM的应用与理解
本文分享自天翼云开发者社区<CRISP-DM的应用与理解>,作者:吴****嫄 CRISP-DM是一个数据挖掘项目规划的开放标准流程框架模型,主要分为业务理解.数据理解.数据准备.建模.评 ...
- C# 深度学习框架 TorchSharp 原生训练模型和图像识别-自定义网络模型和识别手写数字
目录 使用 Torch 训练模型 定义神经网络 加载数据集 创建网络模型 定义损失函数 训练 识别手写图像 教程名称:使用 C# 入门深度学习 作者:痴者工良 教程地址:https://torch.w ...
- Data Warehouse - [00] 参考文献
浪尖大数据:什么是数据仓库的架构?企业数据仓库架构如何建设? 浪尖大数据:元数据管理在数据仓库的实践应用 - 要养成终生学习的习惯 -
- spring - [01] 简介
Spring发展至今,已经形成了一个生态体系(Spring全家桶) 001 || Spring 定义 Spring是一款主流的Java EE轻量级开源框架,目的是用于简化Java企业级应用的开发难 ...
- Flink学习(十三) Flink 常见核心概念分析
分布式缓存熟悉 Hadoop 的你应该知道,分布式缓存最初的思想诞生于 Hadoop 框架,Hadoop 会将一些数据或者文件缓存在 HDFS 上,在分布式环境中让所有的计算节点调用同一个配置文件.在 ...
- php执行时间
要计算代码的bai执行时间,在PHP来讲是du十分简单的,首先,zhi你需要知道,PHP是一种dao顺序执行的脚本语言,所以,可以按照以下步骤来计算代码的执行时间: <?php function ...