C#.Net实现百度语音实时识别结合AI实现说话写代码
·

简单说一下实现步骤,然后就贴代码:
1、购买百度实时语音包,新用户2块钱10小时(自己玩玩是够用的)
2、开通百度实时语音并生成apiKey、secretKey
3、申请通义千问api接口(也需要付费,但很便宜,几分钱就能玩好久)
4、建立一个WPF项目(百度实时语音可以兼容framework版本,再高的版本还没尝试过)
5、调试百度实时语音代码,它采用websocket模式链接百度服务器接口
6、调试通义千问api(我上一篇文章有详细的代码可以参考)
7、通义千问生成的代码,保存到数据库
8、web系统实时轮询数据库的数据,当发生变化则提取到页面进行渲染
注意:
1、WPF调用的百度实时语音使用的异步回调,如果正式使用还需要多方位测试
2、百度实时语音经过测试,存在一次最多识别10秒的限制(猜测),本次代码已经增加补充逻辑,当判断百度反馈结束实时语音后自动开启新的进程来抓取语音信息,防止中断带来的不便
3、为了让玩具更好玩,也因为识别说话存在误识别,比如我要说“画一个页面”,识别成“换一个页面”针对这种情况增加了删除上一句逻辑,如果说了“不要上一句”则会删除上一次识别的纪录。
4、同样当我们表达完成要生成代码的时候可以说一句“好了,试一下”则开始生成代码啦。
上代码:(总是这么善良,直接上源码)
MainWindow.xaml
<Window x:Class="BaiduAsrDemo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="虾码玩具" Height="500" Width="600"><Grid Margin="20"><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="*"/><RowDefinition Height="*"/><RowDefinition Height="Auto"/></Grid.RowDefinitions><TextBlock Name="StatusTextBlock" Grid.Row="0"Text="就绪" Margin="0,0,0,10" Foreground="Gray"/><TextBox Grid.Row="1"Name="ResultTextBox"TextWrapping="Wrap"IsReadOnly="True"FontSize="16"Margin="0,0,0,10"/><Label Grid.Row="2" Content="识别历史:" FontWeight="Bold"/><TextBox Grid.Row="2"Name="HistoryTextBox"TextWrapping="Wrap"AcceptsReturn="True"VerticalScrollBarVisibility="Auto"FontSize="14" Margin="0,34.429,0.286,0.429" Grid.RowSpan="2"/><StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0"><Button Name="StartButton" Content="开始识别" Width="100" Height="30" Click="StartButton_Click" Margin="0,0,10,0"/><Button Name="StopButton" Content="停止" Width="100" Height="30" Click="StopButton_Click" IsEnabled="False"/></StackPanel></Grid></Window>
MainWindow.xaml.cs
using System;using System.Linq;using System.Windows;using System.Windows.Threading;using yixiang;namespace BaiduAsrDemo{public partial class MainWindow : Window{private BaiduSpeechRecognizer _recognizer;public MainWindow(){InitializeComponent();// TODO: 替换为你的百度 AI 凭据var apiKey = "XXXXXXXd";var secretKey = "XXXXX4201";var appId = 10xxxxxx;//这里是数字_recognizer = new BaiduSpeechRecognizer(apiKey, secretKey, appId);// 订阅事件_recognizer.StatusChanged += OnStatusChanged;_recognizer.ResultChanged += OnResultChanged;_recognizer.FinalResultReceived += OnFinalResultReceived;}private void OnStatusChanged(string status){// 线程安全更新 UIDispatcher.Invoke(() => StatusTextBlock.Text = $"[状态] {status}");}private void OnResultChanged(string result){Dispatcher.Invoke(() => ResultTextBox.Text = result);}private void OnFinalResultReceived(string result){Dispatcher.Invoke(() =>{if (string.IsNullOrEmpty(result))return;//新的一轮会话删除等待术语if (HistoryTextBox.Text.Contains("尝试中,请稍等")){HistoryTextBox.Text = "";}if ((result.Contains("上一句") && (result.Contains("不要") || result.Contains("重说")))|| result.Contains("说错了")){// 删除最后一行string text = HistoryTextBox.Text.TrimEnd('\r', '\n');if (string.IsNullOrEmpty(text))return;string[] lines = text.Split(new[] { "\r\n" }, StringSplitOptions.None);if (lines.Length > 0){string updatedText = string.Join("\r\n", lines.Take(lines.Length - 1));// ✅ 确保结尾有换行符(否则下一次 AppendText 可能出问题)HistoryTextBox.Text = updatedText + "\r\n";}else{HistoryTextBox.Text = string.Empty;}}else if ( result.Contains("试一下")){string question = HistoryTextBox.Text.Replace("\r\n", ",");//调用通义千问,生成html文本QianWen.SendMessageAsync(question + ",仅返回完整html代码,不需要文字描述");HistoryTextBox.Text = "尝试中,请稍等";}else{// ❌ 不要再用 AppendText!// ✅ 改为统一使用 Text 拼接HistoryTextBox.Text += result + "\r\n";}// ✅ 所有修改后都滚动到底部HistoryTextBox.ScrollToEnd();}//HistoryTextBox.AppendText(result + "\r\n"));}private async void StartButton_Click(object sender, RoutedEventArgs e){await _recognizer.StartRecognitionAsync();StartButton.IsEnabled = false;StopButton.IsEnabled = true;}private async void StopButton_Click(object sender, RoutedEventArgs e){await _recognizer.StopRecognitionAsync();StartButton.IsEnabled = true;StopButton.IsEnabled = false;}protected override void OnClosed(EventArgs e){_recognizer?.Dispose();base.OnClosed(e);}}}
BaiduSpeechRecognizer.cs
using System;using System.IO;using System.Net.WebSockets;using System.Text;using System.Threading;using System.Threading.Tasks;using System.Net.Http;using Newtonsoft.Json;using NAudio.Wave;using System.Security.Cryptography;using Newtonsoft.Json.Linq;namespace BaiduAsrDemo{public class BaiduSpeechRecognizer : IDisposable{// ========== 配置参数 ==========private const string TokenUrl = "https://aip.baidubce.com/oauth/2.0/token";private const string WebSocketUrlTemplate = "wss://vop.baidu.com/realtime_asr?sn={0}&token={1}";// 音频参数(必须与百度要求一致)private const int SampleRate = 16000;private const int BitsPerSample = 16;private const int Channels = 1;// ========== 成员变量 ==========private readonly string _apiKey;private readonly string _secretKey;private readonly int _appId;private readonly string _cuid;private ClientWebSocket _webSocket;private WaveInEvent _waveIn;private CancellationTokenSource _cts;private volatile bool _isRunning;private readonly object _sendLock = new object(); // 防止 SendAsync 并发的关键锁// ========== 事件 ==========public event Action<string> StatusChanged;public event Action<string> ResultChanged;public event Action<string> FinalResultReceived;// ========== 构造函数 ==========public BaiduSpeechRecognizer(string apiKey, string secretKey, int appId, string cuid = "WPF_CLIENT"){_apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey)); ;_secretKey = secretKey ?? throw new ArgumentNullException(nameof(secretKey));_appId = appId;_cuid = cuid;}// ========== 主要方法 ==========public async Task StartRecognitionAsync(){if (_isRunning){OnStatusChanged("识别已在运行中。");return;}_isRunning = true;_cts = new CancellationTokenSource();try{OnStatusChanged("正在获取 access_token...");var token = await GetAccessTokenAsync();if (string.IsNullOrEmpty(token)){OnStatusChanged("获取 access_token 失败。");return;}OnStatusChanged("access_token 获取成功,正在连接 WebSocket...");var sn = GenerateSn(); // 会话编号var wsUrl = string.Format(WebSocketUrlTemplate, sn, token);_webSocket = new ClientWebSocket();await _webSocket.ConnectAsync(new Uri(wsUrl), _cts.Token);OnStatusChanged("WebSocket 连接成功,发送 START 指令...");await SendStartCommandAsync();OnStatusChanged("已发送 START 指令,准备开始录音...");// 启动接收识别结果任务_ = Task.Run(() => ReceiveRecognitionResultsAsync(_cts.Token), _cts.Token);// 初始化麦克风_waveIn = new WaveInEvent{WaveFormat = new WaveFormat(SampleRate, BitsPerSample, Channels),BufferMilliseconds = 100};_waveIn.DataAvailable += OnWaveInDataAvailable;_waveIn.StartRecording();OnStatusChanged("麦克风已启动,开始实时识别...");}catch (OperationCanceledException){OnStatusChanged("识别已取消。");}catch (Exception ex){OnStatusChanged($"启动失败: {ex.Message}");await HandleErrorAsync(ex);}}public async Task StopRecognitionAsync(){if (!_isRunning) return;_isRunning = false;_cts?.Cancel();try{if (_waveIn != null){_waveIn.StopRecording();_waveIn.DataAvailable -= OnWaveInDataAvailable;_waveIn.Dispose();_waveIn = null;}if (_webSocket != null && _webSocket.State == WebSocketState.Open){byte[] messageBytes = Encoding.UTF8.GetBytes("{\"type\":\"STOP\"}");ArraySegment<byte> messageSegment = new ArraySegment<byte>(messageBytes);await _webSocket.SendAsync(messageSegment,WebSocketMessageType.Text,true,CancellationToken.None);await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure,"Stop recognition",CancellationToken.None);}_webSocket?.Dispose();_webSocket = null;OnStatusChanged("识别已停止");}catch (Exception ex){// 处理异常}}// ========== 私有方法 ==========private async Task<string> GetAccessTokenAsync(){var client = new HttpClient();var requestUri = $"{TokenUrl}?grant_type=client_credentials&client_id={_apiKey}&client_secret={_secretKey}";var response = await client.GetAsync(requestUri);response.EnsureSuccessStatusCode();var json = await response.Content.ReadAsStringAsync();var tokenObj = JObject.Parse(json);return tokenObj["access_token"]?.ToString();}private async Task SendStartCommandAsync(){var startMessage = new{type = "START",data = new{appid = _appId, // 替换为你的实际 AppIDappkey = _apiKey, // 替换为你的实际 API Keydev_pid = 15372, // 识别模型,推荐15372cuid = "cuid-1", // 设备唯一ID,可以随意填写format = "pcm", // 固定格式sample = 16000 // 固定采样率}};string json = System.Text.Json.JsonSerializer.Serialize(startMessage);await SendTextAsync(json);}private async void OnWaveInDataAvailable(object sender, WaveInEventArgs e){if (_webSocket == null || _webSocket.State != WebSocketState.Open) return;try{// 直接发送原始 PCM 数据(二进制)await SendBinaryAsync(e.Buffer, 0, e.BytesRecorded);}catch (Exception ex){OnStatusChanged($"音频发送失败: {ex.Message}");await HandleErrorAsync(ex);}}private async Task ReceiveRecognitionResultsAsync(CancellationToken ct){var buffer = new byte[1024 * 4];var sb = new StringBuilder(); // 用于拼接部分结果while (!ct.IsCancellationRequested && _webSocket.State == WebSocketState.Open){try{var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), ct);if (result.MessageType == WebSocketMessageType.Close){await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);break;}if (result.MessageType != WebSocketMessageType.Text) continue;var json = Encoding.UTF8.GetString(buffer, 0, result.Count);var response = JsonConvert.DeserializeObject<BaiduAsrResponse>(json);if (response == null) continue;switch (response.Type?.ToLower()){case "text":sb.Clear();sb.Append(response.Result);OnResultChanged(sb.ToString());OnStatusChanged("收到中间结果");break;case "fin_text":OnFinalResultReceived(response.Result);OnResultChanged(response.Result);OnStatusChanged("收到最终结果: " + response.Result);break;case "error":OnStatusChanged($"识别错误: {response.Error} (Code: {response.ErrorCode})");await HandleErrorAsync(new Exception(response.Error));break;}}catch (OperationCanceledException){break;}catch (Exception ex) when (!(ex is OperationCanceledException)){if (_webSocket.State != WebSocketState.Aborted)OnStatusChanged($"接收结果异常: {ex.Message}");break;}}}// ✅ 安全发送文本(加锁防止并发)private async Task SendTextAsync(string text){var bytes = Encoding.UTF8.GetBytes(text);await SendBinaryAsync(bytes, 0, bytes.Length, WebSocketMessageType.Text);}// ✅ 安全发送二进制数据(加锁)private readonly SemaphoreSlim _sendSemaphore = new SemaphoreSlim(1, 1);private async Task SendBinaryAsync(byte[] buffer, int offset, int count, WebSocketMessageType type = WebSocketMessageType.Binary){if (_webSocket?.State != WebSocketState.Open) return;await _sendSemaphore.WaitAsync(); // 等待信号量try{var tcs = new TaskCompletionSource<bool>();var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); // 超时保护cts.Token.Register(() => tcs.TrySetException(new TimeoutException("WebSocket SendAsync 超时")));_webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), type, true, cts.Token).ContinueWith(t =>{if (t.IsFaulted) tcs.TrySetException(t.Exception);else tcs.TrySetResult(true);});await tcs.Task;}catch (Exception ex){throw new Exception($"发送二进制数据失败: {ex.Message}");}finally{_sendSemaphore.Release(); // 释放信号量}}private async Task HandleErrorAsync(Exception ex){await StopRecognitionAsync();// 可选:自动重连(示例:3秒后尝试重启)// Task.Delay(3000).ContinueWith(_ => StartRecognitionAsync());}private string GenerateSn(){return Guid.NewGuid().ToString("N").Substring(0, 16);}// ========== 事件触发 ==========private void OnStatusChanged(string status) => StatusChanged?.Invoke(status);private void OnResultChanged(string result) => ResultChanged?.Invoke(result);private void OnFinalResultReceived(string result) => FinalResultReceived?.Invoke(result);// ========== Dispose ==========public void Dispose(){_cts?.Cancel();_cts?.Dispose();_waveIn?.StopRecording();_waveIn?.Dispose();_webSocket?.Dispose();}// ========== 响应模型 ==========private class AccessTokenResponse{[JsonProperty("access_token")]public string access_token { get; set; }}private class BaiduAsrResponse{[JsonProperty("type")]public string Type { get; set; }[JsonProperty("result")]public string Result { get; set; }[JsonProperty("error")]public string Error { get; set; }[JsonProperty("err_no")]public int? ErrorCode { get; set; }}}}
QianWen.cs
可见我的公众号(虾码艺术)上一篇文章,哈哈
更多推荐


所有评论(0)