Files
2025-12-01 03:42:34 +08:00

138 lines
4.4 KiB
Python

# 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需分布式队列
)