update:兼容豆包流式ASR

This commit is contained in:
hrz
2025-05-29 23:56:34 +08:00
parent d86a2cf9de
commit bb42095ca0
18 changed files with 630 additions and 410 deletions
+27 -11
View File
@@ -12,18 +12,22 @@ import subprocess
import websockets import websockets
from core.utils.util import ( from core.utils.util import (
extract_json_from_string, extract_json_from_string,
initialize_modules,
check_vad_update, check_vad_update,
check_asr_update, check_asr_update,
filter_sensitive_info, filter_sensitive_info,
initialize_tts,
) )
from typing import Dict, Any from typing import Dict, Any
from core.mcp.manager import MCPManager from core.mcp.manager import MCPManager
from core.utils.modules_initialize import (
initialize_modules,
initialize_tts,
initialize_asr,
)
from core.handle.reportHandle import report from core.handle.reportHandle import report
from core.providers.tts.default import DefaultTTS from core.providers.tts.default import DefaultTTS
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from core.utils.dialogue import Message, Dialogue from core.utils.dialogue import Message, Dialogue
from core.providers.asr.dto.dto import InterfaceType
from core.handle.textHandle import handleTextMessage from core.handle.textHandle import handleTextMessage
from core.handle.functionHandler import FunctionHandler from core.handle.functionHandler import FunctionHandler
from plugins_func.loadplugins import auto_import_modules from plugins_func.loadplugins import auto_import_modules
@@ -112,7 +116,6 @@ class ConnectionHandler:
# asr相关变量 # asr相关变量
self.asr_audio = [] self.asr_audio = []
self.asr_server_receive = True
# llm相关变量 # llm相关变量
self.llm_finish_task = True self.llm_finish_task = True
@@ -315,10 +318,14 @@ class ConnectionHandler:
if self.vad is None: if self.vad is None:
self.vad = self._vad self.vad = self._vad
if self.asr is None: if self.asr is None:
self.asr = self._asr self.asr = self._initialize_asr()
# 打开语音识别通道
asyncio.run_coroutine_threadsafe(
self.asr.open_audio_channels(self), self.loop
)
if self.tts is None: if self.tts is None:
self.tts = self._initialize_tts() self.tts = self._initialize_tts()
# 使用事件循环运行异步方法 # 打开语音合成通道
asyncio.run_coroutine_threadsafe( asyncio.run_coroutine_threadsafe(
self.tts.open_audio_channels(self), self.loop self.tts.open_audio_channels(self), self.loop
) )
@@ -356,6 +363,19 @@ class ConnectionHandler:
return tts return tts
def _initialize_asr(self):
"""初始化ASR"""
if self._asr.interface_type == InterfaceType.LOCAL:
# 如果公共ASR是本地服务,则直接返回
# 因为本地一个实例ASR,可以被多个连接共享
asr = self._asr
else:
# 如果公共ASR是远程服务,则初始化一个新实例
# 因为远程ASR,涉及到websocket连接和接收线程,需要每个连接一个实例
asr = initialize_asr(self.config)
return asr
def _initialize_private_config(self): def _initialize_private_config(self):
"""如果是从配置文件获取,则进行二次实例化""" """如果是从配置文件获取,则进行二次实例化"""
if not self.read_config_from_api: if not self.read_config_from_api:
@@ -597,6 +617,8 @@ class ConnectionHandler:
text_index = 0 text_index = 0
for response in llm_responses: for response in llm_responses:
if self.client_abort:
break
if functions is not None: if functions is not None:
content, tools_call = response content, tools_call = response
if "content" in response: if "content" in response:
@@ -622,9 +644,6 @@ class ConnectionHandler:
if content is not None and len(content) > 0: if content is not None and len(content) > 0:
if not tool_call_flag: if not tool_call_flag:
response_message.append(content) response_message.append(content)
if self.client_abort:
break
if text_index == 0: if text_index == 0:
self.tts.tts_text_queue.put( self.tts.tts_text_queue.put(
TTSMessageDTO( TTSMessageDTO(
@@ -801,9 +820,7 @@ class ConnectionHandler:
item = self.report_queue.get(timeout=1) item = self.report_queue.get(timeout=1)
if item is None: # 检测毒丸对象 if item is None: # 检测毒丸对象
break break
type, text, audio_data, report_time = item type, text, audio_data, report_time = item
try: try:
# 检查线程池状态 # 检查线程池状态
if self.executor is None: if self.executor is None:
@@ -834,7 +851,6 @@ class ConnectionHandler:
def clearSpeakStatus(self): def clearSpeakStatus(self):
self.logger.bind(tag=TAG).debug(f"清除服务端讲话状态") self.logger.bind(tag=TAG).debug(f"清除服务端讲话状态")
self.asr_server_receive = True
async def close(self, ws=None): async def close(self, ws=None):
"""资源清理方法""" """资源清理方法"""
@@ -1,4 +1,3 @@
from config.logger import setup_logging
import json import json
import uuid import uuid
from core.handle.sendAudioHandle import send_stt_message from core.handle.sendAudioHandle import send_stt_message
@@ -146,4 +145,3 @@ async def process_intent_result(conn, intent_result, original_text):
def speak_txt(conn, text): def speak_txt(conn, text):
conn.tts.tts_one_sentence(conn, ContentType.TEXT, content_detail=text) conn.tts.tts_one_sentence(conn, ContentType.TEXT, content_detail=text)
conn.dialogue.put(Message(role="assistant", content=text)) conn.dialogue.put(Message(role="assistant", content=text))
@@ -1,10 +1,7 @@
import time
import copy
from core.utils.util import remove_punctuation_and_length
from core.handle.sendAudioHandle import send_stt_message from core.handle.sendAudioHandle import send_stt_message
from core.handle.intentHandler import handle_user_intent from core.handle.intentHandler import handle_user_intent
from core.utils.output_counter import check_device_output_limit from core.utils.output_counter import check_device_output_limit
from core.handle.reportHandle import enqueue_asr_report import time
from core.handle.sendAudioHandle import SentenceType from core.handle.sendAudioHandle import SentenceType
from core.utils.util import audio_to_data from core.utils.util import audio_to_data
@@ -13,46 +10,17 @@ TAG = __name__
async def handleAudioMessage(conn, audio): async def handleAudioMessage(conn, audio):
if conn.vad is None: if conn.vad is None:
conn.logger.bind(tag=TAG).warning("VAD模块未初始化,继续等待")
return return
if not conn.asr_server_receive: if conn.asr is None:
conn.logger.bind(tag=TAG).debug(f"前期数据处理中,暂停接收") conn.logger.bind(tag=TAG).warning("ASR模块未初始化,继续等待")
return return
if conn.client_listen_mode == "auto" or conn.client_listen_mode == "realtime": # 当前片段是否有人说话
have_voice = conn.vad.is_vad(conn, audio) have_voice = conn.vad.is_vad(conn, audio)
else: # 设备长时间空闲检测,用于say goodbye
have_voice = conn.client_have_voice await no_voice_close_connect(conn, have_voice)
# 接收音频
# 如果本次没有声音,本段也没声音,就把声音丢弃了 await conn.asr.receive_audio(audio, have_voice)
if have_voice == False and conn.client_have_voice == False:
await no_voice_close_connect(conn)
conn.asr_audio.append(audio)
conn.asr_audio = conn.asr_audio[
-10:
] # 保留最新的10帧音频内容,解决ASR句首丢字问题
return
conn.client_no_voice_last_time = 0.0
conn.asr_audio.append(audio)
# 如果本段有声音,且已经停止了
if conn.client_voice_stop:
conn.client_abort = False
conn.asr_server_receive = False
# 音频太短了,无法识别
if len(conn.asr_audio) < 15:
conn.asr_server_receive = True
else:
raw_text, _ = await conn.asr.speech_to_text(
conn.asr_audio, conn.session_id
) # 确保ASR模块返回原始文本
conn.logger.bind(tag=TAG).info(f"识别文本: {raw_text}")
text_len, _ = remove_punctuation_and_length(raw_text)
if text_len > 0:
# 使用自定义模块进行上报
await startToChat(conn, raw_text)
enqueue_asr_report(conn, raw_text, copy.deepcopy(conn.asr_audio))
else:
conn.asr_server_receive = True
conn.asr_audio.clear()
conn.reset_vad_states()
async def startToChat(conn, text): async def startToChat(conn, text):
@@ -73,7 +41,6 @@ async def startToChat(conn, text):
if intent_handled: if intent_handled:
# 如果意图已被处理,不再进行聊天 # 如果意图已被处理,不再进行聊天
conn.asr_server_receive = True
return return
# 意图未被处理,继续常规聊天流程 # 意图未被处理,继续常规聊天流程
@@ -81,7 +48,10 @@ async def startToChat(conn, text):
conn.executor.submit(conn.chat, text) conn.executor.submit(conn.chat, text)
async def no_voice_close_connect(conn): async def no_voice_close_connect(conn, have_voice):
if have_voice:
conn.client_no_voice_last_time = 0.0
return
if conn.client_no_voice_last_time == 0.0: if conn.client_no_voice_last_time == 0.0:
conn.client_no_voice_last_time = time.time() * 1000 conn.client_no_voice_last_time = time.time() * 1000
else: else:
@@ -95,7 +65,6 @@ async def no_voice_close_connect(conn):
): ):
conn.close_after_chat = True conn.close_after_chat = True
conn.client_abort = False conn.client_abort = False
conn.asr_server_receive = False
end_prompt = conn.config.get("end_prompt", {}) end_prompt = conn.config.get("end_prompt", {})
if end_prompt and end_prompt.get("enable", True) is False: if end_prompt and end_prompt.get("enable", True) is False:
conn.logger.bind(tag=TAG).info("结束对话,无需发送结束提示语") conn.logger.bind(tag=TAG).info("结束对话,无需发送结束提示语")
@@ -131,6 +131,11 @@ async def send_tts_message(conn, state, text=None):
async def send_stt_message(conn, text): async def send_stt_message(conn, text):
end_prompt_str = conn.config.get("end_prompt", {}).get("prompt")
if end_prompt_str and end_prompt_str == text:
await send_tts_message(conn, "start")
return
"""发送 STT 状态消息""" """发送 STT 状态消息"""
stt_text = get_string_no_punctuation_or_emoji(text) stt_text = get_string_no_punctuation_or_emoji(text)
await conn.websocket.send( await conn.websocket.send(
@@ -41,12 +41,13 @@ async def handleTextMessage(conn, message):
if len(conn.asr_audio) > 0: if len(conn.asr_audio) > 0:
await handleAudioMessage(conn, b"") await handleAudioMessage(conn, b"")
elif msg_json["state"] == "detect": elif msg_json["state"] == "detect":
conn.asr_server_receive = False
conn.client_have_voice = False conn.client_have_voice = False
conn.asr_audio.clear() conn.asr_audio.clear()
if "text" in msg_json: if "text" in msg_json:
original_text = msg_json["text"] # 保留原始文本 original_text = msg_json["text"] # 保留原始文本
filtered_len, filtered_text = remove_punctuation_and_length(original_text) filtered_len, filtered_text = remove_punctuation_and_length(
original_text
)
# 识别是否是唤醒词 # 识别是否是唤醒词
is_wakeup_words = filtered_text in conn.config.get("wakeup_words") is_wakeup_words = filtered_text in conn.config.get("wakeup_words")
+2 -2
View File
@@ -3,8 +3,8 @@ import time
import asyncio import asyncio
from aiohttp import web from aiohttp import web
from config.logger import setup_logging from config.logger import setup_logging
from core.connection import ConnectionHandler from core.utils.util import get_local_ip
from core.utils.util import get_local_ip, initialize_modules from core.utils.modules_initialize import initialize_modules
TAG = __name__ TAG = __name__
@@ -2,7 +2,6 @@ import http.client
import json import json
import asyncio import asyncio
from typing import Optional, Tuple, List from typing import Optional, Tuple, List
import opuslib_next
import os import os
import uuid import uuid
import hmac import hmac
@@ -14,6 +13,7 @@ import time
from datetime import datetime from datetime import datetime
from config.logger import setup_logging from config.logger import setup_logging
from core.providers.asr.base import ASRProviderBase from core.providers.asr.base import ASRProviderBase
from core.providers.asr.dto.dto import InterfaceType
TAG = __name__ TAG = __name__
logger = setup_logging() logger = setup_logging()
@@ -90,6 +90,7 @@ class AccessToken:
class ASRProvider(ASRProviderBase): class ASRProvider(ASRProviderBase):
def __init__(self, config: dict, delete_audio_file: bool): def __init__(self, config: dict, delete_audio_file: bool):
super().__init__() super().__init__()
self.interface_type = InterfaceType.NON_STREAM
"""阿里云ASR初始化""" """阿里云ASR初始化"""
# 新增空值判断逻辑 # 新增空值判断逻辑
self.access_key_id = config.get("access_key_id") self.access_key_id = config.get("access_key_id")
@@ -1,18 +1,10 @@
import base64
import hashlib
import hmac
import json
import time import time
from datetime import datetime, timezone
import os import os
import uuid
from typing import Optional, Tuple, List from typing import Optional, Tuple, List
import wave
import opuslib_next
from aip import AipSpeech from aip import AipSpeech
from core.providers.asr.base import ASRProviderBase from core.providers.asr.base import ASRProviderBase
from config.logger import setup_logging from config.logger import setup_logging
from core.providers.asr.dto.dto import InterfaceType
TAG = __name__ TAG = __name__
logger = setup_logging() logger = setup_logging()
@@ -21,6 +13,7 @@ logger = setup_logging()
class ASRProvider(ASRProviderBase): class ASRProvider(ASRProviderBase):
def __init__(self, config: dict, delete_audio_file: bool = True): def __init__(self, config: dict, delete_audio_file: bool = True):
super().__init__() super().__init__()
self.interface_type = InterfaceType.NON_STREAM
self.app_id = config.get("app_id") self.app_id = config.get("app_id")
self.api_key = config.get("api_key") self.api_key = config.get("api_key")
self.secret_key = config.get("secret_key") self.secret_key = config.get("secret_key")
@@ -1,10 +1,15 @@
import os import os
import time
import copy
import uuid import uuid
import wave import wave
import opuslib_next import opuslib_next
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from config.logger import setup_logging from config.logger import setup_logging
from typing import Optional, Tuple, List from typing import Optional, Tuple, List
from core.utils.util import remove_punctuation_and_length
from core.handle.reportHandle import enqueue_asr_report
from core.handle.receiveAudioHandle import startToChat
TAG = __name__ TAG = __name__
logger = setup_logging() logger = setup_logging()
@@ -13,6 +18,54 @@ logger = setup_logging()
class ASRProviderBase(ABC): class ASRProviderBase(ABC):
def __init__(self): def __init__(self):
self.audio_format = "opus" self.audio_format = "opus"
self.conn = None
# 打开音频通道
# 这里默认是非流式的处理方式
# 流式处理方式请在子类中重写
async def open_audio_channels(self, conn):
self.conn = conn
# 接收音频
# 这里默认是非流式的处理方式
# 流式处理方式请在子类中重写
async def receive_audio(self, audio, audio_have_voice):
if (
self.conn.client_listen_mode == "auto"
or self.conn.client_listen_mode == "realtime"
):
have_voice = audio_have_voice
else:
have_voice = self.conn.client_have_voice
# 如果本次没有声音,本段也没声音,就把声音丢弃了
if have_voice == False and self.conn.client_have_voice == False:
self.conn.asr_audio.append(audio)
self.conn.asr_audio = self.conn.asr_audio[-10:]
return
# 如果本段有声音,且已经停止了
if self.conn.client_voice_stop:
self.conn.client_abort = False
# 音频太短了,无法识别
if len(self.conn.asr_audio) < 15:
self.conn.asr_audio.clear()
self.conn.reset_vad_states()
else:
await self.handle_voice_stop()
# 处理语音停止
async def handle_voice_stop(self):
raw_text, _ = await self.speech_to_text(
self.conn.asr_audio, self.conn.session_id
) # 确保ASR模块返回原始文本
self.conn.logger.bind(tag=TAG).info(f"识别文本: {raw_text}")
text_len, _ = remove_punctuation_and_length(raw_text)
if text_len > 0:
# 使用自定义模块进行上报
await startToChat(self.conn, raw_text)
enqueue_asr_report(self.conn, raw_text, copy.deepcopy(self.conn.asr_audio))
self.conn.asr_audio.clear()
self.conn.reset_vad_states()
def save_audio_to_file(self, pcm_data: List[bytes], session_id: str) -> str: def save_audio_to_file(self, pcm_data: List[bytes], session_id: str) -> str:
"""PCM数据保存为WAV文件""" """PCM数据保存为WAV文件"""
+375 -209
View File
@@ -1,265 +1,431 @@
import time
import os
import uuid
import json import json
import gzip import gzip
import uuid
import asyncio
import websockets import websockets
from config.logger import setup_logging import opuslib_next
from typing import Optional, Tuple, List
from core.providers.asr.base import ASRProviderBase from core.providers.asr.base import ASRProviderBase
from config.logger import setup_logging
from core.providers.asr.dto.dto import InterfaceType
import threading
TAG = __name__ TAG = __name__
logger = setup_logging() logger = setup_logging()
CLIENT_FULL_REQUEST = 0b0001 CLIENT_FULL_REQUEST = 0b0001
CLIENT_AUDIO_ONLY_REQUEST = 0b0010 CLIENT_AUDIO_ONLY_REQUEST = 0b0010
NO_SEQUENCE = 0b0000
NEG_SEQUENCE = 0b0010
SERVER_FULL_RESPONSE = 0b1001 SERVER_FULL_RESPONSE = 0b1001
SERVER_ACK = 0b1011 SERVER_ACK = 0b1011
SERVER_ERROR_RESPONSE = 0b1111 SERVER_ERROR_RESPONSE = 0b1111
NO_SEQUENCE = 0b0000
NO_SERIALIZATION = 0b0000 NEG_SEQUENCE = 0b0010
JSON = 0b0001 JSON_SERIALIZATION = 0b0001
THRIFT = 0b0011 GZIP_COMPRESSION = 0b0001
CUSTOM_TYPE = 0b1111 PROTOCOL_VERSION = 0b0001
NO_COMPRESSION = 0b0000
GZIP = 0b0001
CUSTOM_COMPRESSION = 0b1111
def parse_response(res):
"""
protocol_version(4 bits), header_size(4 bits),
message_type(4 bits), message_type_specific_flags(4 bits)
serialization_method(4 bits) message_compression(4 bits)
reserved 8bits) 保留字段
header_extensions 扩展头(大小等于 8 * 4 * (header_size - 1) )
payload 类似与http 请求体
"""
protocol_version = res[0] >> 4
header_size = res[0] & 0x0F
message_type = res[1] >> 4
message_type_specific_flags = res[1] & 0x0F
serialization_method = res[2] >> 4
message_compression = res[2] & 0x0F
reserved = res[3]
header_extensions = res[4 : header_size * 4]
payload = res[header_size * 4 :]
result = {}
payload_msg = None
payload_size = 0
if message_type == SERVER_FULL_RESPONSE:
payload_size = int.from_bytes(payload[:4], "big", signed=True)
payload_msg = payload[4:]
elif message_type == SERVER_ACK:
seq = int.from_bytes(payload[:4], "big", signed=True)
result["seq"] = seq
if len(payload) >= 8:
payload_size = int.from_bytes(payload[4:8], "big", signed=False)
payload_msg = payload[8:]
elif message_type == SERVER_ERROR_RESPONSE:
code = int.from_bytes(payload[:4], "big", signed=False)
result["code"] = code
payload_size = int.from_bytes(payload[4:8], "big", signed=False)
payload_msg = payload[8:]
if payload_msg is None:
return result
if message_compression == GZIP:
payload_msg = gzip.decompress(payload_msg)
if serialization_method == JSON:
payload_msg = json.loads(str(payload_msg, "utf-8"))
elif serialization_method != NO_SERIALIZATION:
payload_msg = str(payload_msg, "utf-8")
result["payload_msg"] = payload_msg
result["payload_size"] = payload_size
return result
class ASRProvider(ASRProviderBase): class ASRProvider(ASRProviderBase):
def __init__(self, config: dict, delete_audio_file: bool): def __init__(self, config, delete_audio_file):
super().__init__() super().__init__()
self.appid = config.get("appid") self.interface_type = InterfaceType.STREAM
self.config = config
self.text = ""
self.max_retries = 3
self.retry_delay = 2 # 重试延迟秒数
self.recv_lock = asyncio.Lock() # 添加接收锁
self.appid = str(config.get("appid"))
self.cluster = config.get("cluster") self.cluster = config.get("cluster")
self.access_token = config.get("access_token") self.access_token = config.get("access_token")
self.boosting_table_name = config.get("boosting_table_name", "") self.boosting_table_name = config.get("boosting_table_name", "")
self.correct_table_name = config.get("correct_table_name", "") self.correct_table_name = config.get("correct_table_name", "")
self.output_dir = config.get("output_dir") self.output_dir = config.get("output_dir", "temp/")
self.delete_audio_file = delete_audio_file self.delete_audio_file = delete_audio_file
self.host = "openspeech.bytedance.com" self.ws_url = "wss://openspeech.bytedance.com/api/v2/asr"
self.ws_url = f"wss://{self.host}/api/v2/asr" self.uid = config.get("uid", "streaming_asr_service")
self.success_code = 1000 self.workflow = config.get(
self.seg_duration = 15000 "workflow", "audio_in,resample,partition,vad,fe,decode,itn,nlu_punctuate"
)
self.result_type = config.get("result_type", "single")
self.format = config.get("format", "raw")
self.codec = config.get("codec", "pcm")
self.rate = config.get("sample_rate", 16000)
self.language = config.get("language", "zh-CN")
self.bits = config.get("bits", 16)
self.channel = config.get("channel", 1)
self.auth_method = config.get("auth_method", "token")
self.secret = config.get("secret", "access_secret")
self.decoder = opuslib_next.Decoder(16000, 1)
self.asr_ws = None
self.forward_task = None
self.conn = None
self.asr_thread = None
# 确保输出目录存在 ###################################################################################
os.makedirs(self.output_dir, exist_ok=True) # 豆包流式ASR重写父类的方法--开始
###################################################################################
async def open_audio_channels(self, conn):
await super().open_audio_channels(conn)
@staticmethod retry_count = 0
def _generate_header( while retry_count < self.max_retries:
message_type=CLIENT_FULL_REQUEST, message_type_specific_flags=NO_SEQUENCE try:
) -> bytearray: # 确保关闭旧的连接
"""Generate protocol header.""" if self.asr_ws is not None:
header = bytearray() try:
header_size = 1 await self.asr_ws.close()
header.append((0b0001 << 4) | header_size) # Protocol version except Exception as e:
header.append((message_type << 4) | message_type_specific_flags) logger.bind(tag=TAG).warning(f"关闭旧连接时发生错误: {e}")
header.append((0b0001 << 4) | 0b0001) # JSON serialization & GZIP compression finally:
header.append(0x00) # reserved self.asr_ws = None
return header
def _construct_request(self, reqid) -> dict: headers = self.token_auth() if self.auth_method == "token" else None
"""Construct the request payload.""" self.asr_ws = await websockets.connect(
return { self.ws_url,
additional_headers=headers,
max_size=1000000000,
ping_interval=None, # 禁用ping,因为服务器可能不支持
ping_timeout=None,
close_timeout=10,
)
# 发送初始化请求
request_params = self.construct_request(str(uuid.uuid4()))
try:
payload_bytes = str.encode(json.dumps(request_params))
payload_bytes = gzip.compress(payload_bytes)
full_client_request = self.generate_header()
full_client_request.extend((len(payload_bytes)).to_bytes(4, "big"))
full_client_request.extend(payload_bytes)
await self.asr_ws.send(full_client_request)
logger.bind(tag=TAG).debug(f"发送初始化请求: {request_params}")
except Exception as e:
logger.bind(tag=TAG).error(f"发送初始化请求失败: {e}")
raise e
# 等待初始化响应
try:
init_res = await self.asr_ws.recv()
logger.bind(tag=TAG).debug(f"收到原始响应: {init_res}")
result = self.parse_response(init_res)
logger.bind(tag=TAG).info(f"ASR服务初始化响应: {result}")
except Exception as e:
logger.bind(tag=TAG).error(f"ASR服务初始化失败: {e}")
raise e
# 启动接收ASR结果的异步任务
asr_priority = threading.Thread(
target=self._start_monitor_asr_response_thread, daemon=True
)
asr_priority.start()
return
except websockets.exceptions.WebSocketException as e:
retry_count += 1
if retry_count < self.max_retries:
logger.bind(tag=TAG).warning(
f"WebSocket连接失败,正在进行第{retry_count}次重试: {e}"
)
await asyncio.sleep(self.retry_delay)
else:
logger.bind(tag=TAG).error(
f"WebSocket连接失败,已达到最大重试次数: {e}"
)
raise
except Exception as e:
logger.bind(tag=TAG).error(f"WebSocket连接发生未知错误: {e}")
raise
async def receive_audio(self, audio, _):
if not isinstance(audio, bytes):
return
try:
# 解码opus得到PCM数据
pcm_frame = self.decoder.decode(audio, 960)
payload = gzip.compress(pcm_frame)
audio_request = bytearray(self.generate_audio_default_header())
audio_request.extend(len(payload).to_bytes(4, "big"))
audio_request.extend(payload)
if self.asr_ws:
await self.asr_ws.send(audio_request)
except Exception as e:
logger.bind(tag=TAG).error(f"发送音频数据时发生错误: {e}")
###################################################################################
# 豆包流式ASR重写父类的方法--结束
###################################################################################
def construct_request(self, reqid):
req = {
"app": { "app": {
"appid": f"{self.appid}", "appid": self.appid,
"cluster": self.cluster, "cluster": self.cluster,
"token": self.access_token, "token": self.access_token,
}, },
"user": { "user": {"uid": self.uid},
"uid": str(uuid.uuid4()),
},
"request": { "request": {
"reqid": reqid, "reqid": reqid,
"show_utterances": False, "workflow": self.workflow,
"show_utterances": True,
"result_type": self.result_type,
"sequence": 1, "sequence": 1,
"boosting_table_name": self.boosting_table_name, "boosting_table_name": self.boosting_table_name,
"correct_table_name": self.correct_table_name, "correct_table_name": self.correct_table_name,
}, },
"audio": { "audio": {
"format": "raw", "format": self.format,
"rate": 16000, "codec": self.codec,
"language": "zh-CN", "rate": self.rate,
"bits": 16, "language": self.language,
"channel": 1, "bits": self.bits,
"codec": "raw", "channel": self.channel,
}, },
} }
return req
async def _send_request( def token_auth(self):
self, audio_data: List[bytes], segment_size: int return {"Authorization": f"Bearer; {self.access_token}"}
) -> Optional[str]:
"""Send request to Volcano ASR service.""" def generate_header(
self,
version=PROTOCOL_VERSION,
message_type=CLIENT_FULL_REQUEST,
message_type_specific_flags=NO_SEQUENCE,
serial_method=JSON_SERIALIZATION,
compression_type=GZIP_COMPRESSION,
reserved_data=0x00,
extension_header: bytes = b"",
):
"""
生成协议头:
- 第1字节:高4位:协议版本,低4位:头部大小(单位 4 字节)
- 第2字节:高4位:消息类型,低4位:消息类型特定标志
- 第3字节:高4位:序列化方式,低4位:压缩方式
- 第4字节:保留字段
- 后续:扩展头(如果有)
"""
header = bytearray()
header_size = int(len(extension_header) / 4) + 1
header.append((version << 4) | header_size)
header.append((message_type << 4) | message_type_specific_flags)
header.append((serial_method << 4) | compression_type)
header.append(reserved_data)
header.extend(extension_header)
return header
def generate_full_default_header(self):
# full client request 默认头
return self.generate_header(
version=PROTOCOL_VERSION,
message_type=CLIENT_FULL_REQUEST,
message_type_specific_flags=NO_SEQUENCE,
serial_method=JSON_SERIALIZATION,
compression_type=GZIP_COMPRESSION,
)
def generate_audio_default_header(self):
# 普通音频片段请求
return self.generate_header(
version=PROTOCOL_VERSION,
message_type=CLIENT_AUDIO_ONLY_REQUEST,
message_type_specific_flags=NO_SEQUENCE,
serial_method=JSON_SERIALIZATION,
compression_type=GZIP_COMPRESSION,
)
def generate_last_audio_default_header(self):
# 最后一个音频片段标志
return self.generate_header(
version=PROTOCOL_VERSION,
message_type=CLIENT_AUDIO_ONLY_REQUEST,
message_type_specific_flags=NEG_SEQUENCE, # 用 NEG_SEQUENCE 表示结束
serial_method=JSON_SERIALIZATION,
compression_type=GZIP_COMPRESSION,
)
def _start_monitor_asr_response_thread(self):
# 初始化链接
asyncio.run_coroutine_threadsafe(
self._forward_asr_results(), loop=self.conn.loop
)
async def _forward_asr_results(self):
try: try:
auth_header = {"Authorization": "Bearer; {}".format(self.access_token)} while not self.conn.stop_event.is_set():
async with websockets.connect( try:
self.ws_url, additional_headers=auth_header if self.asr_ws is None:
) as websocket: logger.bind(tag=TAG).info("尝试重新连接ASR服务...")
# Prepare request data await self.open_audio_channels(self.conn)
request_params = self._construct_request(str(uuid.uuid4())) continue
payload_bytes = str.encode(json.dumps(request_params))
payload_bytes = gzip.compress(payload_bytes)
full_client_request = self._generate_header()
full_client_request.extend(
(len(payload_bytes)).to_bytes(4, "big")
) # payload size(4 bytes)
full_client_request.extend(payload_bytes) # payload
# Send header and metadata # 使用锁来确保同一时间只有一个协程在接收数据
# full_client_request async with self.recv_lock:
await websocket.send(full_client_request) response = await self.asr_ws.recv()
res = await websocket.recv() result = self.parse_response(response)
result = parse_response(res) # 检查是否需要重连
if ( if result.get("need_reconnect", False):
"payload_msg" in result logger.bind(tag=TAG).info(
and result["payload_msg"]["code"] != self.success_code "检测到需要重连的错误,准备重新连接..."
):
logger.bind(tag=TAG).error(f"ASR error: {result}")
return None
for seq, (chunk, last) in enumerate(
self.slice_data(audio_data, segment_size), 1
):
if last:
audio_only_request = self._generate_header(
message_type=CLIENT_AUDIO_ONLY_REQUEST,
message_type_specific_flags=NEG_SEQUENCE,
) )
else: if self.asr_ws is not None:
audio_only_request = self._generate_header( try:
message_type=CLIENT_AUDIO_ONLY_REQUEST await self.asr_ws.close()
) except Exception as e:
payload_bytes = gzip.compress(chunk) logger.bind(tag=TAG).warning(
audio_only_request.extend( f"关闭旧连接时发生错误: {e}"
(len(payload_bytes)).to_bytes(4, "big") )
) # payload size(4 bytes) finally:
audio_only_request.extend(payload_bytes) # payload self.asr_ws = None
# Send audio data continue
await websocket.send(audio_only_request)
# Receive response if "payload_msg" in result:
response = await websocket.recv() if "result" in result["payload_msg"]:
result = parse_response(response) # 检查是否有utterances并且definite为True
utterances = result["payload_msg"]["result"][0].get(
if ( "utterances", []
"payload_msg" in result )
and result["payload_msg"]["code"] == self.success_code for utterance in utterances:
): if utterance.get("definite", False):
if len(result["payload_msg"]["result"]) > 0: self.text = utterance["text"]
return result["payload_msg"]["result"][0]["text"] await self.handle_voice_stop()
return None break
else: except websockets.ConnectionClosed:
logger.bind(tag=TAG).error(f"ASR error: {result}") logger.bind(tag=TAG).debug("ASR服务连接已关闭,准备重连...")
return None # 确保关闭旧连接
if self.asr_ws is not None:
try:
await self.asr_ws.close()
except Exception as e:
logger.bind(tag=TAG).warning(f"关闭旧连接时发生错误: {e}")
finally:
self.asr_ws = None
retry_count = 0
while (
retry_count < self.max_retries
and not self.conn.stop_event.is_set()
):
try:
logger.bind(tag=TAG).info(
f"正在进行第{retry_count + 1}次重连尝试..."
)
await self.open_audio_channels(self.conn)
break
except Exception as e:
retry_count += 1
if retry_count < self.max_retries:
logger.bind(tag=TAG).warning(
f"重连失败,等待{self.retry_delay}秒后重试: {e}"
)
await asyncio.sleep(self.retry_delay)
else:
logger.bind(tag=TAG).error(
f"重连失败,已达到最大重试次数: {e}"
)
await asyncio.sleep(
self.retry_delay
) # 继续等待,以便后续重试
except Exception as e:
logger.bind(tag=TAG).error(f"处理ASR结果时发生错误: {e}")
if not self.conn.stop_event.is_set():
await asyncio.sleep(2) # 增加重试延迟
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"ASR request failed: {e}", exc_info=True) logger.bind(tag=TAG).error(f"ASR监听线程发生错误: {e}")
return None # 确保在发生严重错误时也能继续尝试重连
if not self.conn.stop_event.is_set():
await asyncio.sleep(self.retry_delay)
await self._forward_asr_results() # 递归重试
@staticmethod async def speech_to_text(self, opus_data, session_id):
def slice_data(data: bytes, chunk_size: int) -> (list, bool): result = self.text
self.text = "" # 清空text
return result, None
def parse_response(self, res: bytes) -> dict:
""" """
slice data 解析 ASR 服务返回的二进制响应。
:param data: wav data 根据协议格式解析头部和 payload,若采用 GZIP 压缩则先解压,再根据 JSON 反序列化。
:param chunk_size: the segment size in one request
:return: segment data, last flag
""" """
data_len = len(data) protocol_version = res[0] >> 4
offset = 0 header_size = res[0] & 0x0F
while offset + chunk_size < data_len: message_type = res[1] >> 4
yield data[offset : offset + chunk_size], False serialization_method = res[2] >> 4
offset += chunk_size message_compression = res[2] & 0x0F
payload = res[header_size * 4 :]
result = {}
payload_msg = None
payload_size = 0
if message_type == SERVER_FULL_RESPONSE:
payload_size = int.from_bytes(payload[:4], "big", signed=True)
payload_msg = payload[4:]
elif message_type == SERVER_ACK:
seq = int.from_bytes(payload[:4], "big", signed=True)
result["seq"] = seq
if len(payload) >= 8:
payload_size = int.from_bytes(payload[4:8], "big", signed=False)
payload_msg = payload[8:]
elif message_type == SERVER_ERROR_RESPONSE:
code = int.from_bytes(payload[:4], "big", signed=False)
result["code"] = code
payload_size = int.from_bytes(payload[4:8], "big", signed=False)
payload_msg = payload[8:]
if payload_msg is None:
return result
if message_compression == GZIP_COMPRESSION:
payload_msg = gzip.decompress(payload_msg)
if serialization_method == JSON_SERIALIZATION:
payload_msg = json.loads(payload_msg.decode("utf-8"))
else: else:
yield data[offset:data_len], True payload_msg = payload_msg.decode("utf-8")
result["payload_msg"] = payload_msg
result["payload_size"] = payload_size
async def speech_to_text( # 错误码处理
self, opus_data: List[bytes], session_id: str if "code" in result:
) -> Tuple[Optional[str], Optional[str]]: error_code = result["code"]
"""将语音数据转换为文本""" error_message = ""
file_path = None if error_code == 1000:
try: error_message = "成功"
# 合并所有opus数据包 elif error_code == 1001:
if self.audio_format == "pcm": error_message = "请求参数无效:请求参数缺失必需字段/字段值无效/重复请求"
pcm_data = opus_data elif error_code == 1002:
error_message = "无访问权限:token无效/过期/无权访问指定服务"
elif error_code == 1003:
error_message = "访问超频:当前appid访问QPS超出设定阈值"
elif error_code == 1004:
error_message = "访问超额:当前appid访问次数超出限制"
elif error_code == 1005:
error_message = "服务器繁忙:服务过载,无法处理当前请求"
elif error_code == 1010:
error_message = "音频过长:音频数据时长超出阈值"
elif error_code == 1011:
error_message = "音频过大:音频数据大小超出阈值"
elif error_code == 1012:
error_message = "音频格式无效:音频header有误/无法进行音频解码"
elif error_code == 1013:
error_message = "音频静音:音频未识别出任何文本结果"
elif error_code >= 1020 and error_code <= 1022:
error_message = "识别相关错误:需要重连"
if error_code == 1020:
error_message = "识别等待超时:等待下一包就绪超时"
elif error_code == 1021:
error_message = "识别处理超时:识别处理过程超时"
elif error_code == 1022:
error_message = "识别错误:识别过程中发生错误"
else: else:
pcm_data = self.decode_opus(opus_data) error_message = "未知错误:未归类错误"
combined_pcm_data = b"".join(pcm_data)
# 判断是否保存为WAV文件 logger.bind(tag=TAG).debug(
if self.delete_audio_file: f"ASR错误: {error_message} (错误码: {error_code})"
pass )
else:
file_path = self.save_audio_to_file(pcm_data, session_id)
# 直接使用PCM数据 # 如果是识别相关错误(>=1020),标记需要重连
# 计算分段大小 (单声道, 16bit, 16kHz采样率) if error_code >= 1020:
size_per_sec = 1 * 2 * 16000 # nchannels * sampwidth * framerate result["need_reconnect"] = True
segment_size = int(size_per_sec * self.seg_duration / 1000)
# 语音识别 return result
start_time = time.time()
text = await self._send_request(combined_pcm_data, segment_size)
if text:
logger.bind(tag=TAG).debug(
f"语音识别耗时: {time.time() - start_time:.3f}s | 结果: {text}"
)
return text, file_path
return "", file_path
except Exception as e:
logger.bind(tag=TAG).error(f"语音识别失败: {e}", exc_info=True)
return "", file_path
@@ -0,0 +1,9 @@
from enum import Enum
from typing import Union, Optional
class InterfaceType(Enum):
# 接口类型
STREAM = "STREAM" # 流式接口
NON_STREAM = "NON_STREAM" # 非流式接口
LOCAL = "LOCAL" # 本地服务
@@ -1,15 +1,14 @@
import time import time
import wave
import os import os
import sys import sys
import io import io
from config.logger import setup_logging from config.logger import setup_logging
from typing import Optional, Tuple, List from typing import Optional, Tuple, List
import uuid
from core.providers.asr.base import ASRProviderBase from core.providers.asr.base import ASRProviderBase
from funasr import AutoModel from funasr import AutoModel
from funasr.utils.postprocess_utils import rich_transcription_postprocess from funasr.utils.postprocess_utils import rich_transcription_postprocess
import shutil import shutil
from core.providers.asr.dto.dto import InterfaceType
TAG = __name__ TAG = __name__
logger = setup_logging() logger = setup_logging()
@@ -38,6 +37,7 @@ class CaptureOutput:
class ASRProvider(ASRProviderBase): class ASRProvider(ASRProviderBase):
def __init__(self, config: dict, delete_audio_file: bool): def __init__(self, config: dict, delete_audio_file: bool):
super().__init__() super().__init__()
self.interface_type = InterfaceType.LOCAL
self.model_dir = config.get("model_dir") self.model_dir = config.get("model_dir")
self.output_dir = config.get("output_dir") # 修正配置键名 self.output_dir = config.get("output_dir") # 修正配置键名
self.delete_audio_file = delete_audio_file self.delete_audio_file = delete_audio_file
@@ -1,11 +1,8 @@
from typing import Optional, Tuple, List from typing import Optional, Tuple, List
import opuslib_next
from core.providers.asr.base import ASRProviderBase from core.providers.asr.base import ASRProviderBase
import os from core.providers.asr.dto.dto import InterfaceType
import ssl import ssl
import json import json
import uuid
import wave
import websockets import websockets
from config.logger import setup_logging from config.logger import setup_logging
import asyncio import asyncio
@@ -23,6 +20,7 @@ class ASRProvider(ASRProviderBase):
:param delete_audio_file: Boolean to indicate whether to delete audio files after processing. :param delete_audio_file: Boolean to indicate whether to delete audio files after processing.
""" """
super().__init__() super().__init__()
self.interface_type = InterfaceType.NON_STREAM
self.host = config.get("host", "localhost") self.host = config.get("host", "localhost")
self.port = config.get("port", 10095) self.port = config.get("port", 10095)
self.api_key = config.get("api_key", "none") self.api_key = config.get("api_key", "none")
@@ -5,8 +5,7 @@ import sys
import io import io
from config.logger import setup_logging from config.logger import setup_logging
from typing import Optional, Tuple, List from typing import Optional, Tuple, List
import uuid from core.providers.asr.dto.dto import InterfaceType
import opuslib_next
from core.providers.asr.base import ASRProviderBase from core.providers.asr.base import ASRProviderBase
import numpy as np import numpy as np
@@ -38,6 +37,7 @@ class CaptureOutput:
class ASRProvider(ASRProviderBase): class ASRProvider(ASRProviderBase):
def __init__(self, config: dict, delete_audio_file: bool): def __init__(self, config: dict, delete_audio_file: bool):
super().__init__() super().__init__()
self.interface_type = InterfaceType.LOCAL
self.model_dir = config.get("model_dir") self.model_dir = config.get("model_dir")
self.output_dir = config.get("output_dir") self.output_dir = config.get("output_dir")
self.delete_audio_file = delete_audio_file self.delete_audio_file = delete_audio_file
@@ -5,9 +5,8 @@ import json
import time import time
from datetime import datetime, timezone from datetime import datetime, timezone
import os import os
import uuid
from typing import Optional, Tuple, List from typing import Optional, Tuple, List
import wave from core.providers.asr.dto.dto import InterfaceType
import requests import requests
from core.providers.asr.base import ASRProviderBase from core.providers.asr.base import ASRProviderBase
from config.logger import setup_logging from config.logger import setup_logging
@@ -23,6 +22,7 @@ class ASRProvider(ASRProviderBase):
def __init__(self, config: dict, delete_audio_file: bool = True): def __init__(self, config: dict, delete_audio_file: bool = True):
super().__init__() super().__init__()
self.interface_type = InterfaceType.NON_STREAM
self.secret_id = config.get("secret_id") self.secret_id = config.get("secret_id")
self.secret_key = config.get("secret_key") self.secret_key = config.get("secret_key")
self.output_dir = config.get("output_dir") self.output_dir = config.get("output_dir")
@@ -0,0 +1,128 @@
from typing import Dict, Any
from config.logger import setup_logging
from core.utils import tts, llm, intent, memory, vad, asr
TAG = __name__
logger = setup_logging()
def initialize_modules(
logger,
config: Dict[str, Any],
init_vad=False,
init_asr=False,
init_llm=False,
init_tts=False,
init_memory=False,
init_intent=False,
) -> Dict[str, Any]:
"""
初始化所有模块组件
Args:
config: 配置字典
Returns:
Dict[str, Any]: 包含所有初始化后的模块的字典
"""
modules = {}
# 初始化TTS模块
if init_tts:
select_tts_module = config["selected_module"]["TTS"]
modules["tts"] = initialize_tts(config)
logger.bind(tag=TAG).info(f"初始化组件: tts成功 {select_tts_module}")
# 初始化LLM模块
if init_llm:
select_llm_module = config["selected_module"]["LLM"]
llm_type = (
select_llm_module
if "type" not in config["LLM"][select_llm_module]
else config["LLM"][select_llm_module]["type"]
)
modules["llm"] = llm.create_instance(
llm_type,
config["LLM"][select_llm_module],
)
logger.bind(tag=TAG).info(f"初始化组件: llm成功 {select_llm_module}")
# 初始化Intent模块
if init_intent:
select_intent_module = config["selected_module"]["Intent"]
intent_type = (
select_intent_module
if "type" not in config["Intent"][select_intent_module]
else config["Intent"][select_intent_module]["type"]
)
modules["intent"] = intent.create_instance(
intent_type,
config["Intent"][select_intent_module],
)
logger.bind(tag=TAG).info(f"初始化组件: intent成功 {select_intent_module}")
# 初始化Memory模块
if init_memory:
select_memory_module = config["selected_module"]["Memory"]
memory_type = (
select_memory_module
if "type" not in config["Memory"][select_memory_module]
else config["Memory"][select_memory_module]["type"]
)
modules["memory"] = memory.create_instance(
memory_type,
config["Memory"][select_memory_module],
config.get("summaryMemory", None),
)
logger.bind(tag=TAG).info(f"初始化组件: memory成功 {select_memory_module}")
# 初始化VAD模块
if init_vad:
select_vad_module = config["selected_module"]["VAD"]
vad_type = (
select_vad_module
if "type" not in config["VAD"][select_vad_module]
else config["VAD"][select_vad_module]["type"]
)
modules["vad"] = vad.create_instance(
vad_type,
config["VAD"][select_vad_module],
)
logger.bind(tag=TAG).info(f"初始化组件: vad成功 {select_vad_module}")
# 初始化ASR模块
if init_asr:
select_asr_module = config["selected_module"]["ASR"]
modules["asr"] = initialize_asr(config)
logger.bind(tag=TAG).info(f"初始化组件: asr成功 {select_asr_module}")
return modules
def initialize_tts(config):
select_tts_module = config["selected_module"]["TTS"]
tts_type = (
select_tts_module
if "type" not in config["TTS"][select_tts_module]
else config["TTS"][select_tts_module]["type"]
)
new_tts = tts.create_instance(
tts_type,
config["TTS"][select_tts_module],
str(config.get("delete_audio", True)).lower() in ("true", "1", "yes"),
)
return new_tts
def initialize_asr(config):
select_asr_module = config["selected_module"]["ASR"]
asr_type = (
select_asr_module
if "type" not in config["ASR"][select_asr_module]
else config["ASR"][select_asr_module]["type"]
)
new_asr = asr.create_instance(
asr_type,
config["ASR"][select_asr_module],
str(config.get("delete_audio", True)).lower() in ("true", "1", "yes"),
)
return new_asr
-118
View File
@@ -7,8 +7,6 @@ import numpy as np
import requests import requests
import opuslib_next import opuslib_next
from pydub import AudioSegment from pydub import AudioSegment
from typing import Dict, Any
from core.utils import tts, llm, intent, memory, vad, asr
import copy import copy
TAG = __name__ TAG = __name__
@@ -245,122 +243,6 @@ def extract_json_from_string(input_string):
return None return None
def initialize_modules(
logger,
config: Dict[str, Any],
init_vad=False,
init_asr=False,
init_llm=False,
init_tts=False,
init_memory=False,
init_intent=False,
) -> Dict[str, Any]:
"""
初始化所有模块组件
Args:
config: 配置字典
Returns:
Dict[str, Any]: 包含所有初始化后的模块的字典
"""
modules = {}
# 初始化TTS模块
if init_tts:
select_tts_module = config["selected_module"]["TTS"]
modules["tts"] = initialize_tts(config)
logger.bind(tag=TAG).info(f"初始化组件: tts成功 {select_tts_module}")
# 初始化LLM模块
if init_llm:
select_llm_module = config["selected_module"]["LLM"]
llm_type = (
select_llm_module
if "type" not in config["LLM"][select_llm_module]
else config["LLM"][select_llm_module]["type"]
)
modules["llm"] = llm.create_instance(
llm_type,
config["LLM"][select_llm_module],
)
logger.bind(tag=TAG).info(f"初始化组件: llm成功 {select_llm_module}")
# 初始化Intent模块
if init_intent:
select_intent_module = config["selected_module"]["Intent"]
intent_type = (
select_intent_module
if "type" not in config["Intent"][select_intent_module]
else config["Intent"][select_intent_module]["type"]
)
modules["intent"] = intent.create_instance(
intent_type,
config["Intent"][select_intent_module],
)
logger.bind(tag=TAG).info(f"初始化组件: intent成功 {select_intent_module}")
# 初始化Memory模块
if init_memory:
select_memory_module = config["selected_module"]["Memory"]
memory_type = (
select_memory_module
if "type" not in config["Memory"][select_memory_module]
else config["Memory"][select_memory_module]["type"]
)
modules["memory"] = memory.create_instance(
memory_type,
config["Memory"][select_memory_module],
config.get("summaryMemory", None),
)
logger.bind(tag=TAG).info(f"初始化组件: memory成功 {select_memory_module}")
# 初始化VAD模块
if init_vad:
select_vad_module = config["selected_module"]["VAD"]
vad_type = (
select_vad_module
if "type" not in config["VAD"][select_vad_module]
else config["VAD"][select_vad_module]["type"]
)
modules["vad"] = vad.create_instance(
vad_type,
config["VAD"][select_vad_module],
)
logger.bind(tag=TAG).info(f"初始化组件: vad成功 {select_vad_module}")
# 初始化ASR模块
if init_asr:
select_asr_module = config["selected_module"]["ASR"]
asr_type = (
select_asr_module
if "type" not in config["ASR"][select_asr_module]
else config["ASR"][select_asr_module]["type"]
)
modules["asr"] = asr.create_instance(
asr_type,
config["ASR"][select_asr_module],
str(config.get("delete_audio", True)).lower() in ("true", "1", "yes"),
)
logger.bind(tag=TAG).info(f"初始化组件: asr成功 {select_asr_module}")
return modules
def initialize_tts(config):
select_tts_module = config["selected_module"]["TTS"]
tts_type = (
select_tts_module
if "type" not in config["TTS"][select_tts_module]
else config["TTS"][select_tts_module]["type"]
)
new_tts = tts.create_instance(
tts_type,
config["TTS"][select_tts_module],
str(config.get("delete_audio", True)).lower() in ("true", "1", "yes"),
)
return new_tts
def analyze_emotion(text): def analyze_emotion(text):
""" """
分析文本情感并返回对应的emoji名称(支持中英文) 分析文本情感并返回对应的emoji名称(支持中英文)
+2 -1
View File
@@ -2,8 +2,9 @@ import asyncio
import websockets import websockets
from config.logger import setup_logging from config.logger import setup_logging
from core.connection import ConnectionHandler from core.connection import ConnectionHandler
from core.utils.util import initialize_modules, check_vad_update, check_asr_update
from config.config_loader import get_config_from_api from config.config_loader import get_config_from_api
from core.utils.modules_initialize import initialize_modules
from core.utils.util import check_vad_update, check_asr_update
TAG = __name__ TAG = __name__