如何将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
核心问题
- 如何安全地将Asterisk的UUID传递到
app.py的AudioSocket服务器中? - 采用何种最佳设计可安全处理并发呼叫,避免一个呼叫获取到另一个呼叫的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替代内存字典,天然支持多实例并发:
api_server.py收到呼叫启动请求后,将uuid、callerId存入MongoDB,标记状态为waitingapp.py读取到UUID后,查询MongoDB获取呼叫信息,并更新状态为connected- 呼叫结束后,将状态更新为
completed或删除记录
这种方式无需处理线程锁,且支持跨实例的数据共享。
额外注意事项
- 为AudioSocket连接设置超时时间,防止无效连接占用资源
- 每个
Connection线程处理完呼叫后,必须清理对应上下文,避免内存泄漏 - 为每个呼叫生成独立的音频处理管道,确保音频数据不会串流
内容的提问来源于stack exchange,提问作者Shaan Srivastava
相关产品推荐
相关产品推荐

