From 3130044909fbd61d512286536540da933d9ef9f4 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Thu, 29 May 2025 09:05:08 +0800 Subject: [PATCH] test --- main/xiaozhi-server/config.yaml | 7 ++ .../core/providers/tts/linkerai.py | 95 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 main/xiaozhi-server/core/providers/tts/linkerai.py diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index 25c8e017..473c095b 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -713,4 +713,11 @@ TTS: headers: # 自定义请求头 # Authorization: Bearer xxxx format: mp3 # 接口返回的音频格式 + output_dir: tmp/ + LinkeraiTTS: + #各参数意义见开发文档:https://tts.linkerai.top/docs#/default/text_to_speech_tts_get + type: linkerai + api_url: https://tts.linkerai.top/tts + access_token: "test" + voice: "OUeAo1mhq6IBExi" output_dir: tmp/ \ No newline at end of file diff --git a/main/xiaozhi-server/core/providers/tts/linkerai.py b/main/xiaozhi-server/core/providers/tts/linkerai.py new file mode 100644 index 00000000..8ad069bd --- /dev/null +++ b/main/xiaozhi-server/core/providers/tts/linkerai.py @@ -0,0 +1,95 @@ +from core.providers.tts.base import TTSProviderBase +from core.providers.tts.dto.dto import ( + TTSMessageDTO, + SentenceType, + ContentType, + InterfaceType +) +from config.logger import setup_logging +from core.utils import opus_encoder_utils +import requests + +TAG = __name__ +logger = setup_logging() + + +class TTSProvider(TTSProviderBase): + def __init__(self, config, delete_audio_file): + super().__init__(config, delete_audio_file) + self.interface_type = InterfaceType.SINGLE_STREAM + self.access_token = config.get("access_token") + self.voice = config.get("voice") + self.api_url = config.get("api_url") + + # 根据配置选择音频格式,优先使用opus + self.audio_format = config.get("audio_format", "opus") + + # 创建Opus编码器 + self.opus_encoder = opus_encoder_utils.OpusEncoderUtils( + sample_rate=16000, + channels=1, + frame_size_ms=60 + ) + + async def text_to_speak(self, text, _): + """将文本转换为语音(流式)""" + await self.send_text(self.voice, text) + return + + async def send_text(self, speaker: str, text: str): + """向 TTS 服务发送文本并获取音频流""" + try: + # 构造请求参数 + params = { + "tts_text": text, + "spk_id": self.voice, + "frame_duration": 60, + "stream": "true", + "target_sr": 16000, + "audio_format": self.audio_format, + } + + # 构造请求头 + headers = { + "Authorization": f"Bearer {self.access_token}", + } + + # 发送流式请求 + response = requests.get( + self.api_url, + params=params, + headers=headers, + stream=True + ) + + # 检查响应状态 + if response.status_code != 200: + logger.bind(tag=TAG).error(f"TTS 请求失败: {response.status_code}, {response.text}") + return + + # 处理音频流 + audio_frames = [] + for chunk in response.iter_content(chunk_size=1024): + if chunk: + if self.audio_format == "pcm": + # 将PCM转换为Opus + opus_frames = self.opus_encoder.encode_pcm_to_opus(chunk, False) + audio_frames.extend(opus_frames) + else: + # 直接使用Opus帧 + audio_frames.append(chunk) + + # 将音频帧放入队列 + self.tts_audio_queue.put( + (SentenceType.MIDDLE, audio_frames, text) + ) + + except Exception as e: + logger.bind(tag=TAG).error(f"TTS 流式处理异常:{str(e)}") + raise + + async def close(self): + """资源清理方法""" + await super().close() + if hasattr(self, "opus_encoder"): + self.opus_encoder.close()