新增 ASR 厂商开发指南
这篇文档面向需要把新 ASR 厂商接入 EasyMrcp 的开发者。
新增 ASR 厂商时,通常不需要改 SIP、RTP、VAD 主流程。正确做法是新增一个厂商处理器,让它继承 AsrHandler,再通过 ProcessorCreator 挂到 mrcp.asrMode 上。
接入思路
可以把新增 ASR 厂商理解成“给 EasyMrcp 加一个新的语音识别适配器”。
EasyMrcp 已经负责了电话侧的事情:接 SIP、协商 RTP、接收音频、解码、按需做 VAD 和重采样。新厂商代码只需要站在 EasyMrcp 和厂商接口中间,把 EasyMrcp 交过来的 PCM 音频送给厂商,再把厂商返回的识别文本交回 EasyMrcp。
实际开发时按这个顺序理解最简单:
- 先选识别方式:一句话识别用
dictation,长连接实时转写用transliterate。 - 再写配置:把厂商地址、密钥、模型、采样率等放进
asr/*.properties。 - 再写处理器:继承
AsrHandler,实现连接厂商、发送 PCM、结束一句话、关闭资源。 - 最后注册入口:在
EMConstant和ProcessorCreator里加新模式,让mrcp.asrMode能选到它。
整个过程的核心不是重写 EasyMrcp,而是把一家厂商的协议“翻译”成 EasyMrcp 已经认识的 RecognitionComplete、AsrRealTimeResult 和打断事件。
1. 推荐接入路径
第一次接新厂商时,建议先参考讯飞实现:
src/main/java/com/cfsl/easymrcp/asr/xfyun/
├─ XfyunAsrConfig.java
├─ dictation/
│ ├─ XfyunDictationAsrProcessor.java
│ └─ XfyunDictationWsClient.java
└─ transliterate/
├─ XfyunTransliterateAsrProcessor.java
└─ XfyunTransliterateWsClient.java原因是讯飞实现同时覆盖了:
dictation:一句话识别。transliterate:长连接实时转写。- 厂商配置类。
- 厂商 WebSocket 客户端。
- 中间结果、最终结果、打断、实时推送等回调路径。
2. 需要改哪些文件
新增一家 ASR 厂商,一般涉及这些文件:
| 文件 | 是否必须 | 作用 |
|---|---|---|
pom.xml | 视情况 | 如果厂商有 SDK,需要增加依赖。 |
EMConstant.java | 是 | 增加新的 asrMode 常量。 |
ProcessorCreator.java | 是 | 注册新 ASR 处理器的创建逻辑。 |
asr/<vendor>/... | 是 | 新增厂商配置类、处理器、协议客户端。 |
src/main/resources/asr/<vendor>-asr.properties | 是 | 提供默认配置模板。 |
application.yaml | 可选 | 本地联调时切换默认 mrcp.asrMode。 |
推荐目录结构:
src/main/java/com/cfsl/easymrcp/asr/<vendor>/
├─ <Vendor>AsrConfig.java
├─ dictation/
│ ├─ <Vendor>DictationAsrProcessor.java
│ └─ <Vendor>DictationClient.java
└─ transliterate/
├─ <Vendor>TransliterateAsrProcessor.java
└─ <Vendor>TransliterateClient.java
src/main/resources/asr/
└─ <vendor>-asr.properties如果厂商只支持一种模式,可以只实现对应目录。
3. 选择识别模式
EasyMrcp 里的 ASR 接入分两种模式:
| 模式 | 说明 | 处理特点 |
|---|---|---|
dictation | 一句话识别 | EasyMrcp 本地 VAD 判断开始和结束,句尾时调用 sendEof()。 |
transliterate | 长连接实时转写 | EasyMrcp 持续送 PCM,分句更多依赖厂商服务端。 |
如果厂商接口是“用户说一句,结束后返回最终结果”,选 dictation。
如果厂商接口是“整路音频持续推送,服务端实时返回中间结果或分句结果”,选 transliterate。
4. 新增配置类
配置类继承 AsrConfig,并使用 @ConfigurationProperties 和 @PropertySource 绑定配置文件。
示例结构:
@Data
@Configuration
@ConfigurationProperties(prefix = "your-asr")
@EqualsAndHashCode(callSuper = true)
@PropertySource(
value = {"classpath:asr/your-asr.properties", "file:asr/your-asr.properties"},
ignoreResourceNotFound = true
)
public class YourAsrConfig extends AsrConfig {
private String hostUrl;
private String appId;
private String apiKey;
private String apiSecret;
}配置文件示例:
your-asr.host-url=wss://example.com/asr
your-asr.app-id=<your-app-id>
your-asr.api-key=<your-api-key>
your-asr.api-secret=<your-api-secret>
your-asr.identify-patterns=dictation
# 如果电话 RTP 是 8k,而厂商要求 16k PCM,打开重采样
# your-asr.re-sample=upsample8kTo16kidentify-patterns 和 re-sample 来自 AsrConfig,建议保留。
部署时不要只依赖 src/main/resources/asr/ 里的模板。运行目录也应提供外部配置文件,例如:
asr/your-asr.properties5. 实现处理器
厂商处理器继承 AsrHandler,需要实现 4 个方法:
| 方法 | 作用 |
|---|---|
create() | 建立厂商连接或初始化 SDK。 |
receive(byte[] pcmData) | 接收 EasyMrcp 解码后的 PCM,并转发给厂商。 |
sendEof() | 句子结束时通知厂商收尾,dictation 通常必须实现。 |
asrClose() | 通话结束时释放连接、线程池、SDK 客户端等资源。 |
处理器只负责对接厂商,不要重复做这些事:
- SIP/SDP 解析。
- RTP 解包。
- G.711 解码。
- VAD 检测。
- 采样率重采样。
这些已经由 EasyMrcp 主流程完成,传入 receive(byte[] pcmData) 的就是 PCM 数据。
6. 回调结果
厂商客户端拿到结果后,需要通过 AsrCallback 回到 EasyMrcp。
常用回调动作:
| 动作 | 说明 |
|---|---|
ASRConstant.Result | 最终识别结果,会转换成 RecognitionComplete。 |
ASRConstant.Interrupt | 用户说话打断当前 TTS。 |
| 实时结果推送 | 如果开启 PushAsrRealtimeResult,厂商中间结果可推送为 AsrRealTimeResult。 |
接入时至少要保证最终结果能触发 RecognitionComplete,否则业务侧无法拿到识别文本。
7. 注册到 EasyMrcp
7.1 增加模式常量
在 EMConstant.java 增加新的 ASR 模式:
public static final String YOUR_ASR = "your-asr";7.2 注入配置类
在 ProcessorCreator 里注入你的配置类:
@Resource
YourAsrConfig yourAsrConfig;7.3 增加创建分支
在 createAsrHandler() 里根据 asrMode 和 identifyPatterns 返回处理器:
case EMConstant.YOUR_ASR:
if (ASRConstant.IDENTIFY_PATTERNS_DICTATION.equals(yourAsrConfig.getIdentifyPatterns())) {
YourDictationAsrProcessor processor = new YourDictationAsrProcessor(yourAsrConfig);
processor.setConfig(yourAsrConfig);
return processor;
} else if (ASRConstant.IDENTIFY_PATTERNS_TRANSLITERATE.equals(yourAsrConfig.getIdentifyPatterns())) {
YourTransliterateAsrProcessor processor = new YourTransliterateAsrProcessor(yourAsrConfig);
processor.setConfig(yourAsrConfig);
return processor;
}
break;processor.setConfig(yourAsrConfig) 很重要,它会把 identify-patterns 和 re-sample 写回 AsrHandler 基类。
8. 切换配置并联调
在 application.yaml 中切换默认 ASR:
mrcp:
asrMode: your-asr联调时建议使用:
examples/java-enhanced-client
examples/scripts至少验证下面几项:
create()能成功连接厂商。- 厂商连接成功后能释放
countDownLatch,避免 EasyMrcp 等待超时。 receive(byte[] pcmData)能持续收到 PCM。dictation模式下句尾会调用sendEof()。- 最终结果能触发
RecognitionComplete。 - 开启实时推送时,能收到
AsrRealTimeResult。 - 通话关闭时
asrClose()会释放连接和线程资源。
9. 常见问题
| 现象 | 排查方向 |
|---|---|
| 启动后找不到厂商配置 | 检查 @PropertySource、配置文件名和运行目录 asr/*.properties。 |
| 一直等待 ASR 连接 | 检查厂商连接成功回调里是否执行了 countDownLatch.countDown()。 |
| 没有最终识别结果 | 检查厂商最终结果是否回调 ASRConstant.Result。 |
dictation 不结束 | 检查 VAD、SpeechCompleteTimeout 和 sendEof()。 |
| 音频识别异常 | 检查厂商采样率要求和 re-sample 配置。 |
| 资源泄漏 | 检查 asrClose() 是否关闭 WebSocket、线程池和 SDK 客户端。 |
