C#连接小智服务器并将音频解码播放过程记录

360影视 欧美动漫 2025-04-01 10:33 2

摘要: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消息:

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"":""""
}";
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}");
}
}
}

现在测试一下是否成功连接:

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 != )
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

相关推荐