You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 17:29:57