简单说一下实现步骤,然后就贴代码:

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)        {            // 线程安全更新 UI            Dispatcher.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, // 替换为你的实际 AppID                    appkey = _apiKey, // 替换为你的实际 API Key                    dev_pid = 15372, // 识别模型,推荐15372                    cuid = "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

可见我的公众号(虾码艺术)上一篇文章,哈哈

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐