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

CommunityToolkit.SpeechToText在.NET MAUI中性能异常缓慢求助

.NET MAUI CommunityToolkit 语音转文本超时问题

问题现象

基于官方示例实现的.NET MAUI语音转文本功能,使用CommunityToolkit的ISpeechToText接口时,无论是调用ListenAsync还是事件驱动方式,都无法自动检测语音结束,必须等待1分钟超时后才返回识别结果。在iPad测试时,说出"This is a test"后,调试窗口需等待1分钟才输出:

[0:] Success This is a test Time 00:01:00.9459623

且无论何时说话均存在该问题,同时ISpeechToText方法未提供超时设置项。

测试代码

async Task StartListener(CancellationToken cancellationToken)
{
    var isGranted = await speechToText.RequestPermissions(cancellationToken);
    if (!isGranted)
    {
        await Toast.Make("Permission not granted").Show(CancellationToken.None);
        return;
    }

    do
    {
        Stopwatch sw = Stopwatch.StartNew();

        var recognitionResult = await speechToText.ListenAsync(
                                    CultureInfo.GetCultureInfo(Language),
                                    new Progress<string>(partialText =>
                                    {
                                        RecognitionText += partialText + " ";
                                    }), cancellationToken);

        sw.Stop();

        if (recognitionResult.IsSuccessful)
        {
            RecognitionText = recognitionResult.Text;
            Debug.WriteLine("Success " + RecognitionText + " Time " + sw.Elapsed);
            RecognitionText = string.Empty;
        }
        else
        {
            Debug.WriteLine("failed - Time " + sw.Elapsed);
        }
    } while (!cancellationToken.IsCancellationRequested);
}

问题原因

  1. iOS平台底层默认配置:CommunityToolkit的ISpeechToText在iOS端依赖SFSpeechRecognizer,系统默认无语音超时时间为60秒,Toolkit封装时未暴露该配置项。
  2. 识别模式特性:默认的ListenAsync采用连续识别模式,不会自动在用户停止说话时终止,必须等待超时或手动取消。

优化方案

方案1:手动检测静音,主动取消识别

通过监听部分识别结果的更新时间,结合静音时长判断,手动取消识别任务:

async Task StartListener(CancellationToken cancellationToken)
{
    var isGranted = await speechToText.RequestPermissions(cancellationToken);
    if (!isGranted)
    {
        await Toast.Make("Permission not granted").Show(CancellationToken.None);
        return;
    }

    do
    {
        Stopwatch sw = Stopwatch.StartNew();
        var lastUpdateTime = DateTime.Now;
        var listenCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

        var recognitionResult = await speechToText.ListenAsync(
                                    CultureInfo.GetCultureInfo(Language),
                                    new Progress<string>(partialText =>
                                    {
                                        RecognitionText += partialText + " ";
                                        lastUpdateTime = DateTime.Now;
                                        // 后台检测静音
                                        _ = Task.Run(async () =>
                                        {
                                            while (!listenCts.Token.IsCancellationRequested)
                                            {
                                                if ((DateTime.Now - lastUpdateTime).TotalSeconds >= 2)
                                                {
                                                    listenCts.Cancel();
                                                    break;
                                                }
                                                await Task.Delay(500);
                                            }
                                        }, listenCts.Token);
                                    }), listenCts.Token);

        sw.Stop();

        if (recognitionResult.IsSuccessful)
        {
            RecognitionText = recognitionResult.Text;
            Debug.WriteLine("Success " + RecognitionText + " Time " + sw.Elapsed);
            RecognitionText = string.Empty;
        }
        else if (recognitionResult.Status == SpeechToTextStatus.Canceled)
        {
            Debug.WriteLine("Recognition canceled (silence detected) - Time " + sw.Elapsed);
        }
        else
        {
            Debug.WriteLine("failed - Time " + sw.Elapsed);
        }
    } while (!cancellationToken.IsCancellationRequested);
}

方案2:自定义iOS平台实现,修改系统超时参数

由于Toolkit未暴露SFSpeechRecognizer的超时配置,可通过自定义平台代码修改:

  1. 在iOS项目中创建自定义实现,继承Toolkit的SpeechToTextImplementation:
[assembly: Dependency(typeof(CustomSpeechToText))]
namespace YourApp.iOS
{
    public class CustomSpeechToText : SpeechToTextImplementation
    {
        protected override async Task<SpeechToTextResult> StartListeningAsync(CultureInfo culture, IProgress<string>? recognitionProgress, CancellationToken cancellationToken)
        {
            var recognizer = new SFSpeechRecognizer(new NSLocale(culture.Name));
            if (recognizer == null)
            {
                return new SpeechToTextResult(SpeechToTextStatus.NotSupported, null);
            }

            var request = new SFSpeechAudioBufferRecognitionRequest();
            request.ShouldReportPartialResults = true;
            // 设置无语音超时为2秒
            request.EndSilenceTimeout = TimeSpan.FromSeconds(2);

            return await base.StartListeningAsync(culture, recognitionProgress, cancellationToken);
        }
    }
}
  1. 在MauiProgram.cs中替换默认注册:
builder.Services.AddSingleton<ISpeechToText, CustomSpeechToText>();

方案3:事件驱动模式,主动停止识别

使用ListenWithEventsAsync方法,结合静音检测主动调用StopListenAsync:

async Task StartListener(CancellationToken cancellationToken)
{
    var isGranted = await speechToText.RequestPermissions(cancellationToken);
    if (!isGranted)
    {
        await Toast.Make("Permission not granted").Show(CancellationToken.None);
        return;
    }

    speechToText.RecognitionResultUpdated += (s, e) =>
    {
        RecognitionText += e.RecognitionResult + " ";
        lastUpdateTime = DateTime.Now;
    };

    do
    {
        Stopwatch sw = Stopwatch.StartNew();
        var lastUpdateTime = DateTime.Now;
        var listenTask = speechToText.ListenWithEventsAsync(CultureInfo.GetCultureInfo(Language), cancellationToken);

        // 静音检测任务
        var silenceTask = Task.Run(async () =>
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                if ((DateTime.Now - lastUpdateTime).TotalSeconds >= 2)
                {
                    await speechToText.StopListenAsync();
                    break;
                }
                await Task.Delay(500);
            }
        }, cancellationToken);

        await listenTask;
        sw.Stop();

        var result = speechToText.CurrentResult;
        if (result != null && result.IsSuccessful)
        {
            Debug.WriteLine("Success " + result.Text + " Time " + sw.Elapsed);
            RecognitionText = string.Empty;
        }
        else
        {
            Debug.WriteLine("failed - Time " + sw.Elapsed);
        }
    } while (!cancellationToken.IsCancellationRequested);
}

内容的提问来源于stack exchange,提问作者Gratzy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 12:04:53