基于CSCore与System.Speech.Recognition的实时音频流识别问题求助
解决System.Speech.Recognition无法实时读取CSCore捕获音频流的问题
你碰到的问题核心在于普通的MemoryStream是静态缓冲区——当你把Position设为0后,识别引擎会一次性读到流的末尾,之后新捕获的音频数据虽然追加到了流里,但引擎不会主动去轮询新数据,自然不会触发SpeechRecognized或SpeechDetected事件。
网上提到的自定义Stream方案,本质是实现一个支持阻塞读取的流:当识别引擎调用Read时,如果没有新数据,流会等待直到有数据写入,这样引擎就能持续获取实时音频。下面是具体的实现步骤和代码:
1. 实现自定义阻塞式Stream
我们可以写一个基于并发队列配合信号量的阻塞流,逻辑简单易懂,能满足实时读取的需求:
using System; using System.Collections.Concurrent; using System.IO; using System.Threading; public class BlockingAudioStream : Stream { private readonly ConcurrentQueue<byte[]> _dataQueue = new ConcurrentQueue<byte[]>(); private readonly AutoResetEvent _dataReadySignal = new AutoResetEvent(false); private bool _isStreamClosed; public override bool CanRead => true; public override bool CanSeek => false; public override bool CanWrite => true; public override long Length => throw new NotSupportedException(); public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } public override void Flush() { } public override int Read(byte[] buffer, int offset, int count) { // 如果流已关闭且无剩余数据,返回0表示读取结束 if (_isStreamClosed && _dataQueue.IsEmpty) return 0; // 阻塞等待,直到有新数据或流关闭 while (_dataQueue.IsEmpty && !_isStreamClosed) _dataReadySignal.WaitOne(); if (_dataQueue.TryDequeue(out var dataChunk)) { int copySize = Math.Min(count, dataChunk.Length); Array.Copy(dataChunk, 0, buffer, offset, copySize); // 如果当前数据块未读完,把剩余部分放回队列 if (dataChunk.Length > copySize) _dataQueue.Enqueue(dataChunk.AsSpan(copySize).ToArray()); return copySize; } return 0; } public override void Write(byte[] buffer, int offset, int count) { if (_isStreamClosed) throw new ObjectDisposedException(nameof(BlockingAudioStream)); // 复制数据到新数组,避免外部修改原缓冲区 var dataToEnqueue = new byte[count]; Array.Copy(buffer, offset, dataToEnqueue, 0, count); _dataQueue.Enqueue(dataToEnqueue); _dataReadySignal.Set(); // 通知读取端有新数据可用 } public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); public override void SetLength(long value) => throw new NotSupportedException(); protected override void Dispose(bool disposing) { if (disposing) { _isStreamClosed = true; _dataReadySignal.Set(); // 唤醒所有等待的读取操作 _dataReadySignal.Dispose(); } base.Dispose(disposing); } }
这个流的核心逻辑:
Write方法接收捕获到的音频数据块,加入队列后触发信号通知读取端Read方法会阻塞等待新数据,确保识别引擎能持续获取实时音频,不会提前读到流末尾
2. 配置CSCore捕获格式,匹配识别引擎要求
System.Speech.Recognition默认支持的音频格式为PCM 16位、16kHz、单声道,我们需要把WasapiLoopbackCapture的格式调整为对应参数,避免格式不兼容导致识别失败:
private WasapiLoopbackCapture _audioCapture; private BlockingAudioStream _blockingStream; private void LoadTargetDevice() { // 初始化默认循环捕获设备 _audioCapture = new WasapiLoopbackCapture(); // 设置为识别引擎兼容的音频格式 _audioCapture.WaveFormat = new WaveFormat(16000, 16, 1); // 绑定数据捕获事件 _audioCapture.DataAvailable += AudioCapture_DataAvailable; }
3. 修改捕获与识别的启动逻辑
替换原来的MemoryStream为自定义的BlockingAudioStream,调整启动流程:
private void startButton_Click(object sender, EventArgs e) { // 重置识别引擎 _recognitionEngine.UnloadAllGrammars(); _recognitionEngine.LoadGrammar(new DictationGrammar()); // 绑定识别事件 _recognitionEngine.SpeechDetected += RecognitionEngine_SpeechDetected; _recognitionEngine.SpeechRecognized += RecognitionEngine_SpeechRecognized; // 初始化捕获组件与阻塞流 LoadTargetDevice(); _blockingStream = new BlockingAudioStream(); // 启动音频捕获 _audioCapture.Start(); // 设置识别引擎输入为阻塞流(无需重置Position) _recognitionEngine.SetInputToWaveStream(_blockingStream); _recognitionEngine.RecognizeAsync(RecognizeMode.Multiple); } // 捕获数据的回调:将音频数据写入阻塞流 private void AudioCapture_DataAvailable(object sender, DataAvailableEventArgs e) { _blockingStream.Write(e.Data, 0, e.ByteCount); } // 测试语音检测事件 private void RecognitionEngine_SpeechDetected(object sender, SpeechDetectedEventArgs e) { Debug.WriteLine("已检测到语音输入"); } // 测试语音识别事件 private void RecognitionEngine_SpeechRecognized(object sender, SpeechRecognizedEventArgs e) { Debug.WriteLine($"识别结果:{e.Result.Text}"); }
4. 资源释放注意事项
记得在停止操作时释放所有资源,避免内存泄漏:
private void stopButton_Click(object sender, EventArgs e) { _recognitionEngine.RecognizeAsyncStop(); _audioCapture.Stop(); _blockingStream.Dispose(); _audioCapture.Dispose(); }
如果识别仍有问题,可以通过_recognitionEngine.RecognizerInfo.SupportedAudioFormats查看引擎支持的所有格式,确保CSCore的捕获格式完全匹配。
这个方案通过自定义流解决了实时数据读取的核心问题,完全符合你想要的临时学习方案需求。
内容的提问来源于stack exchange,提问作者Johnson Whitler
相关产品推荐
相关产品推荐

