TRTC 开麦语音识别 + 房间流行语(Buzzword)开发者说明
本文说明 trtc_speech_recognizer 插件与主工程 PopularPhraseUtil 流行语命中如何协作,以及为提高开麦说话时命中准确率已做的优化与后续可演进方向。面向 Flutter / iOS 原生同事,尽量通俗。
1. 整体在做什么
用户在语音房内开麦说话,本地用 TRTC 采集到的 PCM 喂给 Apple Speech(SFSpeechRecognizer,zh_CN),实时得到整句转写文本;业务侧用后台下发的流行语词表做子串匹配,命中后播特效(飘字 / 表情雨等)。
插件不负责流行语逻辑,只负责「把麦上音频变成字符串」;流行语算法在主工程 lib/utils/util_popular_phrase.dart,由 RoomBuzzwordViewModel 在 onTextUpdated 里调用。
flowchart LR
subgraph iOS["iOS 原生"]
TRTC["TRTC 本地处理后音频帧"]
PCM["PCM → SFSpeechAudioBufferRecognitionRequest"]
ASR["Apple Speech zh_CN"]
TRTC --> PCM --> ASR
end
subgraph Flutter["Flutter 业务"]
EVT["EventChannel 文本流"]
MATCH["PopularPhraseUtil.matchPopularPhrase"]
FX["特效 / clearTranscript"]
EVT --> MATCH --> FX
end
ASR -->|"bestTranscription.formattedString"| EVT
2. 关键代码位置(便于跳转维护)
| 职责 | 路径 |
|---|---|
| 插件 Dart API、与业务对齐的静态入口 | packages/trtc_speech_recognizer/lib/trtc_speech_recognizer.dart |
| iOS:TRTC 音频帧 → Speech、清空 transcript、事件发送 | packages/trtc_speech_recognizer/ios/Classes/TrtcSpeechRecognizerPlugin.swift |
| 流行语词表拉取、开麦检测、命中后特效 | lib/modules/room/view-model/view_model_room_buzzword.dart |
| 子串匹配 + 规范化(本次优化核心) | lib/utils/util_popular_phrase.dart |
| 词表模型 | lib/modules/room/model/model_buzzword.dart |
3. 开麦检测到命中:时序(简化)
sequenceDiagram
participant VM as RoomBuzzwordViewModel
participant TR as TrtcSpeechRecognizer
participant iOS as TrtcSpeechRecognizerPlugin
participant U as PopularPhraseUtil
VM->>TR: initialize / startDetection
TR->>iOS: MethodChannel + EventChannel 监听
loop 音频帧
iOS->>iOS: appendPCMToSpeech
iOS-->>VM: onTextUpdated(整段转写)
VM->>U: matchPopularPhrase(text)
alt 命中
VM->>VM: onKeywordHitHandle
VM->>TR: clearTranscript
TR->>iOS: 结束当前 request,新建 request,忽略旧 final
iOS-->>VM: onTextUpdated("")
end
end
要点:
shouldReportPartialResults = true,因此onTextUpdated会频繁回调(中间结果 + 最终结果),有利于尽早命中流行语。- 命中后
clearTranscript:结束当前识别段、新建SFSpeechAudioBufferRecognitionRequest,并ignoreNextFinalResult防止清空后旧任务最后一次final把旧句子又推回来(交互上避免「闪回」)。
4. 原流行语算法与痛点
4.1 原逻辑(概念)
- 对词表
keywords按name字符串长度从长到短排序,避免「牛」盖住「牛马」。 - 对识别文本
trim后,做contains(关键词)子串匹配。
4.2 主要痛点:ASR 文本形态与词表不一致
Apple Speech 的 SFTranscription.formattedString 在中文场景下,经常在字与字之间插入空格(排版/分词风格)。例如用户说的语义是「牛马」,识别结果可能是 牛 马。此时:
- 词表配置为
牛马 - 旧逻辑:
"牛 马".contains("牛马")→ false → 漏命中
同类问题还包括:零宽字符、从运营后台复制带来的 BOM、全角英文字母与半角不一致、英文大小写等。
5. 本次已落地的优化(提高命中率)
在 PopularPhraseUtil 中增加统一入口 normalizeForPopularPhraseMatch,在匹配前对「识别文本」和「关键词」做同一套规范化,再执行 contains:
- 去掉空白:合并并移除
\s+,消除中文间空格导致的漏匹配。 - 去掉零宽字符与 BOM:
\u200B-\u200D、\uFEFF。 - 全角 ASCII → 半角:Unicode
FF01–FF5E映射到21–7E(常见运营配置全角符号场景)。 - 英文统一小写:
toLowerCase(),避免大小写造成的漏匹配。
排序规则改为按 「规范化后的关键词长度」 从长到短,与真实匹配语义一致。
实现见:lib/utils/util_popular_phrase.dart 中 normalizeForPopularPhraseMatch 与 matchPopularPhrase。
5.1 优化前后对照(示意)
| 识别文本(示例) | 词表关键词 | 旧逻辑 | 新逻辑 |
|---|---|---|---|
牛 马 | 牛马 | 不命中 | 命中 |
牛\u200B马 | 牛马 | 不命中 | 命中 |
HELLO | hello | 视情况不命中 | 命中(全角转半角 + 小写) |
6. 性能与交互说明
- 词表规模:当前是
O(n)遍历 + 每次对关键词做一次规范化;n为词表条数。一般房间流行语量级较小,可接受。若未来上千条,可考虑:- 在
initBuzzwordList拉取词表后预计算并缓存每条name的规范化结果; - 或使用 Aho-Corasick / trie 做多模式匹配(需评估包体积与维护成本)。
- 在
- 重复命中:每次命中会
clearTranscript,相当于消费掉当前整段上下文;若产品需要「同一句连说两次都触发」,要单独设计冷却或分句策略(当前文档不展开)。
7. 仍未解决的一类问题:ASR「听错字」
若识别结果把「牛马」听成「流马」等 语义错误,仅靠字符串 contains 无法纠正。可选演进(需产品 / 后台配合):
- 后台为每条流行语配置 别名 / 常见误识别变体;
- 或引入 拼音 / 编辑距离 的模糊匹配(注意误触风险与性能);
- 或在
requiresOnDeviceRecognition、模型版本、采样链路上调优(属 iOS 侧策略,需真机 A/B)。
8. 自测建议(开发自测清单)
- iOS 真机进房、开麦,说一条词表中连续两字的流行语,观察 Log:
onTextUpdated是否出现带空格的中间结果,并确认仍能命中。 - 在后台将关键词配为 全角英文,口播半角,确认命中。
- 连续快速说同一流行语,确认
clearTranscript后 UI 不闪回旧句(依赖插件侧ignoreNextFinalResult)。
9. 文档与代码变更摘要
- 文档位置:仓库根目录本文件
TRTC_SPEECH_BUZZWORD_DEVELOPER.md。 - 代码变更:
lib/utils/util_popular_phrase.dart— 新增规范化方法并用于matchPopularPhrase;插件与 ViewModel 调用链不变。
若后续要把「别名词表」或「模糊匹配」也产品化,建议在本文件追加一节「数据契约」说明后台 JSON 字段约定,避免客户端与配置平台理解不一致。