# main.py import asyncio import uvicorn from fastapi import FastAPI, WebSocket, WebSocketDisconnect from starlette.middleware.cors import CORSMiddleware # 导入抽离的消息管理模块 from ws_message_manager import ws_queue_manager, ServerMsgType import logging from frontend_ws import frontend_websocket_handler from asr_client import init_asr_pool, close_asr_pool from contextlib import asynccontextmanager # 初始化日志配置(全局生效) logging.basicConfig( level=logging.INFO, # 日志级别:DEBUG/INFO/WARNING/ERROR format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", # 日志格式 handlers=[ logging.StreamHandler(), # 输出到控制台 logging.FileHandler("ws_server.log", encoding="utf-8") # 输出到文件(可选) ] ) # 创建日志实例(后续所有logger调用都用这个实例) logger = logging.getLogger(__name__) # __name__ 是当前模块名,便于区分日志来源 # FastAPI 启动时初始化 ASR 连接池 @asynccontextmanager async def lifespan(app: FastAPI): # 启动时执行(原 startup 逻辑) print(' FastAPI 启动时初始化 ASR 连接池') await init_asr_pool() yield # 应用运行中 # 关闭时执行(可选,比如清理连接池) print("应用关闭,开始清理 ASR 连接池...") await close_asr_pool() # 这里可以添加连接池关闭逻辑(如关闭所有 ASR 连接) # 初始化FastAPI应用 app = FastAPI( title="语音交互网关", description="整合前端/ASR/大模型/TTS的异步网关服务", version="1.0", lifespan=lifespan # 绑定生命周期 ) # 跨域配置 app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) async def process_audio(websocket: WebSocket, audio_data: bytes): """处理音频数据,生成多类型消息并推送""" try: # 1. 模拟ASR识别(生成文字) text_result = f"识别结果:音频长度 {len(audio_data)} bytes" await ws_queue_manager.send_message(websocket, ServerMsgType.TEXT, text_result) # 2. 模拟TTS生成语音(二进制数据) voice_data = b"xx" # 替换为真实TTS输出 await ws_queue_manager.send_message(websocket, ServerMsgType.VOICE, voice_data) # 3. 模拟数字人动画信息 animation_data = { "action": "mouth_move", "speed": 1.2, "duration": 1000 } await ws_queue_manager.send_message(websocket, ServerMsgType.ANIMATION, animation_data) # 4. 模拟前端动作指令 action_data = { "type": "show_loading", "status": False } await ws_queue_manager.send_message(websocket, ServerMsgType.ACTION, action_data) except Exception as e: # 推送错误信息 await ws_queue_manager.send_message(websocket, ServerMsgType.ERROR, str(e)) @app.websocket("/ws/audio") async def websocket_audio(websocket: WebSocket): # 完全委托给frontend_websocket_handler处理 await frontend_websocket_handler(websocket) # 前端WebSocket路由 # @app.websocket("/ws/audio") # async def websocket_audio(websocket: WebSocket): # # 1. 接受连接并创建专属队列 # await websocket.accept() # await ws_queue_manager.create_queue(websocket) # # try: # # 2. 循环接收前端音频数据 # while True: # # 接收二进制音频数据(前端发送的麦克风数据) # audio_data = await websocket.receive_bytes() # # 异步处理音频(不阻塞接收) # asyncio.create_task(process_audio(websocket, audio_data)) # # except WebSocketDisconnect: # logger.info(f"连接 {id(websocket)} 主动断开") # except Exception as e: # logger.error(f"连接异常: {e}") # finally: # # 3. 清理队列和协程 # await ws_queue_manager.close_queue(websocket) # 健康检查接口 @app.get("/health") async def health_check(): return { "status": "ok", "service": "voice_gateway", "active_connections": len(ws_queue_manager.queue_map) } if __name__ == "__main__": uvicorn.run( "main:app", host="0.0.0.0", port=8000, reload=True, # 生产环境关闭 workers=1 # 异步框架单worker足够,多worker需分布式队列 )