
有没有遇到过这种情况领导丢过来一句话“三天内搞一个语音机器人出来用户打电话进来能正常对话能查订单、能办理挂失。”你的第一反应是去啃ASR识别、NLP对话、TTS合成这一整套AI链路但实际上你手头早就有资源了一个开源的FreeSWITCH一个阿里云SDMMRCP-SERVER接口。把这两者接起来语音机器人的骨架当天就能跑通剩下的事情只是把对话逻辑往里面填而已。本文就是记录这条路怎么走通。我会从FreeSWITCH和MRCP-SERVER各自的分工讲起然后重点拆解Docker化部署FreeSWITCH的细节再一步步把阿里云SDM接入配置、拨号计划、P-Early-Media支持这些关键节点全部过一遍。最后是我自己在容器环境下踩过的四个真实坑排查思路和解决办法一并奉上。不管你是刚接触FreeSWITCH的新手还是已经在生产环境维护过一阵子的老手这篇文章的实操密度应该都能帮上忙。1. 语音机器人的电话接入层FreeSWITCH与MRCP-SERVER各自扮演什么角色1.1 从“总机”和“坐席大脑”理解FreeSWITCHMRCP的组合很多人第一次接触语音机器人容易被一堆协议缩写搞晕SIP、RTP、MRCP、ASR、TTS、NLP……但剥开来看整个系统其实只有两类角色。第一类是电话接入层。用户从手机或座机拨进来的电话本质是一路SIP呼叫媒体流是RTP音频。这个层面要解决的是呼叫怎么进来、音频怎么传输、怎么播放录音、怎么转人工、怎么挂断。这就是FreeSWITCH干的活。它像一个总机接线员把所有电话线揽在自己手里。第二类是AI能力层。用户说了一句“我想查余额”这句话要被识别成文字ASR然后理解意图NLP再把“您的余额是五百元”这句话合成音频播出去TTS。这个层面就是MRCP-SERVER提供的。它像一个坐席大脑负责听懂和回答。MRCPMedia Resource Control Protocol就是连接这两层的协议。FreeSWITCH通过mod_unimrcp模块把RTP音频流送到MRCP-SERVERMRCP-SERVER处理完再把结果或合成音频送回来。整个对话过程中FreeSWITCH不关心AI模型怎么跑它只需要按照MRCP协议把音频送过去、把结果拿回来。用生活类比就是你打电话给银行总机把你的话音接给后台坐席坐席帮你查业务再把答案传回给总机由总机播给你听。FreeSWITCH是总机MRCP-SERVER是坐席MRCP协议就是总机和坐席之间的内部电话线路。1.2 阿里云SDM(MRCP-SERVER)到底提供了什么能力阿里云SDM全称很长但从使用角度只看一件事它给你开好了一个现成的MRCP服务器你只需要知道IP、端口、鉴权信息就能用MRCP协议调用ASR和TTS能力。如果不用这种云端MRCP服务你会发现面前摆着两条非常折腾的路。一条是自己部署开源ASR/TTS引擎。Kaldi、Mozilla DeepSpeech、PaddleSpeech、espeak这些我都碰过光是声学模型、语言模型、发音字典、音频特征提取这些概念就够喝一壶的。更要命的是打电话进来的音频是8kHz采样率、G.711编码的窄带语音很多模型在窄带数据集上的表现会明显下滑识别率惨不忍睹。另一条是直接调用云厂商的API。好处是识别率高问题是API通常是HTTP接口不是MRCP协议。你需要自己写桥接服务一边接FreeSWITCH的媒体一边调云API还要处理异步回调、音频格式转换、并发会话管理。相当于产品还没上线先造了一个中间件后续还得不停维护。阿里云SDM把这条路径缩短了它对外开放MRCP协议接口FreeSWITCH端只要配置好unimrcp就能直接连过去ASR、TTS能力相当于“内嵌”进了通话链路不需要你自己写一行桥接代码。1.3 动手前需要准备的清单在开始之前如果你不想中途卡壳先花几分钟把下面这些准备好一台能运行Docker的Linux服务器内存建议不低于2GB4GB更稳。如果是个人测试Windows 10/11 Docker Desktop也可以但后面会有专门的坑要处理。阿里云账号并开通智能语音交互相关服务进入控制台找到MRCP-SERVER接入信息包括接入地址、端口、账号、密钥以及分配给语音机器人的话术模板/音色等其他配置。不同地域、不同产品版本控制台界面可能不太一样以你实际看到的为准。版本信息Docker镜像我建议直接用社区维护的signalwire/freeswitch或者safarov/freeswitch镜像不要自己从头编译。自己编译FreeSWITCH三件套FreeSWITCH本体、mod_unimrcp、依赖库的时长足够出去吃顿饭了。一个SIP软电话比如MicroSIP、Zoiper、Linphone用于实际呼叫测试。没有软电话用手机装了SIP客户端也行目的就是真实地“拨一通电话”来验证链路。一个支持SIP标准的命令行工具比如sngrep、tcpdump这些在排错阶段非常有用。准备工作的核心逻辑很简单提前把可变因素收敛后面出问题时才有清晰的排查边界。实际配置MRCP接入时最省心的做法是先在阿里云控制台用官方工具跑通一句话识别确认账号权限和网络连通性没问题再往下走。否则后面FreeSWITCH连不上的时候你都不知道该查网络还是查鉴权。2. Docker化部署FreeSWITCH从镜像拉取到一次启动成功2.1 为什么我强烈建议用Docker跑FreeSWITCH我最早一次部署FreeSWITCH是老老实实按官方文档编译安装的。下载源码、解决依赖冲突、编译mod_unimrcp、配置mod_sofia再把RTP端口范围放通一套下来折腾了大半天。后来同一台机器上又因为库版本更新导致某个模块加载崩溃修复成本比重新部署还高。Docker版本把这堆问题全隔离掉了。镜像是别人已经编译好的、验证过的运行环境拉下来直接跑。升级和回退都简单换一个镜像tag再启动就是了。更重要的是临时测试环境可以随时销毁重建完全不污染宿主机。如果你正在开发一个语音机器人的POC概念验证用Docker部署FreeSWITCH能让你把有限的精力集中在语音链路上而不是耗费在环境搭建上。这也是标题里“5分钟搞定”能成立的根本原因。2.2 端口、目录、网络容器启动命令逐项拆解先给一份完整的Docker运行命令作为基准docker run -d \ --name freeswitch \ --network host \ --restart always \ -v /data/freeswitch/conf:/etc/freeswitch \ -v /data/freeswitch/recordings:/var/lib/freeswitch/recordings \ -v /data/freeswitch/log:/var/log/freeswitch \ -v /etc/localtime:/etc/localtime:ro \ safarov/freeswitch:latest逐个拆解这条命令里的关键点你会发现每个选择背后都是有原因的。网络模式用host而不是bridge映射端口。这是FreeSWITCH容器化里最容易被忽略的决策。FreeSWITCH的RTP媒体端口是一个很大的动态范围默认16384-32768如果你用bridge模式就要在docker run里手动映射几百个端口-p 16384-32768:16384-32768/udp既笨拙又容易撞上Docker的端口限制。改成host模式后容器直接复用宿主机网络SIP的5060端口和RTP的UDP范围天然就通完全不需要端口映射的噩梦。这也是我踩过一次错之后后续所有FreeSWITCH容器都默认host模式的原因。为什么挂载conf目录。FreeSWITCH的配置目录包含全部xml配置拨号计划、SIP profile、unimrcp配置、vars定义。不挂载的话容器一删配置就全没了。挂载之后你可以直接在宿主机上改配置再进容器执行fs_cli -x reloadxml热加载非常方便。为什么挂载recordings和log。一个是保存通话录音一个方便排查问题。这两个目录不挂载虽然在容器刚启动时不会报错但你一旦开始调IVR流程需要翻日志、听录音时就会后悔没挂载。挂载后宿主机的日志和录音就是持久化的排查效率高一个量级。为什么挂载localtime。容器默认时区是UTC日志时间比北京时间慢8小时。你半夜排查通话问题时日志时间对不上会很痛苦。挂载宿主机的localtime文件后容器内时间戳与本地一致。这个操作只需要一行但能省掉无数脑细胞。2.3 启动后必须做的三个健康检查容器起来后别急着配MRCP先确认FreeSWITCH本身是健康的。第一个检查模块加载情况。进入容器执行docker exec -it freeswitch fs_cli -x module_exists mod_unimrcp如果返回true说明unimrcp模块已经在镜像中编译并加载。注意不是所有的FreeSWITCH镜像都默认带mod_unimrcp如果你用的镜像没有这个模块要么换一个镜像要么自己补装。这比什么都先确认。第二个检查SIP端口是否正常监听docker exec -it freeswitch fs_cli -x sofia status看到internal-ipv4和external-ipv4都是RUNNING状态说明SIP协议栈起来了。第三个检查用软电话真实拨打一个FreeSWITCH自带demo。镜像里通常默认有拨号计划软电话注册后拨打某个测试号码能听到欢迎语音说明媒体链路没问题。这一步做完证明“电话能进得来、声音能出得去”后面接MRCP-SERVER时才不会混淆问题范围。2.4 Windows装Docker时最常见的Virtualization报错很多朋友是在Windows上开始玩FreeSWITCH的。Docker Desktop装完后点启动直接报错Docker Desktop failed to start because virtualization support wasnt detected。这个问题几乎每天都在各种技术群里被问。这个报错的根因就一句话Windows上跑Linux容器依赖WSL2或Hyper-V而WSL2需要CPU虚拟化支持并且BIOS里必须打开。处理路径按顺序排查重启进BIOS/UEFI找到Intel VT-x或AMD-V/SVM选项设置为Enabled。在Windows功能里勾选“适用于Linux的Windows子系统”和“虚拟机平台”重启电脑。在PowerShell管理员里执行wsl --set-default-version 2确保WSL版本是2而不是1。最后重新启动Docker Desktop。在BIOS里打开虚拟化这个步骤是很多人在软件层面折腾半天没有结果的根源。我第一次遇到时把Docker Desktop重装了三遍最后发现是BIOS里的VT-x没开那一刻的心情想必你能体会。Windows下跑FreeSWITCH容器音频链路有个天然短板如果软电话也运行在同一台Windows上SIP和RTP流量在容器和本机之间穿梭偶尔会出现音频单向或时断时续。我的建议是Windows上只用来做开发和调试验证真正常态测试和生产环境还是放Linux服务器。3. 接入阿里云MRCP-SERVERunimrcp配置改这几个地方就够了3.1 确认mod_unimrcp已经就位FreeSWITCH里和MRCP相关的模块是mod_unimrcp它依赖libunimrcp库。模块加载后系统里会出现unimrcp这个拨号计划应用以及unimrcp:synthTTS合成和unimrcp:recog语音识别这两个核心接口。如果前面健康检查时已经确认module_exists返回true那这一步就跳过了。这里要提醒一点镜像默认的conf配置里unimrcp默认连接的是本地MRCP服务器通常127.0.0.1这个默认值不会自动适配阿里云所以下面手动改配置文件是逃不掉的。3.2 unimrcp.conf.xml核心字段的“翻译”与填法在宿主机上打开/data/freeswitch/conf/autoload_configs/unimrcp.conf.xml核心结构是unimrcp根节点下挂着若干个profile每个profile就是一个MRCP服务器连接配置。我通常建议单独为阿里云建一个新的profile比如叫aliyun_sdm不要动默认的base方便以后切回本地测试。一个基本可用的profile配置长这样以实际控制台地址和端口为准profile namealiyun_sdm version2 param nameserver-ip valueyour-mrcp-server-ip/ param nameserver-port valueyour-mrcp-server-port/ param nameserver-transport valuetcp/ param nameauth-name valueyour-access-key-id/ param nameauth-password valueyour-access-key-secret/ param namesip-ip valueyour-freeswitch-external-ip/ param namesip-port value5090/ param namertp-ip valueyour-freeswitch-external-ip/ param namertp-port-min value40000/ param namertp-port-max value40003/ param namecodec valuePCMU PCMA/ param namespeech-synth-engine valuealiyun_tts/ param namespeech-recog-engine valuealiyun_asr/ /profile重点解释几个看起来不起眼但影响很大的参数。version2MRCP协议有v1和v2两个版本阿里云SDM通常使用MRCPv2基于SIP所以这里必须填2。填错了最典型的症状是连接建立不上或者交互行为异常。auth-name和auth-password云服务的鉴权凭据。有些人会漏掉这两个参数结果FreeSWITCH能连上IP和端口但每次请求都被拒绝。MRCP的鉴权过程类似HTTP Basic Auth但它在SIP level进行。拿不到正确凭据后面永远是一连串的401/403。rtp-port-min/max因为前面Docker用的是host网络这里RTP端口范围只需要在宿主机允许的范围内即可。我把范围缩小到4个端口方便安全组和防火墙规则收敛。speech-synth-engine和speech-recog-engine这两个参数名可能在不同镜像里略有差异有些版本里是通过resource-map的方式映射的。如果你看到配置里已经有synth和recog的resource映射按要求修改里面的server即可。总之目的就是让FreeSWITCH知道“TTS和ASR都走这个阿里云profile”。3.3 针对阿里云的编码与资源类型配置通话中FreeSWITCH和MRCP-SERVER之间的RTP音频编码直接影响识别率和TTS音质。电话网络里最基础的是PCMUG.711 μ-law和PCMAG.711 A-law这两种编码是RTP世界的通用语言兼容性最好。阿里云MRCP-SERVER对这两种编码的支持也最稳定。但要注意一个细节窄带音频8kHz与宽带音频16kHz的识别体验差异。8kHz是传统电话的采样率带宽有限ASR模型在此条件下的表现天然受限。如果你的场景是纯移动网络通话那就只能用8kHz如果是VoIP终端之间通话可能支持G.722等宽带编码。在unimrcp配置里codec参数如果填了多种编码需要在双方协商一致时才生效。我的经验是初期调试阶段就固定用PCMU排除编码不协商导致的哑音问题等链路稳定后再尝试换成宽带编码来对比识别率。另外unimrcp配置里通常还有tasking-timeout、max-timeout这类的超时参数。这些值的设置取决于你对用户语音输入时长的预期。比如对于“请说出你要办理的业务”这类开放式引导识别等待时间过短会导致用户还没说完就超时但如果你的业务场景是“请输入密码”密码通常只有几位等待太久又会拖慢流程。我建议初值设置为5-8秒然后根据实际用户行为统计来调整不要拍脑袋设一个10秒的固定值。3.4 控制台验证MRCP连接是否打通配置改完后在宿主机上先重启容器或者用fs_cli热加载docker exec -it freeswitch fs_cli reloadxml unload mod_unimrcp load mod_unimrcp加载完成后观察控制台日志看看有没有报错。更直接的办法是先在拨号计划里模拟一次调用。在FreeSWITCH控制台执行 originate {unimrcp_profilealiyun_sdm}loopback/1234 unimrcp(recog)如果配置正确你会看到MRCP会话建立成功的日志FreeSWITCH会发起SIP INVITE到阿里云MRCP-SERVER然后媒体协商完成。如果配置有误控制台会打印具体的SIP错误码或MRCP错误信息比如401鉴权失败、488编码不支持等。这一步验证完成后语音机器人的“底座”就算通了。接下来就是设计拨号计划把对话流程串起来。4. 一分钟跑通语音机器人拨号计划P-Early-Media是关键4.1 基础拨号计划让机器人先说话再听你说话FreeSWITCH的拨号计划dialplan是用XML定义的。每个呼叫进来后FreeSWITCH根据被叫号码匹配对应的extension然后执行一系列应用。语音机器人的核心流程就是放招呼语-TTS、开始识别-ASR、根据识别结果走分支这个循环在FreeSWITCH里可以用unimrcp应用来实现。一个极其简单的demo拨号计划看起来像这样extension namevoicebot condition fielddestination_number expression^9000$ action applicationanswer/ action applicationunimrcp datasynth aliyun_sdm 欢迎致电测试中心请说出您要办理的业务/ action applicationunimrcp datarecog aliyun_sdm param1session_timeout:10s/ action applicationlog dataERR 识别结果: ${unimrcp_result}/ /condition /extension看起来很简单对不对这里其实就是FreeSWITCH的答案answer先接通电话然后调用MRCP把TTS合成的招呼语播放给用户听接着进入ASR识别状态把用户的语音转成文本存进unimrcp_result变量。后面就可以根据这个变量用条件判断去匹配“查余额”“办挂失”等意图路由到不同业务节点。一个最简单的语音机器人对话循环就是这个样子。4.2 p-early-media-support参数解决“答非所问”的底层机制在实际调优过程中有一个参数对语音机器人的体验影响极大那就是p-early-media-support。先说现象。最初我按上面的demo搭完发现用户打电话进来后TTS播放“欢迎致电”的时候用户说话是听不到的必须等TTS播完、通道完全answer之后ASR才开始采集声音。这在真实业务里很要命很多用户听到一半就抢话结果抢的那句话被系统丢弃机器人只识别到后半句甚至没识别到用户又得重复一遍。根本原因在于默认情况下FreeSWITCH在呼叫真正接通200 OK ACK之后媒体路径才完全建立。而MRCP识别如果等到answer之后才开始用户提前说的话自然就丢了。p-early-media-support就是为了解决这个时间差。这个参数需要在SIP profile里开启它允许FreeSWITCH在early media阶段也就是用户还没接通、还在听回铃音的阶段就建立双向媒体路径。MRCP里的某些识别引擎也支持early media模式一旦开启用户说第一句话时ASR就能收到音频流不用等机器人把导播语全部播完。在FreeSWITCH里启用这个参数的位置和含义因版本而异。一种典型配置是在conf/sip_profiles/internal.xml的profile节点里加param namep-early-media-support valuetrue/同时在vars.xml里设置X-PRE-PROCESS cmdset dataoutbound_early_mediatrue/开启之后FreeSWITCH会尽早向主叫方发送183 Session Progress告知媒体已经准备好了。但注意仅仅开启SIP profile这个参数还不够。实际工程里还涉及媒体缓冲media bug、dyntrack等机制的配合。最简单有效的姿势是在拨号计划中先执行ring_ready然后调用MRCP识别或播放再在合适的时机才执行answer。这样电话一进来媒体流是活的用户随时可以说话。我遇到过不少同行的坑是参数改了重启了SIP profile但拨号计划仍然是先answer再做MRCP导致early media的设置形同虚设。所以要记住核心逻辑要让用户在电话还未正式接通前就能与MRCP交互而不是等接通后再放语音。4.3 状态机与超时设计把识别逻辑变稳定拨号计划里写死一段单向的TTSASR并不难难的是把对话流程做成状态机让机器人不会“一问三不知”或者陷入死循环。这里我分享一个比较实用的三层状态设计第一层是引导与超时。每次进入ASR识别前先定义一个全局或session级的超时变量比如session_timeout8000也就是8秒内没有说话就触发超时。超时后播放“对不起我没有听到您的声音”然后重新进入识别节点或转人工。这里要注意超时时间不宜全局统一。对于开放式问题可以给8-10秒对于确认类问题可以给3-5秒。第二层是拒识处理。ASR识别完成但置信度低或识别结果为空时不能直接走错误分支更不能不理会。标准做法是重试次数计数同一轮对话最多重试2-3次超过就转人工或者自动挂断。否则用户对着机器人喊三遍“你好”机器人还搁那重复同一句导播语体验非常差。第三层是会话上下文。FreeSWITCH的session变量天然支持在同一个通话内保存状态。比如用户先说“我要查余额”你把它存进intent变量然后追问“请说出账号后四位”用户说话再识别识别结果存进auth_code变量。后面的分支逻辑就能同时使用这两个变量。虽然FreeSWITCH不是专业的NLU平台但做固定流程的语音导航和事务办理这套方案完全够用。状态机的意义在于把翻车率控制在一个可接受的范围。语音机器人上线初期识别率不可能是100%但只要你把超时、拒识、转人工的路径设计好用户感受到的“智能程度”会提升一大截。这其实比单纯追求更高识别率更划算。5. Docker容器下最常踩的四个坑我的排错实录5.1 双方听不到媒体流RTP端口和网络模式有一段时间我在测试服务器上部署宿主机安全组只放行了TCP 5060端口结果软电话注册成功但一通话就断或者听到了对方的“幽灵音”却一句话都传不过去。后来一查FreeSWITCH的RTP媒体流走的是UDP端口而安全组只开了UDP 5060RTP动态端口范围全部被防火墙拦截了。这就是为什么前面我说Docker要使用host网络模式但在host网络模式下宿主机防火墙/SG安全组的配置仍然决定一切。排查媒体流问题最快的方式是用抓包sngrep或tcpdump看SIP信令里SDP协商的IP和端口tcpdump -i any udp portrange 16384-32768 -nn -vvv如果看到RTP包在发送但对方收不到基本可以断定是中间防火墙丢包。如果RTP包根本没从本机发出那大概率是FreeSWITCH的external-ip设置问题或者路由配置把媒体引到了错误网卡。在Docker host网络下建议在vars.xml里显式设置X-PRE-PROCESS cmdset dataexternal_rtp_ipyour_server_public_ip/ X-PRE-PROCESS cmdset dataexternal_sip_ipyour_server_public_ip/很多语音机器人在本地测试正常一到云服务器上就不通原因就在这两个外部IP没有设置成云服务器的公网IP或正确的内网IP。5.2 MRCP鉴权失败/连接被拒常见的三种原因阿里云MRCP-SERVER接入失败日志里最常见的几种情况我都遇到过原因和解决思路各不相同。第一种是401 Unauthorized。这说明网络是通的但鉴权凭据不对。别急着怀疑AccessKey被禁用先检查unimrcp.conf.xml里的auth-name和auth-password是否配置在了正确的profile下。我就干过一次把两个profile搞混结果怎么改都报401最后发现还是默认profile生效。第二种是timeout。这说明TCP连接可能被中间网络拦截或者阿里云侧的控制台里没有放通你的来源IP白名单。典型案例是MRCP-SERVER控制台里默认只允许配置的几个公网IP访问你换了一台服务器部署就忘了加白名单直接timeout。解决方法是登录阿里云控制台检查并添加来源IP白名单。第三种是488 Not Acceptable Here。这是SIP协商不匹配通常是编码格式或MRCP版本不对。前面提到的codec配置里如果填了不支持的编码或者MRCP version写成了1就会出现这个错。排查这类问题时你可以在FreeSWITCH控制台开启更详细的模块日志来定位 console log level debug5.3 识别总是静音超时P-Early-Media没生效的排查路径有一段时期我的语音机器人已经可以接通并识别了但有个诡异现象用户必须在听到一段“嘟”声后才说话否则说早了识别不到。原因就是前面讲的early media配置没有真正生效。排查路径要从头梳理第一步确认SIP profile中确实设置了p-early-media-supporttrue并且你已经重启了sofia profile注意是重启profile不是只reloadxml。这个参数如果改在internal.xml里正确执行是 sofia profile internal restart第二步用软电话发起呼叫后观察FreeSWITCH日志里是否输出了sofftia级别的Early Media相关记录。如果在INVITE阶段就发送了183响应说明early media已经建立问题是出在MRCP引擎侧如果没有183响应说明SIP profile层就没生效。第三步检查拨号计划中ring_ready和answer的调用顺序。如果在MRCP识别之前就执行了answer那Early Media阶段已经被跳过p-early-media-support就算开启也白搭。正确顺序是ring_ready→ 执行MRCP TTS/ASR → 合适的时机再answer。5.4 容器重启后配置没丢但行为变了时区与资源限制有段时间我把容器做了docker restart之后发现FreeSWITCH的日志时间对了但通话录音的命名时间戳还是UTC早上8点的通话在文件系统里看起来是凌晨0点。这虽然不影响通话功能但因为是语音机器人录音的归档时间在业务审计里是敏感的很可能出错。这个问题来自容器内应用读取时区的方式挂载/etc/localtime只解决了C库层面的本地时间但FreeSWITCH的某些模块会额外读取TZ环境变量。所以一个更稳的做法是在docker run时加上-e TZAsia/Shanghai这样时区在两个层面保持一致。另外容器默认没有资源限制但宿主机一旦内存紧张FreeSWITCH进程可能被OOM kill。语音机器人上线后往往是7x24小时运行的我建议在docker run时加上--memory2g --cpus2限制内存可以有效防止容器内存膨胀拖垮宿主机。你可能会担心限制后性能不够但实测FreeSWITCH处理一个并发语音会话的内存占用通常在几十到一百多MB限制2GB对于测试环境绰绰有余生产环境按峰值会话数估算再加。5.5 用一个docker-compose.yml把部署固化下来单个docker run命令够用但配置项一多就容易漏。我后来把所有部署配置全部迁到docker-compose.yml里好处是每次重建环境不用回忆命令而且可以把p-early-media-support等所有配置改动一并记录在文档里。一个参考版的docker-compose.ymlversion: 3.8 services: freeswitch: image: safarov/freeswitch:latest container_name: freeswitch network_mode: host restart: always environment: - TZAsia/Shanghai volumes: - /data/freeswitch/conf:/etc/freeswitch - /data/freeswitch/recordings:/var/lib/freeswitch/recordings - /data/freeswitch/log:/var/log/freeswitch - /etc/localtime:/etc/localtime:ro mem_limit: 2g cpus: 2如果你用了这个compose文件注意一点FreeSWITCH的xml配置尽量放在宿主机目录统一管理不要在容器里手动改。因为compose up重建容器后容器内所有改动都会消失只有挂载进宿主机目录的配置才是持久化的。这点和Docker常规的最佳实践是一致的。最后再分享一点个人经验。语音机器人从“能通”到“好用”中间隔着的往往不是AI能力而是对话流程设计和对超时、拒识、early media这些细节的把控。我目前这套FreeSWITCH 阿里云SDM Docker的方案已经在多个测试项目中稳定运行其中对我帮助最大的一次调试就是花了一晚上把P-Early-Media参数彻底搞明白。你如果也在这个组合上折腾建议先跑通最简demo再逐步加业务逻辑这样每一次出问题都能很快定位到是哪一层掉了链子。