摘要:ClientWebSocket clientWebSocket = new ClientWebSocket;Uri serverUri = new Uri("wss://api.tenclass.net/xiaozhi/v1/");string token =
最近小智很火,本文记录C#连接小智服务器并将音频解码播放的过程,希望能帮助到对此感兴趣的开发者。
如果没有ESP-32也想体验小智AI,那么这两个项目很适合你。
1、https://github.com/huangjunsen0406/py-xiaozhi
2、https://github.com/zhulige/xiaozhi-sharp
从xiaozhi-sharp项目中学习了很多,感谢该项目。
如果你有自定义服务端的需求,可以关注这个项目:
如果没有硬件的话,对接小智服务端主要就是看通讯协议。
小智的通讯协议在这:
本文作为探索小智的入门篇章,就从最基础的对接虾哥的服务器开始,目标是成功连接虾哥服务器并将返回的音频数据解码播放。
连接客户端使用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消息:
{
string message = @"{
""type"": ""hello"",
""version"": 1,
""transport"": ""websocket"",
""audio_params"": {
""format"": ""opus"",
""sample_rate"": 24000,
""channels"": 1,
""frame_duration"": 60
},
""session_id"":""""
}";
message = message.Replace("\n", "").Replace("\r", "").Replace("\r\n", "").Replace(" ", "");
if (string.IsOrEmpty(sessionId))
message = message.Replace(",\"session_id\":\"\"", "");
else
message = message.Replace("", 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(buffer), WebSocketMessageType.Text,true, CancellationToken.None);
Console.WriteLine($"发送消息:{message}");
}
}
接收消息的代码(先不考虑播放音频数据):
private static async Task ReceiveMessagesAsync(ClientWebSocket clientWebSocket){
var buffer = newbyte[1024];
while (clientWebSocket.State == WebSocketState.Open)
{
try
{
var result = await clientWebSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Text)
{
var message = Encoding.UTF8.GetString(buffer,0, result.Count);
if (!string.IsOrEmpty(message))
{
Console.WriteLine($"收到消息:{message}");
}
}
if (result.MessageType == WebSocketMessageType.Binary)
{
}
await Task.Delay(60);
}
catch (Exception ex)
{
Console.WriteLine($"小智:接收消息时出错{ex.Message}");
}
}
}
现在测试一下是否成功连接:
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);
{
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 != )
await SendMessageAsync(clientWebSocket,text);
}
现在来看是否有消息返回:
现在处理音频数据,修改接受消息的函数:
private static async Task ReceiveMessagesAsync(ClientWebSocket clientWebSocket, OpusAudioPlayer opusAudioPlayer){
var buffer = newbyte[1024];
while (clientWebSocket.State == WebSocketState.Open)
{
try
{
var result = await clientWebSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Text)
{
var message = Encoding.UTF8.GetString(buffer,0, result.Count);
if (!string.IsOrEmpty(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 classOpusAudioPlayer:IDisposable{
privatereadonly OpusDecoder _decoder;
privatereadonly BufferedWaveProvider _waveProvider;
privatereadonly WaveOutEvent _outputDevice;
publicOpusAudioPlayer
{
_Decoder = new OpusDecoder(480001);// 单声道
_waveProvider = new BufferedWaveProvider(new WaveFormat(48000161));
_outputDevice = new WaveOutEvent;
_outputDevice.Init(_waveProvider);
_outputDevice.Play;
}
public voidPlayOpusData(byte opusFrame)
{
short pcmBuffer = newshort[5760];
int decodedSamples = _decoder.Decode(
opusFrame, opusFrame.Length,
pcmBuffer, pcmBuffer.Length,
false);
// 转换short为byte
byte pcmBytes = newbyte[decodedSamples *2];
Buffer.BlockCopy(pcmBuffer,0, pcmBytes,0, pcmBytes.Length);
_waveProvider.AddSamples(pcmBytes,0, pcmBytes.Length);
}
public voidDispose
{
_outputDevice.Stop;
_outputDevice.Dispose;
}
}
接受消息改为:
OpusAudioPlayer opusAudioPlayer = new OpusAudioPlayer;_ = Task.Run(async =>
{
await ReceiveMessagesAsync(clientWebSocket, opusAudioPlayer;
});
实现效果在:
来源:opendotnet