You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:55:40