修改设备端对接
This commit is contained in:
@@ -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 :]
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user