使用Twilio <Stream>标签通话5秒后断开,无音频播放且无报错
Twilio WebSocket音频流问题:无音频播放+通话自动断开
使用Twilio <Stream>标签通过WebSocket传输通话音频,WebSocket连接正常,能接收Twilio发送的音频块,但存在以下问题:
- 通话会在5-6秒后自动断开
- 按20ms频率发送μ-law编码音频(8000Hz,每块160字节),但呼叫者听不到音频
- Twilio仅返回
<Start><Stream/></Start>和<Pause length="7"/>,无错误日志
发送的音频负载片段
{ "event": "media", "streamSid": "FAKE_STREAM_SID_FOR_TEST", "media": { "payload": "////////fn5+Fo7/////fe5+..." } }
WebSocket收发代码片段
@api.websocket("/twilio/wss/{call_id}") @tracer.start_as_current_span("twilio_wss_post") async def twilio_wss_post(call_id: str, websocket: WebSocket): stream_sid = None stop_event = asyncio.Event() call_state = await _db.call_get(call_id=UUID(call_id)) if not hasattr(call_state, "audio_to_bot_queue"): object.__setattr__(call_state, "audio_to_bot_queue", asyncio.Queue()) if not hasattr(call_state, "audio_from_bot_queue"): object.__setattr__(call_state, "audio_from_bot_queue", asyncio.Queue()) await websocket.accept() async def _consume_audio(): nonlocal stream_sid while True: msg = json.loads(await websocket.receive_text()) if msg["event"] == "stop": stop_event.set() break if msg["event"] == "start": stream_sid = msg.get("streamSid") continue if msg["event"] != "media": continue chunk = msg["media"]["payload"] decoded = b64decode(chunk) await call_state.audio_to_bot_queue.put(decoded) async def _send_audio(): CHUNK_SIZE = 160 CHUNK_DELAY = 0.02 timeout_start = time.time() while stream_sid is None: if time.time() - timeout_start > 10: return await asyncio.sleep(0.01) while not stop_event.is_set(): try: audio = call_state.audio_from_bot_queue.get_nowait() except: audio = bytes([0xFF] * CHUNK_SIZE) for i in range(0, len(audio), CHUNK_SIZE): if stop_event.is_set(): break chunk = audio[i:i + CHUNK_SIZE] if len(chunk) < CHUNK_SIZE: chunk += bytes([0xFF] * (CHUNK_SIZE - len(chunk))) await websocket.send_text(json.dumps({ "event": "media", "streamSid": stream_sid, "media": { "payload": b64encode(chunk).decode("utf-8") } })) await asyncio.sleep(CHUNK_DELAY) await asyncio.gather( _consume_audio(), _send_audio() )
已验证内容
- 发送的音频在本地播放正常
- 从Twilio接收的音频块可成功解码并保存
- WebSocket日志显示音频块已正常收发
可能的原因与排查建议
1. TwiML配置导致通话断开
你的TwiML中使用了<Pause length="7"/>,这会让Twilio等待7秒后自动结束通话,这是通话5-6秒断开的直接原因。需要修改TwiML保持通话活跃:
- 替换
<Pause>为持续生效的指令,比如结合<Dial>(转接场景)或<Say loop="0">(循环空内容),确保WebSocket流期间Twilio不会主动终止通话 - 确认
<Stream>标签的track属性设置为both或outbound,若仅设置track="inbound",Twilio会忽略你发送的 outbound 音频
2. 音频消息格式缺失关键字段
Twilio官方标注sequenceNumber为可选,但实际场景中,缺少序号可能导致Twilio无法正确排序音频帧,进而无法播放。建议在发送的media事件中添加递增序号:
{ "event": "media", "streamSid": "YOUR_ACTUAL_STREAM_SID", "media": { "payload": "BASE64_ENCODED_AUDIO", "sequenceNumber": 0 // 每次发送递增1 } }
3. StreamSid一致性问题
确保发送media事件时的streamSid与Twilio在start事件返回的值完全一致(包括大小写)。可在代码中打印stream_sid,确认未被修改或为空。
4. 异步逻辑隐患
- 捕获队列空异常时,不要用
except:捕获所有异常,改为明确捕获asyncio.Queue.Empty,避免隐藏其他错误:try: audio = call_state.audio_from_bot_queue.get_nowait() except asyncio.Queue.Empty: audio = bytes([0xFF] * CHUNK_SIZE) _send_audio中的10秒超时逻辑可能提前退出,导致后续收到start事件也无法发送音频,可调整超时时间或改为等待start事件的触发信号,而非轮询。
5. 音频流连续性与时序问题
Twilio要求音频流连续稳定,若发送间隔波动过大(受事件循环阻塞影响),可能导致Twilio判定流中断:
- 验证
asyncio.sleep(0.02)的实际延迟,确保每次发送间隔接近20ms - 收到
start事件后立刻开始发送音频,避免延迟过久导致Twilio终止流
6. 编码验证
虽然本地播放正常,仍需确认:
- 发送的音频是纯μ-law单字节流,无额外封装或转换
- 填充的静音帧
0xFF符合μ-law标准(对应线性PCM的0值,静音正确)
内容的提问来源于stack exchange,提问作者yash bharti
相关产品推荐
相关产品推荐

