尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FreeSWITCH集成ASR:基于mod_unimrcp实现MRCP语音识别IVR系统

FreeSWITCH集成ASR:基于mod_unimrcp实现MRCP语音识别IVR系统 简介面向FreeSWITCH与ASR自动语音识别对接场景的C开源模块定位为可移植、可扩展的参考实现。模块借助Event Socket LibraryESL把语音识别结果实时输出到FreeSWITCH帮助开发者绕过商业模块限制快速理解ASR接入机制代码提交于2017年12月虽较早但整体流程仍具参考价值。压缩包共10个文件、714KB以三个so动态库mod_asr.so、fs1.2_mod_asr.so、librealTimeUnity.so为核心并附带libopus.so.0运行依赖配合mod_asr.cpp及vcxproj工程文件便于二次编译另有README.md、config-realtime.txt说明配置与部署png二维码图片则指向技术交流群。已有920人浏览学习适合正在做FreeSWITCH语音网关、呼叫中心或智能客服集成的中高级C开发者。通过这份资源读者既能获得x64环境可直接加载的模块产物也能从源码层面梳理模块加载、识别事件回调与ESL输出的完整链路还可参照配置文件适配自有识别服务是研究FreeSWITCH集成ASR的不错起点。 做过智能语音呼叫中心的人应该都有同感FreeSWITCH 本身是个非常能打的SIP软交换接电话、路由、放音、录音、开会样样都行但它天生不会“听懂人话”。要让用户拨进来自动说“我查话费”“我要转人工”你就得给FreeSWITCH配上ASR自动语音识别能力。我这个项目就是干这件事的把FreeSWITCH和ASR引擎串起来做成一套能语音导航的IVR系统。如果你正在给FreeSWITCH找ASR方案或者刚把FreeSWITCH 1.10拉下来准备编译这篇实战记录应该能帮你省不少时间。这个项目主要解决三件事一是让FreeSWITCH把通话中的语音流实时送给ASR引擎二是把识别回来的文字变成业务逻辑能用的变量三是处理好放音、打断、超时这些交互细节让用户对着机器说话不像在跟木头人交流。适合接触过FreeSWITCH基础配置、想进一步做智能语音交互的开发者参考。下面我会先讲方案选型再讲编译安装、引擎对比、具体配置最后把我在这个过程中踩过的坑全部列出来。1. 项目定位与整体方案选型1.1 需求拆解FreeSWITCH里的ASR到底在解决什么问题先把这个项目要解决的场景说清楚。用户从手机或者固话拨入系统FreeSWITCH应答后播放一段导航语音比如“请问您需要办理什么业务”然后用户说“我要查余额”。这个流程里FreeSWITCH要做的不是简单录下这段声音而是必须做到实时采集用户说话的那一段音频流而不是等挂机后才处理把音频送给ASR引擎在用户说话的间隙快速拿到识别文本根据识别文本跳转到对应流程比如余额查询业务支持用户说话时打断正在播放的提示音这就是通常说的barge-in如果用户不说话或者识别失败要有超时和重试机制。这些需求叠加起来就决定了不能只靠FreeSWITCH的录音文件加离线转写必须走实时语音识别这条路。我的项目定位很明确做一套可私有化部署、能对接多个ASR引擎的基础语音导航服务而不是绑定某一家云服务商的SDK。还需要考虑的是并发量。呼叫中心不可能只有一个电话同时打进来FreeSWITCH每个话路对应一个Channel每个Channel都要有一路ASR会话。所以方案必须支持多路并发并且要控制好单路识别的资源开销。这也是我在选型时非常看重的一点引擎是否支持大并发、是否需要GPU、模型大小是否可控。1.2 三种主流的ASR接入路线怎么选FreeSWITCH集成ASR业界常见的路线有三种。第一种是走标准MRCP协议用mod_unimrcp对接支持MRCP的识别服务器第二种是用FreeSWITCH 1.10之后官方加入的mod_ai模块直接对接OpenAI等云端AI接口第三种是放弃模块方案用Event Socket LibraryESL在外部程序里控制呼叫并自行调用ASR SDK。MRCP方案是我的首选原因很实际。MRCP是语音资源控制的标准协议国内外主流的ASR服务几乎都提供MRCP接入网关比如阿里云智能语音交互、Nuance以及很多私有化部署的Kaldi、Whisper网关。mod_unimrcp在FreeSWITCH里属于官方模块稳定性有保证而且它对识别过程中的“开始说话”“结束说话”“识别结果”都有完善的异步事件回传机制做多轮交互非常顺手。mod_ai的优势是配置极其简单开玩笑说就是“填个API Key的事”但它绑定云端服务音频要全部上行企业内部对延迟和数据安全要求高的场景不一定接受。ESL方案最灵活等于把FreeSWITCH当成纯粹的呼叫引擎识别逻辑全部自己写但工作量大相当于自己再造一个识别调度层电话多的时候很容易在并发和超时管理上失控。所以我的结论是如果目标是快速上线并且允许云端识别mod_ai或者云厂商MRCP网关都可以如果要长期做私有化、要控制每一路的识别质量老老实实走mod_unimrcp。我这个项目最终选择的是“mod_unimrcp 私有化ASR网关”兼顾灵活性和可控性。2. 环境准备Ubuntu上编译FreeSWITCH 1.102.1 依赖清单与configure关键参数先说编译环境。我用的是Ubuntu 22.04 LTSFreeSWITCH分支选的是v1.10这个版本在Ubuntu上编译流程已经相当成熟。从零开始装依赖包要一次到位sudo apt-get update sudo apt-get install -y build-essential automake autoconf git-core \ libtool pkg-config libcurl4-openssl-dev libjpeg-dev \ libncurses5-dev libssl-dev libpcre3-dev libspeex-dev \ libspeexdsp-dev libldns-dev libedit-dev libopus-dev \ libsndfile1-dev liblua5.2-dev libyaml-dev libpq-dev \ liblzma-dev libjansson-dev这里有个容易忽略的点如果你计划用mod_unimrcp必须先编译安装UniMRCP库因为FreeSWITCH的unimrcp模块是动态链接到这个库上的。UniMRCP源码在GitHub上有官方仓库推荐用当前较新的稳定版本。装完之后把pkg-config路径导出来否则后面FreeSWITCH的configure检测不到./configure --prefix/usr/local make sudo make install export PKG_CONFIG_PATH/usr/local/lib/pkgconfig接下来clone FreeSWITCH源码注意切到标签版本git clone https://github.com/signalwire/freeswitch.git -b v1.10 cd freeswitch ./bootstrap.sh -j这里要专门说说--disable-dependency-tracking这个configure参数。它是GNU autoconf体系的标准选项作用是不精确追踪头文件的依赖关系编译时省掉大量依赖比较和.d文件生成。FreeSWITCH这种体量的项目源码文件几千个开启这个选项后编译过程会明显变快磁盘占用也更小。代价是如果你改了某个公共头文件make可能不会自动重新编译所有受影响源文件。所以它的最佳使用场景就是CI打包、或者像我这样一次性构建稳定的生产版本而不是日常改源码做二次开发。配置命令就是./configure --disable-dependency-tracking加上这个参数后我的编译时间和原来相比大概能省出四分之一左右在只有一台4核构建服务器的条件下很值得。2.2 编译安装与mod_unimrcp启用configure通过之后直接用并行参数编译然后安装make -j$(nproc) sudo make install sudo make cd-sounds-install cd-moh-install这一步会安装默认的提示音和保持音乐没装的话后面放音会静默踩过这个坑的都懂。mod_unimrcp的启用在FreeSWITCH源码里默认是在src/mod/applications/mod_unimrcp目录。确认它已经编译并安装到/usr/local/freeswitch/mod下面即可。如果编译时没有带上它可以单独进目录执行cd src/mod/applications/mod_unimrcp make sudo make install然后编辑/usr/local/freeswitch/conf/autoload_configs/modules.conf.xml确保有这一行load modulemod_unimrcp/我遇到过一种情况模块文件存在但加载时报undefined symbol绝大多数原因就是UniMRCP库版本和mod_unimrcp编译时用的版本对不上。这个在后面排错章节会细说。编译完成后简单验证一下启动FreeSWITCH并进入控制台输入unimrcp能看到模块使用帮助就说明模块加载成功了。3. ASR引擎选型与MRCP接入配置3.1 国内外ASR模型能力横向对比识别引擎是整个链路的灵魂这块我花了不少时间调研。考虑到项目可能既要云端快速验证也要私有化部署我重点对比了四类方案涵盖了国内外主流选择。对比维度OpenAI Whisper讯飞云端识别阿里云智能语音FunASR / WeNet开源私有化中文识别效果良好优秀优秀优秀实时流式识别需自行改造支持支持WeNet原生支持识别延迟较高大模型明显低低低私有化部署简单本地模型不支持混合云可选支持MRCP接入需要MRCP代理云端网关/SDK官方MRCP网关自建MRCP代理主要成本GPU硬件按量付费按量付费硬件与运维实际测下来Whisper的中文识别准确率确实不错尤其对带口音的普通话容忍度比很多老牌引擎高但它的模型体积和推理延迟是个坎。如果想在FreeSWITCH里做到用户刚停嘴就出结果直接上Whisper base size的模型再加上GPU加速也得仔细调分片策略否则一句话说完要等两三秒才有反馈用户体验就比较差。国内云厂商里阿里云智能语音对FreeSWITCH生态最友好它有现成的MRCP Server接入模式mod_unimrcp可以直接把识别服务器指向它的网关省去自己开发网关的麻烦。讯飞识别质量稳定流式接口文档清晰但在MRCP协议支持上需要自己做一层转接。百度、腾讯云的API也很成熟走MRCP同样要写适配网关。如果要私有化我推荐先看WeNet和FunASR。WeNet的流式识别做得非常扎实模型训练和部署资料也多FunASR的Paraformer模型在中文场景的准确率和速度都很能打社区活跃度高。它们和FreeSWITCH对接的思路都是先用Python或者Go写一个MRCP代理服务把MRCP请求翻译成引擎的流式识别接口调用。3.2 unimrcp配置与Dialplan识别流程FreeSWITCH这边的配置集中在UniMRCP模块。打开/usr/local/freeswitch/conf/autoload_configs/unimrcp.conf.xml核心是定义一个识别profile。我项目里的配置长这样profile nameasr-kaldi version2 param nameserver-ip value192.168.10.20/ param nameserver-port value8060/ param nametransport valuertsp/ param namespeechsynth valuespeechsynthesizer/ param namespeechrecog valuespeechrecognizer/ /profile这里的server-ip和server-port就是MRCP网关的地址。transportrtsp对应MRCP v2底层走RTSP控制会话如果网关只支持MRCP v1则用transporttcp。配置好了之后最直接的方式是在拨号计划里调用unimrcp应用。举例分机拨2000时进入测试识别extension nameasr_test condition fielddestination_number expression^2000$ action applicationanswer/ action applicationunimrcp dataasr-kaldi demo-grammar 5000/ /condition /extension这个调用的意思是使用asr-kaldi这个profile加载名为demo-grammar的语法文件识别超时5秒。用户在听到系统提词后开始说话识别结果会连带置信度、回退文字等信息最终设置到呼叫变量里供后续流程读取。要特别提醒的是unimrcp应用本身不会自动“放音提示”。生产环境里更常用的是play_and_detect_speech也就是边放提示音边启动识别用户一说话就能打断放音。语法文件路径要在MRCP引擎侧配置或通过URI引用不同引擎的语法格式也不一样Kaldi这类常用BNF或者ABNF格式云厂商网关往往直接忽略语法、用自由说模式。模块日志默认打在/usr/local/freeswitch/log/freeswitch.log排查问题时可以打开unimrcp的debug级别能看到“Recognizer Started”“Recognition Complete”这类关键事件对定位超时和结果为空非常有用。4. 多轮对话、打断与状态回填实战4.1 play_and_detect_speech做边放边听做IVR语音导航最忌讳的就是“请说话……没听到你说……请再说一遍”这种生硬流程。用户说“转人工”系统还在那里播放“您可以查询话费、余额或者办理业务”听着就着急。所以要启用边放边听这就要用play_and_detect_speech。它的应用语法跟unimrcp类似但多一个提示音参数action applicationplay_and_detect_speech datasay:prompt/ask.wav asr-kaldi demo-grammar 8000/上面这段的意思是先播放ask.wav提示音同时启动ASR识别识别超时8秒。用户一旦开始说话语音能量超过VAD阈值播放立即停止通话进入识别阶段。这个“说话打断放音”的能力对用户体验提升非常明显。还有一个细节对于从手机App或者WebRTC端呼入的用户他们对着屏幕习惯先点一个“按住说话”的按钮再开口这其实跟传统话机的DTMF打断是两种交互模型。但底层ASR链路完全一样区别只在于触发方式。我们项目在App端把触摸按钮监听了音量按住期间才把音频流标记为VAD活动松开就强制结束识别。这个思路可以给做跨端语音交互的朋友参考。4.2 ASR结果如何回填到业务逻辑识别结果回来之后怎么让业务用起来FreeSWITCH处理这种事情有天然优势识别出的文字会写入Channel变量然后走条件判断。我用的是这样的流程extension nameasr_business condition fielddestination_number expression^3000$ action applicationanswer/ action applicationplay_and_detect_speech datasay:prompt/ask.wav asr-kaldi demo-grammar 8000/ action applicationlog dataINFO ASR result: ${detect_speech_result}/ action applicationif data${detect_speech_result} ~ /.*充值.*/ then transfer 4000 XML default/ action applicationif data${detect_speech_result} ~ /.*余额.*/ then transfer 5000 XML default/ action applicationplay_and_detect_speech datasay:prompt/again.wav asr-kaldi demo-grammar 8000/ /condition /extension核心变量是detect_speech_result它保存了最近一次识别的文字。用正则匹配关键词就能把用户意图分流到具体业务模块。如果两次都识别不出来我习惯在第二次失败后直接转人工队列而不是继续无限重试免得用户被机器逼疯。如果对接的是外部业务系统比如CRM或者工单系统更优雅的做法是通过mod_curl把识别文本POST到业务接口由业务侧做NLU理解和流程编排。FreeSWITCH只负责语音交互和会话管理把AI层面的判断交出去这样架构更清晰。5. 常见问题与排错实录5.1 编译与加载阶段的坑先说最折磨人的模块加载问题。mod_unimrcp.so文件存在但FreeSWITCH启动时报undefined symbol: mrcp_application_create之类的错误十有八九是版本不匹配。UniMRCP库更新很频繁mod_unimrcp和libunimrcp必须对应同一个主要版本。我重装了UniMRCP并重新编译mod_unimrcp之后问题彻底消失。还有一个非常隐蔽的坑在Ubuntu 22.04上如果用了系统自带的libssl编译时可能会报openssl/evp.h: No such file or directory。这是因为新版系统的openssl头文件路径变化了加装libssl-dev能解决但如果你之前手动编译过openssl到自定义目录configure会优先找老路径建议干脆把自定义openssl的路径从环境变量里清掉用系统版本最省心。configure阶段遇到checking for ... no导致自动跳过某些模块多数是因为缺少开发包。比如缺libopus-dev则mod_opus不会编译。排查技巧是用./configure --disable-dependency-tracking | grep ***看输出里的警告哪一行标明module skipped就去补哪个依赖。5.2 识别效果与延迟优化识别跑通了才是真正调优的开始。我遇到过识别结果总是为空的情况用debug日志一看识别引擎根本没接收到有效音频。最后定位是编码不匹配通话走的是G.729编码而ASR引擎那边默认只认G.711或L16。FreeSWITCH中间做了转码但是网关侧解码端没把编码参数对齐。解决办法是在MRCP网关侧把编码列表加上G.729或者干脆让通话统一走G.711识别链路会简单很多。VAD阈值也是个关键参数。阈值设太高用户小声说话直接被忽略设太低环境噪声又会造成大量错误触发。我一般会先用一段真实的坐席录音去标定观察识别引擎日志里的speech-detected事件和实际说话时间的差距逐步调节。还有一个实际经验电话线路上音量偏低的情况很常见如果发现识别率总是低优先检查输入增益而不是急着换模型。延迟优化的核心是音频要流式上送不要等整句话说完再交给引擎。MRCP本身支持分帧发送FreeSWITCH和网关之间只要网络正常识别结果基本能在用户停嘴后0.5秒内回来。如果延迟还是高看一眼是不是用了较大的Whisper模型或者MRCP server在排队处理。我会把识别引擎单独部署到和FreeSWITCH同机房确保RTT在1毫秒内这是最便宜的优化手段。最后分享一个我在这个项目里养成的小习惯每次调整完配置先用sip软电话拨一个测试分机录一句固定话术把抓包日志和FreeSWITCH debug日志存下来反复对比。语音识别这种链路光看配置永远不知道实际效果只有一遍遍跑真实通话才能把每一环都调到满意状态。这套方案现在稳定跑了几百路并发用户真正对着电话说一句“我要查询”系统在半秒内就回声“好的正在为您查询”那种感觉还是很有成就感的。本文还有配套的精品资源点击获取
返回列表