网站建设实训个人总结3000字南通网站制作计划
网站建设实训个人总结3000字,南通网站制作计划,东莞搜索网络优化,360信息流广告平台呈郧逊殴前面几篇文章已经把机器人硬件控制部分的开发讲得差不多了#xff0c;包括屏幕控制、舵机驱动、语音交互等功能。但是之前的外形太过简单#xff0c;可动角度不够多#xff0c;所以我就新改进了一个版本#xff0c;叫VerdiBot#xff08;阿荫#xff09;#xf…呈郧逊殴前面几篇文章已经把机器人硬件控制部分的开发讲得差不多了包括屏幕控制、舵机驱动、语音交互等功能。但是之前的外形太过简单可动角度不够多所以我就新改进了一个版本叫VerdiBot阿荫详细视频介绍地址请点击链接。ESP32社区最火的AI对话机器人非小智AI莫属了所以为了让自己做的机器人对话部分也足够的生动我就重新实现了一个.NET版本的小智客户端打算后期集成更多的功能并整理成了一个完整的开源项目——Verdure Assistant绿荫助手这是一个基于.NET 9.0的多平台AI语音助手支持Windows桌面、Android移动端、命令行以及Web API等多种使用方式。这篇文章主要是给大家讲讲这个对话机器人项目的一些代码方便想尝试的小伙伴快速上手体验。项目代码已经开源了大家可以自己研究遇到问题也欢迎提Issue讨论。机器人图片GitHub项目地址https://github.com/maker-community/Verdure.Assistant问题解答Q: 之前为什么特意做树莓派wifi配网的功能A: 之前的博客有网友说我浪费生命开发wifi配网功能我在评论区也有讲过原因现在我在这里再讲一遍因为有时候我们拿着设备到新环境的时候并不能时刻有可用的显示器和鼠标键盘但是又需要联网这时就可以使用wifi配网了。然后ssh连接到设备上就可以像服务器一样控制了。Q: 支持哪些AI服务A: 目前主要对接的是小智AI服务后续计划支持更多AI服务的接入包括OpenAI等。项目采用了抽象设计扩展起来比较方便。Q: 项目使用什么技术栈A: 核心使用.NET 9.0跨平台UI用.NET MAUIWindows桌面使用的WinUI 3。网络音频编解码用的OpusSharp库音频录制播放使用的最近社区刚有人开源的的SoundFlow库这个库功能完善使用方便并且内置了多种音频格式解码的播放所以我用它替换了之前的PortAudioSharp2网络通信基于WebSocket和MQTT未测试。详细的技术点在GitHub的README里都有说明。Q: 为什么要重新实现这个项目A: 目前小智AI机器人有免费的服务端可以使用而且整个架构都很优雅对比我之前的实现优点很多所以重新实现一个客户端对于用户体验有很大的帮助并且协议是公开的以后如果想自己拓展实现服务端也是很轻松的。项目整体架构目录结构项目采用清晰的分层架构便于理解和扩展Verdure.Assistant/├── src/ # 源代码│ ├── Verdure.Assistant.Core/ # 核心库音频、网络、服务│ ├── Verdure.Assistant.ViewModels/ # 共享视图模型MVVM│ ├── Verdure.Assistant.Console/ # 控制台应用│ ├── Verdure.Assistant.WinUI/ # WinUI桌面应用│ ├── Verdure.Assistant.MAUI/ # MAUI移动应用│ └── Verdure.Assistant.Api/ # Web API服务├── tests/ # 测试项目├── docs/ # 技术文档└── scripts/ # 构建脚本GitHub项目地址https://github.com/maker-community/Verdure.Assistant核心功能模块语音交互模块使用微软的语音认知服务的关键词唤醒加载关键词唤醒模型文件不需要Azure订阅你好小电/你好小娜src/Verdure.Assistant.Core/Services/WakeWords/KeywordSpottingService.cs音频处理模块Opus编解码、SoundFlow音频播放、跨平台音频录制src/Verdure.Assistant.Core/Services/Audio/AudioDataDistributor.cssrc/Verdure.Assistant.Core/Services/Audio/OpusSharpAudioCodec.cssrc/Verdure.Assistant.Core/Services/Audio/SoundFlowAudioPlayer.cssrc/Verdure.Assistant.Core/Services/Audio/SoundFlowAudioRecorder.cs网络通信模块WebSocket实时通信、MQTT物联网协议src/Verdure.Assistant.Core/Services/Protocols/WebSocketClient.cs状态管理模块设备状态机、会话状态控制src/Verdure.Assistant.Core/Services/StateMachine/ConversationStateMachine.cssrc/Verdure.Assistant.Core/Services/StateMachine/ConversationStateMachineContext.cs音乐播放模块集成酷狗/酷我API、在线播放和缓存src/Verdure.Assistant.Core/Services/KuwoMusicService?? 应用截图与演示??? WinUI 桌面应用WinUI Application Screenshot - 点击查看演示视频?? 演示视频点击在新标签页播放 ↗现代化的 Windows 桌面应用界面支持语音交互和实时状态显示?? MAUI 移动应用AndroidMAUI Application Screenshot - 点击查看演示视频?? 演示视频点击在新标签页播放 ↗基于 .NET MAUI 的 Android 移动应用支持后台语音处理和音乐播放? MAUI 安卓手表应用Android WatchMAUI Android Watch Application Screenshot - 点击查看演示视频?? 演示视频点击在新标签页播放 ↗基于 .NET MAUI 的安卓手表应用适配圆形/方形表盘支持语音助手核心功能?? Web API 服务Console Application Screenshot - 点击查看演示视频?? 演示视频点击在新标签页播放 ↗适合树莓派机器人和普通的测试使用快速开始环境准备基础要求.NET 9.0 SDK - 下载地址Visual Studio 2022 (17.8) 或 Visual Studio Code克隆项目git clone https://github.com/maker-community/Verdure.Assistant.gitcd Verdure.Assistant各平台使用指南1. Windows桌面版WinUI运行方式在Visual Studio中直接设置为启动项目运行。使用流程启动应用后界面会显示连接状态如果没有在小智后台绑定会提示进行绑定绑定完成说出你好小电开启对话说再见会再次进入等待状态功能特性自动模式自动持续监听无需重复唤醒实时状态显示连接状态、语音识别状态可视化音乐控制搜索、播放、暂停音乐主题切换支持深色/浅色主题2. Android移动版MAUI运行方式使用Visual Studio打开解决方案选择Android设备或模拟器使用流程安装APK到Android设备授予录音和通知权限使用唤醒词开启对话3. 命令行版Console运行方式cd src/Verdure.Assistant.Consoledotnet restoredotnet run使用场景服务器端部署Linux/Windows Server开发调试和测试查看详细日志输出自动化脚本集成4. Web API服务树莓派/服务器运行方式cd src/Verdure.Assistant.Apidotnet restoredotnet run主要API端点音乐相关# 搜索音乐GET /api/music/search?songName青花瓷# 播放音乐POST /api/music/search-and-playContent-Type: application/json{songName: 青花瓷}# 播放控制POST /api/music/pausePOST /api/music/resumePOST /api/music/stop树莓派部署适合部署在树莓派等嵌入式设备上配合VerdiBot硬件机器人使用。详细部署步骤参考项目中的API文档。核心技术详解1. 会话状态机项目使用状态机管理设备状态主要状态包括IDLE空闲等待唤醒LISTENING监听正在录音SPEAKING说话播放回复状态转换逻辑清晰避免混乱的条件判断。核心代码如下请求状态变更代码////// 请求状态转换//////触发事件///上下文信息/// 是否成功转换public bool RequestTransition(ConversationTrigger trigger, string? context null){lock (_stateLock){var fromState _currentState;var toState GetNextState(_currentState, trigger);if (toState null){_logger?.LogWarning(Invalid state transition: {FromState} - {Trigger} (context: {Context}),fromState, trigger, context);return false;}if (fromState toState.Value){_logger?.LogDebug(State transition ignored (already in target state): {State} - {Trigger},fromState, trigger);return true;}_logger?.LogInformation(State transition: {FromState} - {ToState} (trigger: {Trigger}, context: {Context}),fromState, toState.Value, trigger, context);_currentState toState.Value;_previousState fromState;// Fire state change eventvar eventArgs new StateTransitionEventArgs{FromState fromState,ToState toState.Value,Trigger trigger,Context context};try{StateChanged?.Invoke(this, eventArgs);}catch (Exception ex){_logger?.LogError(ex, Error in state change event handler);}return true;}}状态处理代码private void InitializeStateMachine(){_stateMachine new ConversationStateMachine();_stateMachineContext new ConversationStateMachineContext(_stateMachine){// Set up state machine actionsOnEnterListening async () {await StartListeningInternalAsync();},OnExitListening async () {await StopListeningInternalAsync();},OnEnterSpeaking async () {// 进入说话状态 - 保持录音以检测用户打断// 不需要停止录音继续监听用户的打断_logger?.LogDebug(进入说话状态保持录音以检测打断);await Task.CompletedTask;},OnExitSpeaking async () {await StopSpeakingInternalAsync();},OnEnterIdle async () {await EnterIdleStateAsync();},OnEnterConnecting async () {await EnterConnectingStateAsync();}};// Subscribe to state changes to sync with legacy state property_stateMachine.StateChanged OnStateMachineStateChanged;}2. Opus编解码使用Opus编解码器进行音频压缩特点低延迟适合实时语音通信高质量保证语音清晰度带宽节省有效降低网络传输压力项目中封装了OpusCodec类简化了编解码操作。完整代码如下using OpusSharp.Core;using Verdure.Assistant.Core.Interfaces;namespace Verdure.Assistant.Core.Services;////// OpusSharp音频编解码器实现///public class OpusSharpAudioCodec : IAudioCodec{private OpusEncoder? _encoder;private OpusDecoder? _decoder;private readonly object _lock new();private int _currentSampleRate;private int _currentChannels;public byte[] Encode(byte[] pcmData, int sampleRate, int channels){lock (_lock){// 验证输入参数是否符合官方规格if (sampleRate ! 16000){System.Console.WriteLine($警告: 编码采样率 {sampleRate} 不符合官方规格 16000Hz);}if (channels ! 1){System.Console.WriteLine($警告: 编码声道数 {channels} 不符合官方规格 1单声道);}if (_encoder null || _currentSampleRate ! sampleRate || _currentChannels ! channels){_encoder?.Dispose();_encoder new OpusEncoder(sampleRate, channels, OpusPredefinedValues.OPUS_APPLICATION_AUDIO);_currentSampleRate sampleRate;_currentChannels channels;System.Console.WriteLine($Opus编码器已初始化: {sampleRate}Hz, {channels}声道);}try{// 计算帧大小 (采样数不是字节数) - 严格按照官方60ms规格int frameSize sampleRate * 60 / 1000; // 对于16kHz 960样本// 确保输入数据长度正确 (16位音频 2字节/样本)int expectedBytes frameSize * channels * 2;//System.Console.WriteLine($编码PCM数据: 输入长度{pcmData.Length}字节, 期望长度{expectedBytes}字节, 帧大小{frameSize}样本);if (pcmData.Length ! expectedBytes){//System.Console.WriteLine($调整PCM数据长度: 从{pcmData.Length}字节到{expectedBytes}字节);// 调整数据长度或填充零byte[] adjustedData new byte[expectedBytes];if (pcmData.Length expectedBytes){// 数据不足复制现有数据并填充零Array.Copy(pcmData, adjustedData, pcmData.Length);//System.Console.WriteLine($PCM数据不足已填充{expectedBytes - pcmData.Length}字节的零);}else{// 数据过多截断Array.Copy(pcmData, adjustedData, expectedBytes);//System.Console.WriteLine($PCM数据过多已截断{pcmData.Length - expectedBytes}字节);}pcmData adjustedData;}// 转换为16位短整型数组short[] pcmShorts new short[frameSize * channels];for (int i 0; i pcmShorts.Length i * 2 1 pcmData.Length; i){pcmShorts[i] BitConverter.ToInt16(pcmData, i * 2);}// 可选添加输入音频质量检查//CheckAudioQuality(pcmData, $编码输入PCM长度{pcmData.Length}字节);// OpusSharp编码 - 使用正确的APIbyte[] outputBuffer new byte[4000]; // Opus最大包大小int encodedLength _encoder.Encode(pcmShorts, frameSize, outputBuffer, outputBuffer.Length);//System.Console.WriteLine($编码结果: 输出长度{encodedLength}字节);if (encodedLength 0){// 返回实际编码的数据byte[] result new byte[encodedLength];Array.Copy(outputBuffer, result, encodedLength);return result;}else{//System.Console.WriteLine($编码失败: 返回长度为 {encodedLength});}return Array.Empty();}catch (Exception ex){System.Console.WriteLine($OpusSharp编码失败: {ex.Message});System.Console.WriteLine($堆栈跟踪: {ex.StackTrace});return Array.Empty();}}}public byte[] Decode(byte[] encodedData, int sampleRate, int channels){lock (_lock){// 验证输入参数是否符合官方规格if (sampleRate ! 16000){System.Console.WriteLine($警告: 采样率 {sampleRate} 不符合官方规格 16000Hz);}if (channels ! 1){System.Console.WriteLine($警告: 声道数 {channels} 不符合官方规格 1单声道);}if (_decoder null || _currentSampleRate ! sampleRate || _currentChannels ! channels){_decoder?.Dispose();_decoder new OpusDecoder(sampleRate, channels);_currentSampleRate sampleRate;_currentChannels channels;System.Console.WriteLine($Opus解码器已初始化: {sampleRate}Hz, {channels}声道);}// 检查输入数据有效性if (encodedData null || encodedData.Length 0){System.Console.WriteLine(警告: 接收到空的Opus数据包);int frameSize sampleRate * 60 / 1000; // 60ms帧符合官方规格byte[] silenceData new byte[frameSize * channels * 2];return silenceData;}try{// 计算帧大小 (采样数不是字节数) - 严格按照官方60ms规格int frameSize sampleRate * 60 / 1000; // 对于16kHz 960样本// 为解码输出分配缓冲区确保有足够空间// Opus可能解码出不同长度的帧所以使用最大可能的帧大小int maxFrameSize sampleRate * 120 / 1000; // 最大120ms帧作为安全缓冲short[] outputBuffer new short[maxFrameSize * channels];System.Console.WriteLine($解码Opus数据: 输入长度{encodedData.Length}字节, 期望帧大小{frameSize}样本);// OpusSharp解码 - 使用正确的API让解码器自动确定帧大小int decodedSamples _decoder.Decode(encodedData, encodedData.Length, outputBuffer, maxFrameSize, false);System.Console.WriteLine($解码结果: 解码了{decodedSamples}样本);if (decodedSamples 0){// 验证解码出的样本数是否合理if (decodedSamples maxFrameSize){System.Console.WriteLine($警告: 解码样本数({decodedSamples})超出最大帧大小({maxFrameSize}));decodedSamples maxFrameSize;}// 转换为字节数组 - 确保正确的字节序byte[] pcmBytes new byte[decodedSamples * channels * 2];for (int i 0; i decodedSamples * channels; i){var bytes BitConverter.GetBytes(outputBuffer[i]);pcmBytes[i * 2] bytes[0]; // 低字节pcmBytes[i * 2 1] bytes[1]; // 高字节}// 可选添加简单的音频质量检查CheckAudioQuality(pcmBytes, $解码输出PCM长度{pcmBytes.Length}字节);return pcmBytes;}else{System.Console.WriteLine($解码失败: 返回的样本数为 {decodedSamples});}// 返回静音数据而不是空数组保持音频流连续性int silenceFrameSize frameSize * channels * 2;byte[] silenceData new byte[silenceFrameSize];System.Console.WriteLine($返回静音数据: {silenceFrameSize}字节);return silenceData;}catch (Exception ex){System.Console.WriteLine($OpusSharp解码失败: {ex.Message});System.Console.WriteLine($堆栈跟踪: {ex.StackTrace});// 返回静音数据而不是空数组保持音频流连续性int frameSize sampleRate * 60 / 1000; // 60ms帧byte[] silenceData new byte[frameSize * channels * 2];return silenceData;}}}////// 简单的音频质量检查帮助诊断音频问题///private void CheckAudioQuality(byte[] pcmData, string context){if (pcmData.Length 4) return;// 转换为16位样本进行分析var samples new short[pcmData.Length / 2];Buffer.BlockCopy(pcmData, 0, samples, 0, pcmData.Length);// 计算音频统计信息double sum 0;double sumSquares 0;short min short.MaxValue;short max short.MinValue;int zeroCount 0;foreach (short sample in samples){sum sample;sumSquares sample * sample;min Math.Min(min, sample);max Math.Max(max, sample);if (sample 0) zeroCount;}double mean sum / samples.Length;double rms Math.Sqrt(sumSquares / samples.Length);double zeroPercent (double)zeroCount / samples.Length * 100;// 检测潜在问题bool hasIssues false;var issues new List();// 检查是否全为零静音if (zeroPercent 95){issues.Add(几乎全为静音);hasIssues true;}// 检查是否有削波饱和if (max 32760 || min -32760){issues.Add(可能存在音频削波);hasIssues true;}// 检查是否有异常的DC偏移if (Math.Abs(mean) 1000){issues.Add($异常的DC偏移: {mean:F1});hasIssues true;}// 检查RMS是否异常低可能的损坏信号if (rms 10 zeroPercent 50){issues.Add($异常低的RMS: {rms:F1});hasIssues true;} if (hasIssues){//System.Console.WriteLine($音频质量警告 ({context}): {string.Join(, , issues)});//System.Console.WriteLine($ 统计: 样本数{samples.Length}, RMS{rms:F1}, 范围[{min}, {max}], 零值比例{zeroPercent:F1}%);}else{//System.Console.WriteLine($音频质量正常 ({context}): RMS{rms:F1}, 范围[{min}, {max}]);}}public void Dispose(){lock (_lock){_encoder?.Dispose();_decoder?.Dispose();}}}3. SoundFlow音频框架跨平台音频播放框架提供统一的音频播放接口屏蔽平台差异。录音初始化代码private static SoundFlowAudioRecorder? _instance;private static readonly object _instanceLock new();private AudioEngine? _engine;private AudioCaptureDevice? _captureDevice;private Recorder? _recorder;private readonly object _streamLock new();private readonly AudioDataDistributor _audioDistributor; // 使用 Channel 优化的音频分发器private bool _isRecording false;private bool _isDisposed false;private int _sampleRate 16000;private int _channels 1;private readonly ILogger? _logger;// 设备配置 - 优化为低延迟录音private static readonly MiniAudioDeviceConfig DeviceConfig new(){PeriodSizeInFrames 960, // 60ms 16kHz 960 samplesPeriodSizeInMilliseconds 0,Periods 3,NoPreSilencedOutputBuffer true,NoClip false,NoDisableDenormals false,NoFixedSizedCallback false,Capture new DeviceSubConfig{ShareMode ShareMode.Shared},Wasapi new WasapiSettings{Usage WasapiUsage.ProAudio,NoAutoConvertSRC false, // 允许自动采样率转换NoDefaultQualitySRC false, // 允许高质量重采样NoAutoStreamRouting false,NoHardwareOffloading false}};// 参考 py-xiaozhi 的事件系统public event EventHandler? DataAvailable;public event EventHandler? RecordingStopped;public bool IsRecording _isRecording;private SoundFlowAudioRecorder(ILogger? logger null){_logger logger;_audioDistributor new AudioDataDistributor(logger);InitializeAudioEngine();}////// 在构造函数中初始化音频引擎和基础组件///private void InitializeAudioEngine(){try{// 在构造时就初始化引擎_engine new MiniAudioEngine();// 显示可用的录音设备调试模式if (_logger ! null _logger.IsEnabled(LogLevel.Debug)){_logger.LogDebug(SoundFlow录音引擎初始化完成);_logger.LogDebug(可用SoundFlow录音设备:);for (int i 0; i _engine.CaptureDevices.Length; i){var device _engine.CaptureDevices[i];var marker device.IsDefault ? (默认) : ;_logger.LogDebug( [{Index}] {Name}{Marker}, i, device.Name, marker);}}}catch (Exception ex){_logger?.LogError(ex, 初始化SoundFlow录音引擎失败);throw;}}播放器初始化代码private readonly ILogger? _logger;private AudioEngine? _engine;private AudioPlaybackDevice? _playbackDevice;private SoundPlayer? _soundPlayer;private QueueDataProvider? _dataProvider;private readonly object _lock new();private bool _isPlaying false;private bool _isDisposed false;private int _sampleRate 16000;private int _channels 1;// 设备配置 - 优化为更低延迟播放减少断断续续private static readonly MiniAudioDeviceConfig DeviceConfig new(){PeriodSizeInFrames 480, // 30ms 16kHz 480 samples (减少到30ms提高响应性)PeriodSizeInMilliseconds 0,Periods 4, // 增加到4个周期提供更好的缓冲NoPreSilencedOutputBuffer false,NoClip false,NoDisableDenormals false,NoFixedSizedCallback false,Playback new DeviceSubConfig{ShareMode ShareMode.Shared},Wasapi new WasapiSettings{Usage WasapiUsage.ProAudio, // 专业音频模式降低延迟NoAutoConvertSRC false, // 允许自动采样率转换NoDefaultQualitySRC false, // 允许高质量重采样NoAutoStreamRouting false,NoHardwareOffloading false}};public event EventHandler? PlaybackStopped;public bool IsPlaying _isPlaying;public SoundFlowAudioPlayer(ILogger? logger null){_logger logger;// 创建无界通道用于音频数据缓冲避免阻塞问题var options new UnboundedChannelOptions{SingleReader true, // 只有播放任务读取SingleWriter false, // 多个来源可能写入音频数据AllowSynchronousContinuations false // 避免死锁};InitializeAudioEngine();// 以默认参数预初始化播放设备与播放器便于后续快速切换/播放try{InitializePlaybackDevice(_sampleRate, _channels);}catch (Exception ex){// 预初始化失败不致命延迟到首次播放再初始化_logger?.LogWarning(ex, SoundFlow预初始化失败将在首次播放时重试);}}////// 在构造函数中初始化音频引擎和基础组件///private void InitializeAudioEngine(){try{// 在构造时就初始化引擎_engine new MiniAudioEngine();// 显示可用的播放设备调试模式if (_logger ! null _logger.IsEnabled(LogLevel.Debug)){_logger.LogDebug(SoundFlow播放引擎初始化完成);_logger.LogDebug(可用SoundFlow播放设备:);for (int i 0; i _engine.PlaybackDevices.Length; i){var device _engine.PlaybackDevices[i];var status device.IsDefault ? (默认) : ;_logger.LogDebug( [{Index}] {Name}{Status}, i, device.Name, status);}}if (_engine.PlaybackDevices.Length 0){throw new InvalidOperationException(未找到SoundFlow音频播放设备);}}catch (Exception ex){_logger?.LogError(ex, 初始化SoundFlow播放引擎失败);throw;}}////// 初始化播放设备仅在参数变化时调用///private void InitializePlaybackDevice(int sampleRate, int channels){if (!ValidateAudioParameters(sampleRate, channels)){throw new ArgumentException(Invalid audio parameters);}// 如果参数相同且设备已初始化直接返回if (_playbackDevice ! null _sampleRate sampleRate _channels channels){return;}// 清理现有设备if (_playbackDevice ! null){try{_playbackDevice.Stop();_playbackDevice null;}catch (Exception ex){_logger?.LogDebug(ex, 清理旧播放设备时出错);}}_sampleRate sampleRate;_channels channels;try{// 引擎已在构造函数中初始化if (_engine null){throw new InvalidOperationException(SoundFlow引擎未正确初始化);}// 修复统一使用F32格式与QueueDataProvider保持一致var format new AudioFormat{SampleRate sampleRate,Channels channels,Format SampleFormat.F32 // 改为F32与播放器格式一致};_playbackDevice _engine.InitializePlaybackDevice(null, format, DeviceConfig);_logger?.LogDebug(已选择SoundFlow播放设备: {DeviceName}, _playbackDevice.Info?.Name ?? 默认设备);_logger?.LogDebug(播放设备格式: {Format}, {Channels}ch, {SampleRate}Hz,_playbackDevice.Format.Format, _playbackDevice.Format.Channels, _playbackDevice.Format.SampleRate);_logger?.LogInformation(SoundFlow音频播放器设备初始化成功: {SampleRate}Hz, {Channels}声道,sampleRate, channels);}catch (Exception ex){throw new Exception($初始化SoundFlow音频播放设备失败: {ex.Message}, ex);}}////// 初始化SoundPlayer与QueueDataProvider仅在参数变化时调用///private async Task InitializePlayer(int sampleRate, int channels){if (_engine null || _playbackDevice null){throw new InvalidOperationException(SoundFlow引擎或播放设备未初始化);}_sampleRate sampleRate;_channels channels;try{// 创建音频格式 - 匹配测试项目的要求var format new AudioFormat{SampleRate sampleRate,Channels channels,Format SampleFormat.F32 // QueueDataProvider使用Float32格式};// 清理旧播放器if (_soundPlayer ! null){try{_soundPlayer.Stop();_playbackDevice.MasterMixer.RemoveComponent(_soundPlayer);_soundPlayer.Dispose();}catch (Exception ex){_logger?.LogDebug(ex, 清理旧播放器时出错);}}_dataProvider?.Dispose();// 创建QueueDataProvider - 专为流式数据设计_dataProvider new QueueDataProvider(format);_dataProvider.EndOfStreamReached (s, e) {_logger?.LogDebug(SoundFlow数据提供者已到达流末尾);PlaybackStopped?.Invoke(this, EventArgs.Empty);};// 创建播放器_soundPlayer new SoundPlayer(_engine, format, _dataProvider);// 添加到播放设备的混音器_playbackDevice.MasterMixer.AddComponent(_soundPlayer);_logger?.LogDebug(SoundFlow播放器初始化完成: {SampleRate}Hz, {Channels}ch, sampleRate, channels);await Task.CompletedTask;}catch (Exception ex){_logger?.LogError(ex, SoundFlow播放器初始化错误);throw;}}4. 关键词唤醒支持两种唤醒词模型xiaodian小电你好小电cortana小娜你好小娜基于音频流实时检测CPU占用低响应速度快。关键词唤醒核心逻辑////// 初始化语音配置离线模式无需订阅密钥///private void InitializeSpeechConfig(){try{// 创建离线语音配置// 对于关键词检测可以使用空的配置因为我们使用本地.table文件_speechConfig SpeechConfig.FromSubscription(dummy, dummy);// 设置为离线模式_speechConfig.SetProperty(SPEECH-UseOfflineRecognition, true);_logger?.LogInformation(语音配置初始化成功离线模式);}catch (Exception ex){_logger?.LogError(ex, 初始化语音配置失败);_isEnabled false;}}////// 启动关键词检测对应py-xiaozhi的start方法///public async Task StartAsync(IAudioRecorder? audioRecorder null){if (!_isEnabled){_logger?.LogWarning(关键词检测功能未启用);return false;}if (_isRunning){_logger?.LogWarning(关键词检测已在运行);return true;}try{await _semaphore.WaitAsync();_cancellationTokenSource new CancellationTokenSource();// 设置音频源对应py-xiaozhi的多种启动模式if (audioRecorder ! null){_audioRecorder audioRecorder;_useExternalAudioSource true;_logger?.LogInformation(使用外部音频源启动关键词检测);}else{_useExternalAudioSource false;_logger?.LogInformation(使用独立音频模式启动关键词检测);}// 加载关键词模型if (!await LoadKeywordModelsAsync()){_logger?.LogError(加载关键词模型失败);return false;}// 配置音频输入 - 使用共享音频流管理器var audioConfig await ConfigureSharedAudioInput();if (audioConfig null){_logger?.LogError(配置音频输入失败);return false;}// 创建关键词识别器 - 确保每次启动都是全新实例_keywordRecognizer new KeywordRecognizer(audioConfig);// 订阅事件SubscribeToRecognizerEvents();// 在后台任务中启动关键词识别避免阻塞主流程_ Task.Run(async () {try{if (_keywordModel ! null _keywordRecognizer ! null){await _keywordRecognizer.RecognizeOnceAsync(_keywordModel);_logger?.LogInformation(关键词识别已启动后台任务);}}catch (Exception ex)