掘金 人工智能 03月31日 19:27
C#连接小智服务器并将音频解码播放过程记录
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文详细介绍了使用 C# 连接小智 AI 服务器并解码播放音频的过程。文章首先提供了与小智服务器交互所需的 C# 代码示例,包括建立 WebSocket 连接、发送消息和接收音频数据。关键在于使用 OpusSharp 解码 Opus 编码的音频数据,并结合 NAudio 实现音频播放。通过实践,开发者可以了解如何利用 C# 编程实现与小智 AI 的语音交互功能,为开发智能语音应用提供参考。

🔌 建立连接:使用 C# 的 ClientWebSocket 类连接到小智服务器,通过设置请求头(Authorization、Protocol-Version、Device-Id、Client-Id)进行身份验证和协议协商。

💬 发送与接收消息:通过 WebSocket 发送“hello”消息进行握手,并发送文本消息与服务器交互,接收服务器返回的文本或二进制数据。

🔈 音频解码与播放:使用 OpusSharp 解码服务器返回的 Opus 编码音频数据,然后利用 NAudio 库播放解码后的音频,实现语音交互。

🛠️ 核心代码与依赖:文章提供了关键的代码片段,包括获取 MAC 地址、发送消息、接收消息以及 OpusAudioPlayer 类的实现,并指出了所需的依赖库。

前言

最近小智很火,本文记录C#连接小智服务器并将音频解码播放的过程,希望能帮助到对此感兴趣的开发者。

如果没有ESP-32也想体验小智AI,那么这两个项目很适合你。

1、github.com/huangjunsen…

2、github.com/zhulige/xia…

从xiaozhi-sharp项目中学习了很多,感谢该项目。

如果你有自定义服务端的需求,可以关注这个项目:

github.com/xinnan-tech…

如果没有硬件的话,对接小智服务端主要就是看通讯协议。

小智的通讯协议在这:

ccnphfhqs21z.feishu.cn/wiki/M0Xiwl…

实践

本文作为探索小智的入门篇章,就从最基础的对接虾哥的服务器开始,目标是成功连接虾哥服务器并将返回的音频数据解码播放。

连接客户端使用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             {                 macAddresses += bytes[i].ToString("X2")                 if (i != bytes.Length - 1)                 {                     macAddresses += ":"                 }             }             break         }     }​     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);​                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})

实现效果在:

mp.weixin.qq.com/s/LPh5hXO8C…

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

C# 小智 WebSocket 音频解码 NAudio
相关文章