Skip to content

新增 ASR 厂商开发指南

这篇文档面向需要把新 ASR 厂商接入 EasyMrcp 的开发者。

新增 ASR 厂商时,通常不需要改 SIP、RTP、VAD 主流程。正确做法是新增一个厂商处理器,让它继承 AsrHandler,再通过 ProcessorCreator 挂到 mrcp.asrMode 上。

接入思路

可以把新增 ASR 厂商理解成“给 EasyMrcp 加一个新的语音识别适配器”。

EasyMrcp 已经负责了电话侧的事情:接 SIP、协商 RTP、接收音频、解码、按需做 VAD 和重采样。新厂商代码只需要站在 EasyMrcp 和厂商接口中间,把 EasyMrcp 交过来的 PCM 音频送给厂商,再把厂商返回的识别文本交回 EasyMrcp。

实际开发时按这个顺序理解最简单:

  1. 先选识别方式:一句话识别用 dictation,长连接实时转写用 transliterate
  2. 再写配置:把厂商地址、密钥、模型、采样率等放进 asr/*.properties
  3. 再写处理器:继承 AsrHandler,实现连接厂商、发送 PCM、结束一句话、关闭资源。
  4. 最后注册入口:在 EMConstantProcessorCreator 里加新模式,让 mrcp.asrMode 能选到它。

整个过程的核心不是重写 EasyMrcp,而是把一家厂商的协议“翻译”成 EasyMrcp 已经认识的 RecognitionCompleteAsrRealTimeResult 和打断事件。

1. 推荐接入路径

第一次接新厂商时,建议先参考讯飞实现:

text
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

推荐目录结构:

text
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 绑定配置文件。

示例结构:

java
@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;
}

配置文件示例:

properties
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=upsample8kTo16k

identify-patternsre-sample 来自 AsrConfig,建议保留。

部署时不要只依赖 src/main/resources/asr/ 里的模板。运行目录也应提供外部配置文件,例如:

text
asr/your-asr.properties

5. 实现处理器

厂商处理器继承 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 模式:

java
public static final String YOUR_ASR = "your-asr";

7.2 注入配置类

ProcessorCreator 里注入你的配置类:

java
@Resource
YourAsrConfig yourAsrConfig;

7.3 增加创建分支

createAsrHandler() 里根据 asrModeidentifyPatterns 返回处理器:

java
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-patternsre-sample 写回 AsrHandler 基类。

8. 切换配置并联调

application.yaml 中切换默认 ASR:

yaml
mrcp:
  asrMode: your-asr

联调时建议使用:

text
examples/java-enhanced-client
examples/scripts

至少验证下面几项:

  1. create() 能成功连接厂商。
  2. 厂商连接成功后能释放 countDownLatch,避免 EasyMrcp 等待超时。
  3. receive(byte[] pcmData) 能持续收到 PCM。
  4. dictation 模式下句尾会调用 sendEof()
  5. 最终结果能触发 RecognitionComplete
  6. 开启实时推送时,能收到 AsrRealTimeResult
  7. 通话关闭时 asrClose() 会释放连接和线程资源。

9. 常见问题

现象排查方向
启动后找不到厂商配置检查 @PropertySource、配置文件名和运行目录 asr/*.properties
一直等待 ASR 连接检查厂商连接成功回调里是否执行了 countDownLatch.countDown()
没有最终识别结果检查厂商最终结果是否回调 ASRConstant.Result
dictation 不结束检查 VAD、SpeechCompleteTimeoutsendEof()
音频识别异常检查厂商采样率要求和 re-sample 配置。
资源泄漏检查 asrClose() 是否关闭 WebSocket、线程池和 SDK 客户端。