mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-29 08:03:55 +08:00
Merge pull request #1988 from myifeng/async-handler
调整OPUS函数式处理,不再以数组方式收集再遍历
This commit is contained in:
@@ -268,11 +268,7 @@ class TTSProvider(TTSProviderBase):
|
||||
)
|
||||
if message.content_file and os.path.exists(message.content_file):
|
||||
# 先处理文件音频数据
|
||||
file_audio = self._process_audio_file(message.content_file)
|
||||
self.before_stop_play_files.append(
|
||||
(file_audio, message.content_detail)
|
||||
)
|
||||
|
||||
self._process_audio_file_stream(message.content_file, callback=lambda audio_data: self.handle_audio_file(audio_data, message.content_detail))
|
||||
if message.sentence_type == SentenceType.LAST:
|
||||
try:
|
||||
logger.bind(tag=TAG).info("开始结束TTS会话...")
|
||||
@@ -422,7 +418,6 @@ class TTSProvider(TTSProviderBase):
|
||||
|
||||
async def _start_monitor_tts_response(self):
|
||||
"""监听TTS响应"""
|
||||
opus_datas_cache = []
|
||||
is_first_sentence = True
|
||||
first_sentence_segment_count = 0 # 添加计数器
|
||||
try:
|
||||
@@ -448,25 +443,15 @@ class TTSProvider(TTSProviderBase):
|
||||
elif event_name == "SentenceBegin":
|
||||
opus_datas_cache = []
|
||||
elif event_name == "SentenceEnd":
|
||||
if (
|
||||
not is_first_sentence
|
||||
or first_sentence_segment_count > 10
|
||||
):
|
||||
# 发送缓存的数据
|
||||
if self.conn.tts_MessageText:
|
||||
logger.bind(tag=TAG).info(
|
||||
f"句子语音生成成功: {self.conn.tts_MessageText}"
|
||||
)
|
||||
self.tts_audio_queue.put(
|
||||
(SentenceType.MIDDLE, opus_datas_cache, self.conn.tts_MessageText)
|
||||
)
|
||||
self.conn.tts_MessageText = None
|
||||
else:
|
||||
self.tts_audio_queue.put(
|
||||
(SentenceType.MIDDLE, opus_datas_cache, None)
|
||||
)
|
||||
# 第一句话结束后,将标志设置为False
|
||||
is_first_sentence = False
|
||||
# 发送缓存的数据
|
||||
if self.conn.tts_MessageText:
|
||||
logger.bind(tag=TAG).info(
|
||||
f"句子语音生成成功: {self.conn.tts_MessageText}"
|
||||
)
|
||||
self.tts_audio_queue.put(
|
||||
(SentenceType.FIRST, [], self.conn.tts_MessageText)
|
||||
)
|
||||
self.conn.tts_MessageText = None
|
||||
elif event_name == "SynthesisCompleted":
|
||||
logger.bind(tag=TAG).debug(f"会话结束~~")
|
||||
self._process_before_stop_play_files()
|
||||
@@ -477,22 +462,7 @@ class TTSProvider(TTSProviderBase):
|
||||
# 二进制消息(音频数据)
|
||||
elif isinstance(msg, (bytes, bytearray)):
|
||||
logger.bind(tag=TAG).debug(f"推送数据到队列里面~~")
|
||||
opus_datas = self.opus_encoder.encode_pcm_to_opus(msg, False)
|
||||
logger.bind(tag=TAG).debug(
|
||||
f"推送数据到队列里面帧数~~{len(opus_datas)}"
|
||||
)
|
||||
if is_first_sentence:
|
||||
first_sentence_segment_count += 1
|
||||
if first_sentence_segment_count <= 6:
|
||||
self.tts_audio_queue.put(
|
||||
(SentenceType.MIDDLE, opus_datas, None)
|
||||
)
|
||||
else:
|
||||
opus_datas_cache.extend(opus_datas)
|
||||
else:
|
||||
# 后续句子缓存
|
||||
opus_datas_cache.extend(opus_datas)
|
||||
|
||||
self.opus_encoder.encode_pcm_to_opus_stream(msg, False, self.handle_opus)
|
||||
except websockets.ConnectionClosed:
|
||||
logger.bind(tag=TAG).warning("WebSocket连接已关闭")
|
||||
break
|
||||
@@ -512,8 +482,10 @@ class TTSProvider(TTSProviderBase):
|
||||
finally:
|
||||
self._monitor_task = None
|
||||
|
||||
def to_tts(self, text: str) -> list:
|
||||
def to_tts_stream(self, text: str, opus_handler=None) -> None:
|
||||
"""非流式TTS处理,用于测试及保存音频文件的场景"""
|
||||
if opus_handler is None:
|
||||
opus_handler = self.handle_opus
|
||||
try:
|
||||
# 创建新的事件循环
|
||||
loop = asyncio.new_event_loop()
|
||||
@@ -616,10 +588,10 @@ class TTSProvider(TTSProviderBase):
|
||||
msg = await ws.recv()
|
||||
if isinstance(msg, (bytes, bytearray)):
|
||||
# 编码为Opus并收集
|
||||
opus_frames = self.opus_encoder.encode_pcm_to_opus(
|
||||
msg, False
|
||||
self.opus_encoder.encode_pcm_to_opus_stream(
|
||||
msg, False, opus_handler
|
||||
)
|
||||
audio_data.extend(opus_frames)
|
||||
# audio_data.extend(opus_frames)
|
||||
elif isinstance(msg, str):
|
||||
data = json.loads(msg)
|
||||
header = data.get("header", {})
|
||||
@@ -647,7 +619,5 @@ class TTSProvider(TTSProviderBase):
|
||||
loop.run_until_complete(_generate_audio())
|
||||
loop.close()
|
||||
|
||||
return audio_data
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"生成音频数据失败: {str(e)}")
|
||||
return []
|
||||
|
||||
@@ -4,12 +4,15 @@ import queue
|
||||
import uuid
|
||||
import asyncio
|
||||
import threading
|
||||
from typing import Callable, Any
|
||||
|
||||
from core.utils import p3
|
||||
from datetime import datetime
|
||||
import time
|
||||
from core.utils import textUtils
|
||||
from abc import ABC, abstractmethod
|
||||
from config.logger import setup_logging
|
||||
from core.utils.util import audio_to_data, audio_bytes_to_data
|
||||
from core.utils.audio_flow_control import FlowControlConfig, simulate_device_consumption
|
||||
from core.utils.util import audio_to_data, audio_bytes_to_data, audio_bytes_to_data_stream, audio_to_data_stream
|
||||
from core.utils.tts import MarkdownCleaner
|
||||
from core.utils.output_counter import add_device_output
|
||||
from core.handle.reportHandle import enqueue_tts_report
|
||||
@@ -50,11 +53,9 @@ class TTSProviderBase(ABC):
|
||||
";",
|
||||
";",
|
||||
":",
|
||||
"~",
|
||||
)
|
||||
self.first_sentence_punctuations = (
|
||||
",",
|
||||
"~",
|
||||
"~",
|
||||
"、",
|
||||
",",
|
||||
@@ -70,6 +71,8 @@ class TTSProviderBase(ABC):
|
||||
self.tts_stop_request = False
|
||||
self.processed_chars = 0
|
||||
self.is_first_sentence = True
|
||||
self.flow_controller = FlowControlConfig.create_flow_controller()
|
||||
self.flow_control_enabled = config.get("enable_flow_control", True)
|
||||
|
||||
def generate_filename(self, extension=".wav"):
|
||||
return os.path.join(
|
||||
@@ -77,7 +80,20 @@ class TTSProviderBase(ABC):
|
||||
f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
|
||||
)
|
||||
|
||||
def to_tts(self, text):
|
||||
def handle_opus(self, opus_data: bytes):
|
||||
logger.bind(tag=TAG).debug(
|
||||
f"推送数据到队列里面帧数~~ {len(opus_data)}"
|
||||
)
|
||||
self.tts_audio_queue.put(
|
||||
(SentenceType.MIDDLE, opus_data, None)
|
||||
)
|
||||
|
||||
def handle_audio_file(self, file_audio: bytes, text):
|
||||
self.before_stop_play_files.append(
|
||||
(file_audio, text)
|
||||
)
|
||||
|
||||
def to_tts_stream(self, text, opus_handler=handle_opus) -> None:
|
||||
text = MarkdownCleaner.clean_markdown(text)
|
||||
max_repeat_time = 5
|
||||
if self.delete_audio_file:
|
||||
@@ -86,10 +102,13 @@ class TTSProviderBase(ABC):
|
||||
try:
|
||||
audio_bytes = asyncio.run(self.text_to_speak(text, None))
|
||||
if audio_bytes:
|
||||
audio_datas, _ = audio_bytes_to_data(
|
||||
audio_bytes, file_type=self.audio_file_type, is_opus=True
|
||||
self.tts_audio_queue.put(
|
||||
(SentenceType.FIRST, None, text)
|
||||
)
|
||||
return audio_datas
|
||||
audio_bytes_to_data_stream(
|
||||
audio_bytes, file_type=self.audio_file_type, is_opus=True, callback=opus_handler
|
||||
)
|
||||
max_repeat_time = 0
|
||||
else:
|
||||
max_repeat_time -= 1
|
||||
except Exception as e:
|
||||
@@ -129,8 +148,10 @@ class TTSProviderBase(ABC):
|
||||
logger.bind(tag=TAG).error(
|
||||
f"语音生成失败: {text},请检查网络或服务是否正常"
|
||||
)
|
||||
|
||||
return tmp_file
|
||||
self.tts_audio_queue.put(
|
||||
(SentenceType.FIRST, None, text)
|
||||
)
|
||||
self._process_audio_file_stream(tmp_file, callback=opus_handler)
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"Failed to generate TTS file: {e}")
|
||||
return None
|
||||
@@ -139,13 +160,13 @@ class TTSProviderBase(ABC):
|
||||
async def text_to_speak(self, text, output_file):
|
||||
pass
|
||||
|
||||
def audio_to_pcm_data(self, audio_file_path):
|
||||
def audio_to_pcm_data_stream(self, audio_file_path, callback: Callable[[Any], Any]=None):
|
||||
"""音频文件转换为PCM编码"""
|
||||
return audio_to_data(audio_file_path, is_opus=False)
|
||||
return audio_to_data_stream(audio_file_path, is_opus=False, callback=callback)
|
||||
|
||||
def audio_to_opus_data(self, audio_file_path):
|
||||
def audio_to_opus_data_stream(self, audio_file_path, callback: Callable[[Any], Any]=None):
|
||||
"""音频文件转换为Opus编码"""
|
||||
return audio_to_data(audio_file_path, is_opus=True)
|
||||
return audio_to_data_stream(audio_file_path, is_opus=True, callback=callback)
|
||||
|
||||
def tts_one_sentence(
|
||||
self,
|
||||
@@ -212,30 +233,14 @@ class TTSProviderBase(ABC):
|
||||
self.tts_text_buff.append(message.content_detail)
|
||||
segment_text = self._get_segment_text()
|
||||
if segment_text:
|
||||
if self.delete_audio_file:
|
||||
audio_datas = self.to_tts(segment_text)
|
||||
if audio_datas:
|
||||
self.tts_audio_queue.put(
|
||||
(message.sentence_type, audio_datas, segment_text)
|
||||
)
|
||||
else:
|
||||
tts_file = self.to_tts(segment_text)
|
||||
if tts_file:
|
||||
audio_datas = self._process_audio_file(tts_file)
|
||||
self.tts_audio_queue.put(
|
||||
(message.sentence_type, audio_datas, segment_text)
|
||||
)
|
||||
self.to_tts_stream(segment_text, opus_handler=self.handle_opus)
|
||||
elif ContentType.FILE == message.content_type:
|
||||
self._process_remaining_text()
|
||||
self._process_remaining_text_stream(opus_handler=self.handle_opus)
|
||||
tts_file = message.content_file
|
||||
if tts_file and os.path.exists(tts_file):
|
||||
audio_datas = self._process_audio_file(tts_file)
|
||||
self.tts_audio_queue.put(
|
||||
(message.sentence_type, audio_datas, message.content_detail)
|
||||
)
|
||||
|
||||
self._process_audio_file_stream(tts_file, callback=self.handle_opus)
|
||||
if message.sentence_type == SentenceType.LAST:
|
||||
self._process_remaining_text()
|
||||
self._process_remaining_text_stream(opus_handler=self.handle_opus)
|
||||
self.tts_audio_queue.put(
|
||||
(message.sentence_type, [], message.content_detail)
|
||||
)
|
||||
@@ -253,26 +258,122 @@ class TTSProviderBase(ABC):
|
||||
text = None
|
||||
try:
|
||||
try:
|
||||
sentence_type, audio_datas, text = self.tts_audio_queue.get(
|
||||
timeout=1
|
||||
)
|
||||
sentence_type, audio_datas, text = self.tts_audio_queue.get(timeout=0.1)
|
||||
except queue.Empty:
|
||||
if self.conn.stop_event.is_set():
|
||||
break
|
||||
continue
|
||||
future = asyncio.run_coroutine_threadsafe(
|
||||
sendAudioMessage(self.conn, sentence_type, audio_datas, text),
|
||||
self.conn.loop,
|
||||
)
|
||||
future.result()
|
||||
if self.conn.max_output_size > 0 and text:
|
||||
add_device_output(self.conn.headers.get("device-id"), len(text))
|
||||
enqueue_tts_report(self.conn, text, audio_datas)
|
||||
|
||||
# 如果启用了流控
|
||||
if self.flow_control_enabled:
|
||||
# 计算音频数据的帧数
|
||||
if isinstance(audio_datas, bytes):
|
||||
frame_count = 1 # 单个字节流作为一帧
|
||||
elif isinstance(audio_datas, (list, tuple)):
|
||||
frame_count = len(audio_datas)
|
||||
else:
|
||||
frame_count = 0
|
||||
|
||||
# 流控检查
|
||||
if frame_count > 0:
|
||||
max_wait_time = FlowControlConfig.DEFAULT_MAX_WAIT_TIME
|
||||
wait_start_time = time.time()
|
||||
retry_interval = FlowControlConfig.DEFAULT_RETRY_INTERVAL
|
||||
|
||||
while not self.flow_controller.can_send_frames(frame_count):
|
||||
# 检查是否超时或需要停止
|
||||
if (time.time() - wait_start_time > max_wait_time or
|
||||
self.conn.stop_event.is_set() or
|
||||
self.conn.client_abort):
|
||||
logger.bind(tag=TAG).warning(
|
||||
f"流控等待超时或收到停止信号,跳过音频发送: {text}"
|
||||
)
|
||||
break
|
||||
|
||||
# 短暂等待后重试
|
||||
time.sleep(retry_interval)
|
||||
|
||||
# status = self.flow_controller.get_status()
|
||||
# logger.bind(tag=TAG).debug(
|
||||
# f"流控状态: 缓冲区使用率={status['buffer_usage_percent']:.1f}%, "
|
||||
# f"可用令牌={status['available_tokens']}..."
|
||||
# f"发送帧数={status['sent_frames']}..."
|
||||
# f"消费帧数={status['consumed_frames']}..."
|
||||
# f"代播放帧数={status['sent_frames'] - status['consumed_frames']}..."
|
||||
# )
|
||||
else:
|
||||
# 可以发送,记录发送的帧数
|
||||
self.flow_controller.record_sent_frames(frame_count)
|
||||
|
||||
# 发送音频
|
||||
future = asyncio.run_coroutine_threadsafe(
|
||||
self._send_audio_with_flow_control(sentence_type, audio_datas, text),
|
||||
self.conn.loop,
|
||||
)
|
||||
future.result()
|
||||
|
||||
# 记录输出和报告
|
||||
if self.conn.max_output_size > 0 and text:
|
||||
add_device_output(self.conn.headers.get("device-id"), len(text))
|
||||
enqueue_tts_report(self.conn, text, audio_datas)
|
||||
|
||||
# 输出流控状态(调试用)
|
||||
# if frame_count > 0: # 只在较大的音频块时输出状态
|
||||
# status = self.flow_controller.get_status()
|
||||
# logger.bind(tag=TAG).debug(
|
||||
# f"流控状态: 缓冲区使用率={status['buffer_usage_percent']:.1f}%, "
|
||||
# f"可用令牌={status['available_tokens']}..."
|
||||
# f"发送帧数={status['sent_frames']}..."
|
||||
# f"消费帧数={status['consumed_frames']}..."
|
||||
# f"代播放帧数={status['sent_frames'] - status['consumed_frames']}..."
|
||||
# )
|
||||
else:
|
||||
# 没有音频数据,直接发送
|
||||
future = asyncio.run_coroutine_threadsafe(
|
||||
self._send_audio_with_flow_control(sentence_type, audio_datas, text),
|
||||
self.conn.loop,
|
||||
)
|
||||
future.result()
|
||||
else:
|
||||
# 未启用流控,直接发送
|
||||
future = asyncio.run_coroutine_threadsafe(
|
||||
sendAudioMessage(self.conn, sentence_type, audio_datas, text),
|
||||
self.conn.loop,
|
||||
)
|
||||
future.result()
|
||||
|
||||
# 记录输出和报告
|
||||
if self.conn.max_output_size > 0 and text:
|
||||
add_device_output(self.conn.headers.get("device-id"), len(text))
|
||||
enqueue_tts_report(self.conn, text, audio_datas)
|
||||
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(
|
||||
f"audio_play_priority priority_thread: {text} {e}"
|
||||
f"audio_play_priority_thread: {text} {e}"
|
||||
)
|
||||
|
||||
async def _send_audio_with_flow_control(self, sentence_type, audio_datas, text):
|
||||
"""带流控的音频发送方法"""
|
||||
await sendAudioMessage(self.conn, sentence_type, audio_datas, text)
|
||||
|
||||
# 模拟设备消费(实际应用中应该从设备获取反馈)
|
||||
if isinstance(audio_datas, bytes):
|
||||
frame_count = 1
|
||||
elif isinstance(audio_datas, (list, tuple)):
|
||||
frame_count = len(audio_datas)
|
||||
else:
|
||||
frame_count = 0
|
||||
|
||||
if frame_count > 0:
|
||||
asyncio.create_task(simulate_device_consumption(self.flow_controller, frame_count))
|
||||
|
||||
# 在类中添加流控制器重置方法
|
||||
def reset_flow_controller(self):
|
||||
"""重置流控制器状态,通常在新会话开始时调用"""
|
||||
if hasattr(self, 'flow_controller'):
|
||||
self.flow_controller.reset()
|
||||
logger.bind(tag=TAG).info("流控制器状态已重置")
|
||||
|
||||
async def start_session(self, session_id):
|
||||
pass
|
||||
|
||||
@@ -323,22 +424,19 @@ class TTSProviderBase(ABC):
|
||||
else:
|
||||
return None
|
||||
|
||||
def _process_audio_file(self, tts_file):
|
||||
def _process_audio_file_stream(self, tts_file, callback: Callable[[Any], Any]) -> None:
|
||||
"""处理音频文件并转换为指定格式
|
||||
|
||||
Args:
|
||||
tts_file: 音频文件路径
|
||||
content_detail: 内容详情
|
||||
|
||||
Returns:
|
||||
tuple: (sentence_type, audio_datas, content_detail)
|
||||
callback: 文件处理函数
|
||||
"""
|
||||
if tts_file.endswith(".p3"):
|
||||
audio_datas, _ = p3.decode_opus_from_file(tts_file)
|
||||
p3.decode_opus_from_file_stream(tts_file, callback=callback)
|
||||
elif self.conn.audio_format == "pcm":
|
||||
audio_datas, _ = self.audio_to_pcm_data(tts_file)
|
||||
self.audio_to_pcm_data_stream(tts_file, callback=callback)
|
||||
else:
|
||||
audio_datas, _ = self.audio_to_opus_data(tts_file)
|
||||
self.audio_to_opus_data_stream(tts_file, callback=callback)
|
||||
|
||||
if (
|
||||
self.delete_audio_file
|
||||
@@ -347,7 +445,6 @@ class TTSProviderBase(ABC):
|
||||
and tts_file.startswith(self.output_file)
|
||||
):
|
||||
os.remove(tts_file)
|
||||
return audio_datas
|
||||
|
||||
def _process_before_stop_play_files(self):
|
||||
for audio_datas, text in self.before_stop_play_files:
|
||||
@@ -355,7 +452,7 @@ class TTSProviderBase(ABC):
|
||||
self.before_stop_play_files.clear()
|
||||
self.tts_audio_queue.put((SentenceType.LAST, [], None))
|
||||
|
||||
def _process_remaining_text(self):
|
||||
def _process_remaining_text_stream(self, opus_handler=handle_opus):
|
||||
"""处理剩余的文本并生成语音
|
||||
|
||||
Returns:
|
||||
@@ -366,18 +463,7 @@ class TTSProviderBase(ABC):
|
||||
if remaining_text:
|
||||
segment_text = textUtils.get_string_no_punctuation_or_emoji(remaining_text)
|
||||
if segment_text:
|
||||
if self.delete_audio_file:
|
||||
audio_datas = self.to_tts(segment_text)
|
||||
if audio_datas:
|
||||
self.tts_audio_queue.put(
|
||||
(SentenceType.MIDDLE, audio_datas, segment_text)
|
||||
)
|
||||
else:
|
||||
tts_file = self.to_tts(segment_text)
|
||||
audio_datas = self._process_audio_file(tts_file)
|
||||
self.tts_audio_queue.put(
|
||||
(SentenceType.MIDDLE, audio_datas, segment_text)
|
||||
)
|
||||
self.to_tts_stream(segment_text, opus_handler=opus_handler)
|
||||
self.processed_chars += len(full_text)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -4,6 +4,8 @@ import json
|
||||
import queue
|
||||
import asyncio
|
||||
import traceback
|
||||
from typing import Callable, Any
|
||||
|
||||
import websockets
|
||||
from core.utils.tts import MarkdownCleaner
|
||||
from config.logger import setup_logging
|
||||
@@ -266,11 +268,7 @@ class TTSProvider(TTSProviderBase):
|
||||
)
|
||||
if message.content_file and os.path.exists(message.content_file):
|
||||
# 先处理文件音频数据
|
||||
file_audio = self._process_audio_file(message.content_file)
|
||||
self.before_stop_play_files.append(
|
||||
(file_audio, message.content_detail)
|
||||
)
|
||||
|
||||
self._process_audio_file_stream(message.content_file, callback=lambda audio_data: self.handle_audio_file(audio_data, message.content_detail))
|
||||
if message.sentence_type == SentenceType.LAST:
|
||||
try:
|
||||
logger.bind(tag=TAG).info("开始结束TTS会话...")
|
||||
@@ -458,21 +456,7 @@ class TTSProvider(TTSProviderBase):
|
||||
and res.header.message_type == AUDIO_ONLY_RESPONSE
|
||||
):
|
||||
logger.bind(tag=TAG).debug(f"推送数据到队列里面~~")
|
||||
opus_datas = self.wav_to_opus_data_audio_raw(res.payload)
|
||||
logger.bind(tag=TAG).debug(
|
||||
f"推送数据到队列里面帧数~~{len(opus_datas)}"
|
||||
)
|
||||
if is_first_sentence:
|
||||
first_sentence_segment_count += 1
|
||||
if first_sentence_segment_count <= 6:
|
||||
self.tts_audio_queue.put(
|
||||
(SentenceType.MIDDLE, opus_datas, None)
|
||||
)
|
||||
else:
|
||||
opus_datas_cache.extend(opus_datas)
|
||||
else:
|
||||
# 后续句子缓存
|
||||
opus_datas_cache.extend(opus_datas)
|
||||
self.wav_to_opus_data_audio_raw_stream(res.payload, callback=self.handle_opus)
|
||||
elif res.optional.event == EVENT_TTSSentenceEnd:
|
||||
logger.bind(tag=TAG).info(f"句子语音生成成功:{self.tts_text}")
|
||||
if not is_first_sentence or first_sentence_segment_count > 10:
|
||||
@@ -655,19 +639,21 @@ class TTSProvider(TTSProviderBase):
|
||||
)
|
||||
)
|
||||
|
||||
def wav_to_opus_data_audio_raw(self, raw_data_var, is_end=False):
|
||||
opus_datas = self.opus_encoder.encode_pcm_to_opus(raw_data_var, is_end)
|
||||
return opus_datas
|
||||
def wav_to_opus_data_audio_raw_stream(self, raw_data_var, is_end=False, callback: Callable[[Any], Any]=None):
|
||||
return self.opus_encoder.encode_pcm_to_opus_stream(raw_data_var, is_end, callback=callback)
|
||||
|
||||
def to_tts(self, text: str) -> list:
|
||||
def to_tts_stream(self, text: str, opus_handler=None) -> None:
|
||||
"""非流式生成音频数据,用于生成音频及测试场景
|
||||
|
||||
Args:
|
||||
text: 要转换的文本
|
||||
opus_handler: opus数据处理方法
|
||||
|
||||
Returns:
|
||||
list: 音频数据列表
|
||||
"""
|
||||
if opus_handler is None:
|
||||
opus_handler = self.handle_opus
|
||||
try:
|
||||
# 创建事件循环
|
||||
loop = asyncio.new_event_loop()
|
||||
@@ -676,9 +662,6 @@ class TTSProvider(TTSProviderBase):
|
||||
# 生成会话ID
|
||||
session_id = uuid.uuid4().__str__().replace("-", "")
|
||||
|
||||
# 存储音频数据
|
||||
audio_data = []
|
||||
|
||||
async def _generate_audio():
|
||||
# 创建新的WebSocket连接
|
||||
ws_header = {
|
||||
@@ -741,8 +724,7 @@ class TTSProvider(TTSProviderBase):
|
||||
res.optional.event == EVENT_TTSResponse
|
||||
and res.header.message_type == AUDIO_ONLY_RESPONSE
|
||||
):
|
||||
opus_datas = self.wav_to_opus_data_audio_raw(res.payload)
|
||||
audio_data.extend(opus_datas)
|
||||
self.wav_to_opus_data_audio_raw_stream(res.payload, callback=opus_handler)
|
||||
elif res.optional.event == EVENT_SessionFinished:
|
||||
break
|
||||
|
||||
@@ -757,8 +739,6 @@ class TTSProvider(TTSProviderBase):
|
||||
loop.run_until_complete(_generate_audio())
|
||||
loop.close()
|
||||
|
||||
return audio_data
|
||||
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"生成音频数据失败: {str(e)}")
|
||||
return []
|
||||
|
||||
@@ -65,14 +65,10 @@ class TTSProvider(TTSProviderBase):
|
||||
)
|
||||
if message.content_file and os.path.exists(message.content_file):
|
||||
# 先处理文件音频数据
|
||||
file_audio = self._process_audio_file(message.content_file)
|
||||
self.before_stop_play_files.append(
|
||||
(file_audio, message.content_detail)
|
||||
)
|
||||
|
||||
self._process_audio_file_stream(message.content_file, callback=lambda audio_data: self.handle_audio_file(audio_data, message.content_detail))
|
||||
if message.sentence_type == SentenceType.LAST:
|
||||
# 处理剩余的文本
|
||||
self._process_remaining_text(True)
|
||||
self._process_remaining_text_stream(True)
|
||||
|
||||
except queue.Empty:
|
||||
continue
|
||||
@@ -81,7 +77,7 @@ class TTSProvider(TTSProviderBase):
|
||||
f"处理TTS文本失败: {str(e)}, 类型: {type(e).__name__}, 堆栈: {traceback.format_exc()}"
|
||||
)
|
||||
|
||||
def _process_remaining_text(self, is_last=False):
|
||||
def _process_remaining_text_stream(self, is_last=False):
|
||||
"""处理剩余的文本并生成语音
|
||||
|
||||
Returns:
|
||||
@@ -237,15 +233,15 @@ class TTSProvider(TTSProviderBase):
|
||||
logger.bind(tag=TAG).error(f"TTS请求异常: {e}")
|
||||
self.tts_audio_queue.put((SentenceType.LAST, [], None))
|
||||
|
||||
def to_tts(self, text: str) -> list:
|
||||
def to_tts_stream(self, text: str, opus_handler=None) -> list:
|
||||
"""非流式TTS处理,用于测试及保存音频文件的场景
|
||||
|
||||
Args:
|
||||
text: 要转换的文本
|
||||
|
||||
Returns:
|
||||
list: 返回opus编码后的音频数据列表
|
||||
opus_handler: opus数据处理方法
|
||||
"""
|
||||
if opus_handler is None:
|
||||
opus_handler = self.handle_opus
|
||||
start_time = time.time()
|
||||
text = MarkdownCleaner.clean_markdown(text)
|
||||
|
||||
@@ -295,14 +291,9 @@ class TTSProvider(TTSProviderBase):
|
||||
# 最后一帧可能不足,用0填充
|
||||
frame = frame + b"\x00" * (frame_bytes - len(frame))
|
||||
|
||||
opus = self.opus_encoder.encode_pcm_to_opus(
|
||||
frame, end_of_stream=(i + frame_bytes >= len(pcm_data))
|
||||
self.opus_encoder.encode_pcm_to_opus_stream(
|
||||
frame, end_of_stream=(i + frame_bytes >= len(pcm_data)), callback=opus_handler
|
||||
)
|
||||
if opus:
|
||||
opus_datas.extend(opus)
|
||||
|
||||
return opus_datas
|
||||
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"TTS请求异常: {e}")
|
||||
return []
|
||||
|
||||
Reference in New Issue
Block a user