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

如何将Asterisk的CALL_UUID从API服务传递到Python AudioSocket应用?

问题描述

我部署了两个Python服务及Asterisk拨号计划:

  • api_server.py:提供HTTP API端点
  • app.py:运行AudioSocket服务器(通过TCP套接字接收Asterisk的呼叫)

拨号计划中生成CALL_UUID并先通知API服务器,再启动AudioSocket:

same  => n,Set(CALL_UUID=${UUID()})
same  => n,System(curl -s "http://192.168.1.6:8000/api/call-start?callerId=${CALLERID(num)}&uuid=${CALL_UUID}" >/dev/null 2>&1 || wget -q -O - "http://192.168.1.7:8000/api/call-start?callerId=${CALLERID(num)}&uuid=${CALL_UUID}" >/dev/null 2>&1)
same  => n,AudioSocket(${CALL_UUID},192.168.1.6:3000)

api_server.py已成功接收并存储callerId与uuid(当前存在内存字典,也可存MongoDB)。但在app.py中遇到问题:

  • 当AudioSocket连接被接受时,调用conn.uuid始终返回None
  • 尝试接收套接字首字节解码时,因AudioSocket发送的是原始二进制音频而非文本元数据,触发UnicodeDecodeError

现有AudioSocket服务器代码:

class Audiosocket:
    def __init__(self, bind_info, timeout=None):
        if not isinstance(bind_info, tuple):
            raise TypeError("Expected tuple (addr, port), received", type(bind_info))

        self.addr, self.port = bind_info
        self.initial_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.initial_sock.bind((self.addr, self.port))
        self.initial_sock.settimeout(timeout)
        self.initial_sock.listen(1)
        self.port = self.initial_sock.getsockname()[1]

        self.user_resample = None
        self.asterisk_resample = None

    def prepare_output(self, outrate=44000, channels=2, ulaw2lin=False):
        self.asterisk_resample = audioop_struct(
            rate=outrate, channels=channels, ulaw2lin=ulaw2lin, ratecv_state=None
        )

    def listen(self):
        conn, peer_addr = self.initial_sock.accept()
        connection = Connection(conn, peer_addr, self.asterisk_resample)
        connection_thread = Thread(target=connection._process, args=())
        connection_thread.start()
        return connection

app.py中使用方式:

def listen_for_connections(self):
    try:
        conn = self.audiosocket.listen()
        log.debug(f"New AudioSocket connection received: {conn.uuid}")
        return conn
    except Exception as e:
        log.error(f"Error listening for connections: {e}")
        raise

核心问题

  1. 如何安全地将Asterisk的UUID传递到app.py的AudioSocket服务器中?
  2. 采用何种最佳设计可安全处理并发呼叫,避免一个呼叫获取到另一个呼叫的UUID?

解决方案

1. 安全传递UUID到AudioSocket服务器

Asterisk的AudioSocket应用会在TCP连接建立后优先发送UUID文本数据(UTF-8编码,末尾带换行符),再开始传输音频。问题出在你的Connection类未正确读取这部分初始元数据。

修改Connection类的初始化逻辑,提前读取UUID:

class Connection:
    def __init__(self, sock, peer_addr, resample_obj):
        self.sock = sock
        self.peer_addr = peer_addr
        self.resample_obj = resample_obj
        self.uuid = None
        
        # 读取UUID:标准UUID长度36字节,加1字节换行符,最多读37字节
        try:
            uuid_data = self.sock.recv(37).strip().decode('utf-8')
            if len(uuid_data) == 36:
                self.uuid = uuid_data
        except Exception as e:
            log.error(f"Failed to read UUID: {e}")
            self.sock.close()
            raise

修改后,在listen_for_connections中即可正常获取conn.uuid,无需解析音频数据。

2. 并发呼叫的UUID隔离设计

要避免UUID混淆,核心是让每个呼叫的上下文数据完全独立,并通过共享存储关联API服务器的数据,以下是两种可行方案:

方案一:线程安全内存字典(单机场景)

在app.py中维护一个线程安全的字典,用UUID作为键存储呼叫上下文,结合api_server.py的存储实现数据关联:

from collections import defaultdict
import threading

# 线程安全的呼叫上下文存储
call_contexts = defaultdict(dict)
lock = threading.Lock()

# api_server.py的call-start接口逻辑
def handle_call_start(caller_id, uuid):
    with lock:
        call_contexts[uuid] = {"callerId": caller_id, "status": "active"}

# app.py的Connection类处理逻辑
def _process(self):
    if self.uuid:
        with lock:
            context = call_contexts.get(self.uuid)
        if context:
            log.info(f"Processing call {self.uuid} from {context['callerId']}")
        # 后续音频处理逻辑
        
        # 呼叫结束后清理上下文
        with lock:
            call_contexts.pop(self.uuid, None)

方案二:MongoDB共享存储(分布式/高并发场景,推荐)

如果服务是分布式部署或并发量较高,用MongoDB替代内存字典,天然支持多实例并发:

  1. api_server.py收到呼叫启动请求后,将uuid、callerId存入MongoDB,标记状态为waiting
  2. app.py读取到UUID后,查询MongoDB获取呼叫信息,并更新状态为connected
  3. 呼叫结束后,将状态更新为completed或删除记录

这种方式无需处理线程锁,且支持跨实例的数据共享。

额外注意事项

  • 为AudioSocket连接设置超时时间,防止无效连接占用资源
  • 每个Connection线程处理完呼叫后,必须清理对应上下文,避免内存泄漏
  • 为每个呼叫生成独立的音频处理管道,确保音频数据不会串流

内容的提问来源于stack exchange,提问作者Shaan Srivastava

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 08:44:51