修改设备端对接

This commit is contained in:
HycJack
2026-04-16 21:13:06 +08:00
parent a22fcafea9
commit 602f0f7737
19 changed files with 1385 additions and 60 deletions

View File

@@ -1,4 +1,5 @@
import asyncio
import threading
from dashscope.audio.asr import TranslationRecognizerChat
from dashscope.audio.asr import TranscriptionResult
from dashscope.audio.asr import TranslationRecognizerCallback
@@ -10,15 +11,18 @@ from utils.logger import session_logger
class GummyCallback(TranslationRecognizerCallback):
def __init__(self, device_id, session_id):
def __init__(self, device_id, session_id, asr_instance=None):
self.device_id = device_id
self.session_id = session_id
self.asr_instance = asr_instance
self.transcript = ""
self.transcript_event = asyncio.Event()
self.received_result = False
self.error_message = None
self.is_closed = False
self.session_end = False
self.reconnect_event = threading.Event()
self.is_rate_limit_error = False
def on_open(self) -> None:
session_logger.info(self.device_id, self.session_id, "Gummy ASR连接建立成功")
@@ -50,9 +54,22 @@ class GummyCallback(TranslationRecognizerCallback):
def on_error(self, result) -> None:
self.error_message = str(result)
session_logger.error(
self.device_id, self.session_id, f"Gummy ASR错误: {self.error_message}"
)
error_str = str(result).lower()
self.is_rate_limit_error = "throttling" in error_str or "rate limit" in error_str
if self.is_rate_limit_error:
session_logger.warning(
self.device_id,
self.session_id,
f"Gummy ASR限流错误触发重连信号: {self.error_message}"
)
self.reconnect_event.set()
if self.asr_instance:
self.asr_instance.need_reconnect = True
else:
session_logger.error(
self.device_id, self.session_id, f"Gummy ASR错误: {self.error_message}"
)
self.transcript_event.set()
def on_close(self) -> None:
@@ -74,8 +91,11 @@ class AliyunASR(ASR, BaseService):
self.audio_buffer = bytearray()
self.translator = None
self.callback = None
self.buffer_size = 3200 # 约100ms的音频按16000采样率计算
self.max_end_silence = 2000 # 设为2000ms允许更长的停顿
self.buffer_size = 3200
self.max_end_silence = 2000
self.max_retries = 3
self.retry_delay = 1.0
self.need_reconnect = False
async def connect(self):
try:
@@ -86,9 +106,8 @@ class AliyunASR(ASR, BaseService):
return
self.transcript = ""
self.audio_buffer = bytearray()
self.callback = GummyCallback(self.device_id, self.session_id)
self.callback = GummyCallback(self.device_id, self.session_id, self)
self.translator = TranslationRecognizerChat(
model="gummy-chat-v1",
@@ -106,20 +125,79 @@ class AliyunASR(ASR, BaseService):
self.translator.start()
self.callback.transcript_event.clear()
self.connected = True
self.need_reconnect = False
except Exception as e:
session_logger.error(
self.device_id, self.session_id, f"建立Gummy ASR连接失败: {str(e)}"
)
self.connected = False
if self.translator:
try:
self.translator.stop()
except:
pass
self.translator = None
await self._cleanup_connection()
async def _cleanup_connection(self):
if self.translator:
try:
self.translator.stop()
except:
pass
self.translator = None
self.callback = None
self.connected = False
async def _handle_reconnect(self):
if not self.need_reconnect:
return True
session_logger.warning(
self.device_id,
self.session_id,
"检测到限流错误,准备重连..."
)
await self._cleanup_connection()
current_delay = self.retry_delay
for attempt in range(self.max_retries):
session_logger.info(
self.device_id,
self.session_id,
f"重连尝试 {attempt + 1}/{self.max_retries},等待{current_delay}"
)
await asyncio.sleep(current_delay)
try:
await self.connect()
if self.connected:
session_logger.info(
self.device_id,
self.session_id,
"重连成功"
)
return True
except Exception as e:
session_logger.error(
self.device_id,
self.session_id,
f"重连失败: {str(e)}"
)
current_delay *= 2
self.retry_delay = current_delay
session_logger.error(
self.device_id,
self.session_id,
"重连失败,已达到最大重试次数"
)
self.need_reconnect = False
return False
async def send_audio(self, audio_data: bytes):
if self.need_reconnect:
reconnected = await self._handle_reconnect()
if not reconnected:
session_logger.error(self.device_id, self.session_id, "Gummy ASR重连失败无法发送音频")
return
if not self.connected or not self.translator:
session_logger.error(self.device_id, self.session_id, "Gummy ASR连接未建立")
return
@@ -128,6 +206,19 @@ class AliyunASR(ASR, BaseService):
self.audio_buffer.extend(audio_data)
while len(self.audio_buffer) >= self.buffer_size:
if self.need_reconnect:
session_logger.info(
self.device_id,
self.session_id,
"检测到限流错误,暂停发送音频,准备重连"
)
reconnected = await self._handle_reconnect()
if not reconnected:
return
if not self.connected or not self.translator:
return
continue
chunk = bytes(self.audio_buffer[: self.buffer_size])
self.audio_buffer = self.audio_buffer[self.buffer_size :]

View File

@@ -24,9 +24,19 @@ class BaseLLM(LLM, BaseService):
async def connect(self):
try:
if self.session:
await self.session.close()
self.session = aiohttp.ClientSession()
if self.session and not self.session.closed:
return
connector = aiohttp.TCPConnector(
limit=20,
limit_per_host=5,
force_close=True,
enable_cleanup_closed=True
)
timeout = aiohttp.ClientTimeout(total=120, connect=30)
self.session = aiohttp.ClientSession(
connector=connector,
timeout=timeout
)
self.connected = True
except Exception as e:
session_logger.error(
@@ -97,8 +107,10 @@ class BaseLLM(LLM, BaseService):
}
try:
if not self.session or self.session.closed:
self.session = aiohttp.ClientSession()
self.connected = True
await self.connect()
if not self.connected:
yield None
return
response = await self.session.post(
f"{self.base_url}/chat/completions", headers=headers, json=data
)

View File

@@ -25,9 +25,24 @@ class MiniMaxTTS(TTS, BaseService):
self.voice_id = selected_role["minimax_voice_id"]
self.model = "speech-02-turbo"
self.selected_role = selected_role
self._client_session: Optional[aiohttp.ClientSession] = None
async def connect(self):
"""连接到MiniMax服务"""
if self._client_session is None or self._client_session.closed:
connector = aiohttp.TCPConnector(
limit=20,
limit_per_host=5,
force_close=True,
enable_cleanup_closed=True
)
timeout = aiohttp.ClientTimeout(total=120, connect=30)
self._client_session = aiohttp.ClientSession(
connector=connector,
timeout=timeout,
max_field_size=1024*1024,
max_line_size=10*1024*1024
)
self.connected = True
session_logger.info(
self.device_id,
@@ -38,6 +53,8 @@ class MiniMaxTTS(TTS, BaseService):
async def close(self):
"""关闭MiniMax服务连接"""
self.connected = False
if self._client_session and not self._client_session.closed:
await self._client_session.close()
async def tts(
self,
@@ -138,48 +155,66 @@ class MiniMaxTTS(TTS, BaseService):
audio_buffer = bytearray()
async with aiohttp.ClientSession() as client_session:
async with client_session.post(url, json=payload, headers=headers) as response:
if response.status != 200:
error_text = await response.text()
session_logger.error(
self.device_id,
self.session_id,
f"MiniMax TTS请求失败: {response.status}, {error_text}"
)
return None
session_logger.info(
if self._client_session is None or self._client_session.closed:
await self.connect()
async with self._client_session.post(url, json=payload, headers=headers) as response:
if response.status != 200:
error_text = await response.text()
session_logger.error(
self.device_id,
self.session_id,
"开始接收MiniMax TTS流式响应"
f"MiniMax TTS请求失败: {response.status}, {error_text}"
)
return None
session_logger.info(
self.device_id,
self.session_id,
"开始接收MiniMax TTS流式响应"
)
buffer = b""
chunk_size = 8192
async for chunk in response.content.iter_chunked(chunk_size):
buffer += chunk
async for line in response.content:
if line.startswith(b'data:'):
try:
data_json = json.loads(line[5:])
if "data" in data_json and "audio" in data_json["data"]:
status = data_json["data"].get("status", 1)
if status == 1: # 只处理status=1(合成中)的音频数据忽略status=2(合成结束)的汇总数据
audio_hex = data_json["data"]["audio"]
audio_binary = binascii.unhexlify(audio_hex)
audio_buffer.extend(audio_binary)
if status == 2: # 合成结束
session_logger.info(
self.device_id,
self.session_id,
"MiniMax TTS流式合成完成"
)
except Exception as e:
session_logger.error(
self.device_id,
self.session_id,
f"处理MiniMax TTS流式响应出错: {str(e)}",
exc_info=True
)
while b'\n' in buffer:
line, buffer = buffer.split(b'\n', 1)
line = line.strip()
if not line or not line.startswith(b'data:'):
continue
try:
data_json = json.loads(line[5:])
if "data" in data_json and "audio" in data_json["data"]:
status = data_json["data"].get("status", 1)
if status == 1:
audio_hex = data_json["data"]["audio"]
audio_binary = binascii.unhexlify(audio_hex)
audio_buffer.extend(audio_binary)
if status == 2:
session_logger.info(
self.device_id,
self.session_id,
"MiniMax TTS流式合成完成"
)
except json.JSONDecodeError as e:
session_logger.warning(
self.device_id,
self.session_id,
f"JSON解析失败: {str(e)}"
)
except Exception as e:
session_logger.error(
self.device_id,
self.session_id,
f"处理MiniMax TTS流式响应出错: {str(e)}",
exc_info=True
)
async with aiofiles.open(output_file, 'wb') as f:
await f.write(audio_buffer)