Azure C#语音双向交互自监听问题解决方案问询
问题
在使用Azure Speech to Text与TTS的C#实现双向语音交互时,麦克风会拾取应用自身合成的语音,引发无限循环。需要在开放式扬声器场景下实现:过滤应用自身语音,避免被麦克风识别,同时支持用户打断正在进行的语音合成并处理输入音频。
已知使用耳机可解决该问题,但需适配扬声器场景。当前已实现标准的麦克风监听与语音合成函数,代码如下:
监听函数
public async Task Listen() { var stopRecognition = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously); using var audioProcessingOptions = AudioProcessingOptions.Create(AudioProcessingConstants.AUDIO_INPUT_PROCESSING_ENABLE_DEFAULT); using var audioInput = AudioConfig.FromDefaultMicrophoneInput(audioProcessingOptions); using var recognizer = new SpeechRecognizer(Config, audioInput); recognizer.Recognized += Recognizer_Recognized; await recognizer.StartContinuousRecognitionAsync().ConfigureAwait(false); // Waits for completion. // Use Task.WaitAny to keep the task rooted. Task.WaitAny(new[] { stopRecognition.Task }); // Stops recognition. await recognizer.StopContinuousRecognitionAsync().ConfigureAwait(false); }
识别回调
private void Recognizer_Recognized(object? sender, SpeechRecognitionEventArgs e) { // 将识别出的文本推送至事件以显示在屏幕上... }
语音合成函数
public async Task Talk(string text) { // 为Windows平台支持中文字符 if (Environment.OSVersion.Platform == PlatformID.Win32NT) { Console.InputEncoding = System.Text.Encoding.Unicode; Console.OutputEncoding = System.Text.Encoding.Unicode; } // 设置语音名称 Config.SpeechSynthesisVoiceName = "en-AU-CarlyNeural"; // 使用默认扬声器作为音频输出创建语音合成器 using var synthesizer = new SpeechSynthesizer(Config); using var result = await synthesizer.SpeakTextAsync(text); if (result.Reason == ResultReason.SynthesizingAudioCompleted) { // 合成完成处理 } else if (result.Reason == ResultReason.Canceled) { var cancellation = SpeechSynthesisCancellationDetails.FromResult(result); } }
核心矛盾:如果在语音合成时停止监听,无法支持用户打断长时间播报;如果持续监听,麦克风会拾取自身合成的语音,陷入循环。
解决方案
1. 启用Azure Speech SDK内置回声消除功能
Azure Speech SDK的音频处理选项中包含回声消除能力,默认配置已启用基础处理,但可以明确指定开启针对性的回声消除与噪声抑制,增强过滤效果:
// 明确开启回声消除和噪声抑制 using var audioProcessingOptions = AudioProcessingOptions.Create( AudioProcessingConstants.AUDIO_INPUT_PROCESSING_ENABLE_ECHO_CANCELLATION | AudioProcessingConstants.AUDIO_INPUT_PROCESSING_ENABLE_NOISE_SUPPRESSION);
注意:该功能依赖硬件和系统底层支持,部分设备可能需要额外配置优化。
2. 结合合成状态过滤识别结果
通过标记合成状态,在识别回调中直接忽略合成期间的麦克风输入,同时支持实时打断:
// 全局变量标记是否正在合成语音 private bool _isSpeaking = false; private CancellationTokenSource? _talkCts; private void Recognizer_Recognized(object? sender, SpeechRecognitionEventArgs e) { // 识别到有效输入时,立即取消当前合成任务 _talkCts?.Cancel(); // 如果处于合成状态,忽略本次识别结果 if (_isSpeaking) return; // 处理用户输入逻辑... } public async Task Talk(string text) { _talkCts = new CancellationTokenSource(); var cancellationToken = _talkCts.Token; try { _isSpeaking = true; Config.SpeechSynthesisVoiceName = "en-AU-CarlyNeural"; using var synthesizer = new SpeechSynthesizer(Config); // 支持取消令牌的合成方法,实现实时打断 using var result = await synthesizer.SpeakTextAsync(text, cancellationToken); if (result.Reason == ResultReason.Canceled) { var cancellation = SpeechSynthesisCancellationDetails.FromResult(result); // 处理取消后的逻辑,比如日志或状态重置 } } finally { _isSpeaking = false; _talkCts.Dispose(); _talkCts = null; } }
3. 本地音频旁路与实时匹配过滤
如果SDK内置功能不足以满足需求,可以通过手动处理合成音频流的方式实现精准过滤:
- 在合成时,先将音频数据输出到内存流而非直接播放;
- 使用第三方音频库播放内存流中的音频;
- 在监听模块中,将麦克风输入的音频与当前播放的合成音频进行实时对比,过滤掉完全匹配的片段。
修改后的合成函数示例:
public async Task Talk(string text, CancellationToken cancellationToken) { if (Environment.OSVersion.Platform == PlatformID.Win32NT) { Console.InputEncoding = System.Text.Encoding.Unicode; Console.OutputEncoding = System.Text.Encoding.Unicode; } Config.SpeechSynthesisVoiceName = "en-AU-CarlyNeural"; // 不直接绑定扬声器输出,手动处理音频流 using var synthesizer = new SpeechSynthesizer(Config, null); var result = await synthesizer.SpeakTextAsync(text); if (result.Reason != ResultReason.SynthesizingAudioCompleted) { var cancellation = SpeechSynthesisCancellationDetails.FromResult(result); return; } // 使用自定义音频播放器播放合成的音频数据(需自行实现或使用第三方库) var audioPlayer = new CustomAudioPlayer(result.AudioData); await audioPlayer.PlayAsync(cancellationToken); }
此方法需要额外开发音频对比逻辑,适合对过滤精度要求极高的场景。
内容的提问来源于stack exchange,提问作者Blakey
相关产品推荐
相关产品推荐

