From 04746961f271f549ef11b5ff6c1c8b88943f8dcb Mon Sep 17 00:00:00 2001 From: root Date: Fri, 25 Jul 2025 10:09:04 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E8=B0=83=E6=95=B4PaddleSpeechTTS=E6=B5=81?= =?UTF-8?q?=E5=BC=8F=E4=BC=A0=E8=BE=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/config.yaml | 15 ++ .../core/providers/tts/paddle_speech.py | 152 ++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 main/xiaozhi-server/core/providers/tts/paddle_speech.py diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index 57f35603..ace2f97a 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -855,3 +855,18 @@ TTS: access_token: "U4YdYXVfpwWnk2t5Gp822zWPCuORyeJL" voice: "OUeAo1mhq6IBExi" output_dir: tmp/ + PaddleSpeechTTS: + #百度飞浆 PaddleSpeech 支持本地离线部署 支持模型训练 + #框架地址 https://www.paddlepaddle.org.cn/ + #项目地址 https://github.com/PaddlePaddle/PaddleSpeech + #SpeechServerDemo https://github.com/PaddlePaddle/PaddleSpeech/tree/develop/demos/speech_server + #流式传输请参考 https://github.com/PaddlePaddle/PaddleSpeech/wiki/PaddleSpeech-Server-WebSocket-API + type: paddle_speech + protocol: websocket # protocol choices = ['websocket', 'http'] + url: ws://127.0.0.1:8092/paddlespeech/tts/streaming # TTS 服务的 URL 地址,指向本地服务器 [websocket默认ws://127.0.0.1:8092/paddlespeech/tts/streaming,http默认http://127.0.0.1:8090/paddlespeech/tts] + spk_id: 0 # 发音人 ID,0 通常表示默认的发音人 + sample_rate: 24000 # 采样率 [websocket默认24000,http默认0 自动选择] + speed: 1.0 # 语速,1.0 表示正常语速,>1 表示加快,<1 表示减慢 + volume: 1.0 # 音量,1.0 表示正常音量,>1 表示增大,<1 表示减小 + save_path: ./streaming_tts.wav # 服务器生成的语音文件保存路径 + diff --git a/main/xiaozhi-server/core/providers/tts/paddle_speech.py b/main/xiaozhi-server/core/providers/tts/paddle_speech.py new file mode 100644 index 00000000..a52a27b1 --- /dev/null +++ b/main/xiaozhi-server/core/providers/tts/paddle_speech.py @@ -0,0 +1,152 @@ +import asyncio +import json +import base64 +import aiohttp +import numpy as np +import io +import wave +import websockets +from core.providers.tts.base import TTSProviderBase +from config.logger import setup_logging + +TAG = __name__ +logger = setup_logging() + + +class TTSProvider(TTSProviderBase): + def __init__(self, config, delete_audio_file): + super().__init__(config, delete_audio_file) + self.url = config.get("url", "ws://192.168.1.10:8092/paddlespeech/tts/streaming") + self.protocol = config.get("protocol", "websocket") + self.spk_id = config.get("spk_id", 0) + self.sample_rate = config.get("sample_rate", 24000) + self.speed = config.get("speed", 1.0) + self.volume = config.get("volume", 1.0) + self.save_path = config.get("save_path", "./streaming_tts.wav") + + async def pcm_to_wav(self, pcm_data: bytes, sample_rate: int = 24000, num_channels: int = 1, + bits_per_sample: int = 16) -> bytes: + """ + 将 PCM 数据转换为 WAV 文件并返回字节数据 + :param pcm_data: PCM 数据(原始字节流) + :param sample_rate: 音频采样率,默认为24000 + :param num_channels: 声道数,默认为单声道 + :param bits_per_sample: 每个样本的位数,默认为16 + :return: WAV 格式的字节数据 + """ + byte_data = np.frombuffer(pcm_data, dtype=np.int16) # 16位PCM + wav_io = io.BytesIO() + + with wave.open(wav_io, "wb") as wav_file: + wav_file.setnchannels(num_channels) + wav_file.setsampwidth(bits_per_sample // 8) + wav_file.setframerate(sample_rate) + wav_file.writeframes(byte_data.tobytes()) + + return wav_io.getvalue() + + async def text_to_speak(self, text, output_file): + if self.protocol == "websocket": + return await self.text_streaming(text, output_file) + elif self.protocol == "http": + return await self.text(text, output_file) + else: + raise ValueError("Unsupported protocol. Please use 'websocket' or 'http'.") + + async def text(self, text, output_file): + request_json = { + "text": text, + "spk_id": self.spk_id, + "speed": self.speed, + "volume": self.volume, + "sample_rate": self.sample_rate, + "save_path": self.save_path + } + + try: + async with aiohttp.ClientSession() as session: + async with session.post(self.url, json=request_json) as resp: + if resp.status == 200: + resp_json = await resp.json() + if resp_json.get("success"): + data = resp_json["result"] + audio_bytes = base64.b64decode(data["audio"]) + if output_file: + with open(output_file, "wb") as file_to_save: + file_to_save.write(audio_bytes) + else: + return audio_bytes + else: + raise Exception( + f"Error: {resp_json.get('message', 'Unknown error')} while processing text: {text}") + else: + raise Exception( + f"HTTP Error: {resp.status} - {await resp.text()} while processing text: {text}") + except Exception as e: + raise Exception(f"Error during TTS HTTP request: {e} while processing text: {text}") + + async def text_streaming(self, text, output_file): + try: + # 使用 websockets 异步连接到 WebSocket 服务器 + async with websockets.connect(self.url) as ws: + # 发送开始请求 + start_request = { + "task": "tts", + "signal": "start" + } + await ws.send(json.dumps(start_request)) + + # 接收开始响应并提取 session_id + start_response = await ws.recv() + start_response = json.loads(start_response) # 解析 JSON 响应 + if start_response.get("status") != 0: + raise Exception(f"连接失败: {start_response.get('signal')}") + + session_id = start_response.get("session") + + # 发送待合成的文本数据 + data_request = { + "text": text, + "spk_id": self.spk_id, + } + await ws.send(json.dumps(data_request)) + + audio_chunks = b"" + timeout_seconds = 60 # 设置超时 + try: + while True: + response = await asyncio.wait_for(ws.recv(), timeout=timeout_seconds) + response = json.loads(response) # 解析 JSON 响应 + status = response.get("status") + + if status == 2: # 最后一个数据包 + break + else: + # 拼接音频数据(base64 编码的 PCM 数据) + audio_chunks += base64.b64decode(response.get("audio")) + except asyncio.TimeoutError: + raise Exception(f"WebSocket 超时:等待音频数据超过 {timeout_seconds} 秒") + + # 将拼接后的 PCM 数据转换为 WAV 格式 + wav_data = await self.pcm_to_wav(audio_chunks) + + # 结束请求 + end_request = { + "task": "tts", + "signal": "end", + "session": session_id # 会话 ID 必须与开始请求中的一致 + } + await ws.send(json.dumps(end_request)) + + # 接收结束响应避免服务抛出异常 + await ws.recv() + + # 返回或保存音频数据 + if output_file: + with open(output_file, "wb") as file_to_save: + file_to_save.write(wav_data) + else: + return wav_data + + except Exception as e: + raise Exception(f"Error during TTS WebSocket request: {e} while processing text: {text}") From f832611d41e04f70e034287e3572d773d2d02c08 Mon Sep 17 00:00:00 2001 From: rainv123 <2148537152@qq.com> Date: Thu, 7 Aug 2025 16:19:06 +0800 Subject: [PATCH 2/6] Create paddlespeech-deploy.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PaddleSpeechTTS流式传输集成到xiaozhi服务 --- paddlespeech-deploy.md | 252 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 paddlespeech-deploy.md diff --git a/paddlespeech-deploy.md b/paddlespeech-deploy.md new file mode 100644 index 00000000..149c58f0 --- /dev/null +++ b/paddlespeech-deploy.md @@ -0,0 +1,252 @@ +# PaddleSpeechTTS集成xiaozhi服务 + +## 一、基础环境要求 +操作系统:Windows / Linux / WSL 2 + +Python 版本:3.8 ~ 3.10 + +Paddle 官方最新版本 + +依赖管理工具:conda 或 venv + +前提条件:需要先根据paddle官方教程安装好PaddlePaddle ```https://www.paddlepaddle.org.cn/install``` + +## 二、启动paddlespeech服务 +### 1.从paddlespeech官方仓库拉取源码 +```bash +git clone https://github.com/PaddlePaddle/PaddleSpeech.git +``` +### 2.建立虚拟环境 +```bash +conda create -n paddle_env python=3.8 -y +conda activate paddle_env +``` +### 3.进入paddlespeech目录 +```bash +cd PaddleSpeech +``` +### 4.安装paddlespeech +```bash +pip install pytest-runner -i https://pypi.tuna.tsinghua.edu.cn/simple + +#以下命令使用任意一个 +pip install paddlepaddle -i https://mirror.baidu.com/pypi/simple +pip install paddlespeech -i https://pypi.tuna.tsinghua.edu.cn/simple +``` +### 5.使用命令自动下载语音模型 +```bash +paddlespeech tts --input "你好,这是一次测试" +``` +此步骤会自动下载模型缓存至本地 .paddlespeech/models 目录 + +### 6.修改tts_online_application.yaml配置 +参考目录 ```"PaddleSpeech\demos\streaming_tts_server\conf\tts_online_application.yaml"``` +选择```tts_online_application.yaml```文件用编辑器打开,设置```protocol```为```websocket``` + +### 7.启动服务 +```yaml +paddlespeech_server start --config_file ./demos/streaming_tts_server/conf/tts_online_application.yaml +``` +请根据你的```tts_online_application.yaml```的实际目录来启动命令 +看到如下日志即启动成功 +``` +Prefix dict has been built successfully. +[2025-08-07 10:03:11,312] [ DEBUG] __init__.py:166 - Prefix dict has been built successfully. +INFO: Started server process [2298] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Uvicorn running on http://0.0.0.0:8092 (Press CTRL+C to quit) +``` + +## 三、修改小智的配置文件 +### 1.```main/xiaozhi-server/core/providers/tts/paddle_speech.py``` +```py +import asyncio +import json +import base64 +import aiohttp +import numpy as np +import io +import wave +import websockets +from core.providers.tts.base import TTSProviderBase +from config.logger import setup_logging + +TAG = __name__ +logger = setup_logging() + + +class TTSProvider(TTSProviderBase): + def __init__(self, config, delete_audio_file): + super().__init__(config, delete_audio_file) + self.url = config.get("url", "ws://192.168.1.10:8092/paddlespeech/tts/streaming") + self.protocol = config.get("protocol", "websocket") + self.spk_id = config.get("spk_id", 0) + self.sample_rate = config.get("sample_rate", 24000) + self.speed = config.get("speed", 1.0) + self.volume = config.get("volume", 1.0) + self.save_path = config.get("save_path", "./streaming_tts.wav") + + async def pcm_to_wav(self, pcm_data: bytes, sample_rate: int = 24000, num_channels: int = 1, + bits_per_sample: int = 16) -> bytes: + """ + 将 PCM 数据转换为 WAV 文件并返回字节数据 + :param pcm_data: PCM 数据(原始字节流) + :param sample_rate: 音频采样率,默认为24000 + :param num_channels: 声道数,默认为单声道 + :param bits_per_sample: 每个样本的位数,默认为16 + :return: WAV 格式的字节数据 + """ + byte_data = np.frombuffer(pcm_data, dtype=np.int16) # 16位PCM + wav_io = io.BytesIO() + + with wave.open(wav_io, "wb") as wav_file: + wav_file.setnchannels(num_channels) + wav_file.setsampwidth(bits_per_sample // 8) + wav_file.setframerate(sample_rate) + wav_file.writeframes(byte_data.tobytes()) + + return wav_io.getvalue() + + async def text_to_speak(self, text, output_file): + if self.protocol == "websocket": + return await self.text_streaming(text, output_file) + elif self.protocol == "http": + return await self.text(text, output_file) + else: + raise ValueError("Unsupported protocol. Please use 'websocket' or 'http'.") + + async def text(self, text, output_file): + request_json = { + "text": text, + "spk_id": self.spk_id, + "speed": self.speed, + "volume": self.volume, + "sample_rate": self.sample_rate, + "save_path": self.save_path + } + + try: + async with aiohttp.ClientSession() as session: + async with session.post(self.url, json=request_json) as resp: + if resp.status == 200: + resp_json = await resp.json() + if resp_json.get("success"): + data = resp_json["result"] + audio_bytes = base64.b64decode(data["audio"]) + if output_file: + with open(output_file, "wb") as file_to_save: + file_to_save.write(audio_bytes) + else: + return audio_bytes + else: + raise Exception( + f"Error: {resp_json.get('message', 'Unknown error')} while processing text: {text}") + else: + raise Exception( + f"HTTP Error: {resp.status} - {await resp.text()} while processing text: {text}") + except Exception as e: + raise Exception(f"Error during TTS HTTP request: {e} while processing text: {text}") + + async def text_streaming(self, text, output_file): + try: + # 使用 websockets 异步连接到 WebSocket 服务器 + async with websockets.connect(self.url) as ws: + # 发送开始请求 + start_request = { + "task": "tts", + "signal": "start" + } + await ws.send(json.dumps(start_request)) + + # 接收开始响应并提取 session_id + start_response = await ws.recv() + start_response = json.loads(start_response) # 解析 JSON 响应 + if start_response.get("status") != 0: + raise Exception(f"连接失败: {start_response.get('signal')}") + + session_id = start_response.get("session") + + # 发送待合成的文本数据 + data_request = { + "text": text, + "spk_id": self.spk_id, + } + await ws.send(json.dumps(data_request)) + + audio_chunks = b"" + timeout_seconds = 60 # 设置超时 + try: + while True: + response = await asyncio.wait_for(ws.recv(), timeout=timeout_seconds) + response = json.loads(response) # 解析 JSON 响应 + status = response.get("status") + + if status == 2: # 最后一个数据包 + break + else: + # 拼接音频数据(base64 编码的 PCM 数据) + audio_chunks += base64.b64decode(response.get("audio")) + except asyncio.TimeoutError: + raise Exception(f"WebSocket 超时:等待音频数据超过 {timeout_seconds} 秒") + + # 将拼接后的 PCM 数据转换为 WAV 格式 + wav_data = await self.pcm_to_wav(audio_chunks) + + # 结束请求 + end_request = { + "task": "tts", + "signal": "end", + "session": session_id # 会话 ID 必须与开始请求中的一致 + } + await ws.send(json.dumps(end_request)) + + # 接收结束响应避免服务抛出异常 + await ws.recv() + + # 返回或保存音频数据 + if output_file: + with open(output_file, "wb") as file_to_save: + file_to_save.write(wav_data) + else: + return wav_data + + except Exception as e: + raise Exception(f"Error during TTS WebSocket request: {e} while processing text: {text}") +``` +### 2.```main/xiaozhi-server/data/.config.yaml``` +使用单模块部署 +```yaml +selected_module: + TTS: PaddleSpeechTTS +TTS: + PaddleSpeechTTS: + type: paddle_speech + protocol: websocket + url: ws://127.0.0.1:8092/paddlespeech/tts/streaming # TTS 服务的 URL 地址,指向本地服务器 [websocket默认ws://127.0.0.1:8092/paddlespeech/tts/streaming] + spk_id: 0 # 发音人 ID,0 通常表示默认的发音人 + sample_rate: 24000 # 采样率 [websocket默认24000,http默认0 自动选择] + speed: 1.0 # 语速,1.0 表示正常语速,>1 表示加快,<1 表示减慢 + volume: 1.0 # 音量,1.0 表示正常音量,>1 表示增大,<1 表示减小 + save_path: ./streaming_tts.wav # 服务器生成的语音文件保存路径 +``` +### 3.启动xiaozhi服务 +```py +python app.py +``` +打开test目录下的test_page.html,测试连接和发送消息时paddlespeech端是否有输出日志 + +输出日志参考: +```b +INFO: 127.0.0.1:44312 - "WebSocket /paddlespeech/tts/streaming" [accepted] +INFO: connection open +[2025-08-07 11:16:33,355] [ INFO] - sentence: 哈哈,怎么突然找我聊天啦? +[2025-08-07 11:16:33,356] [ INFO] - The durations of audio is: 2.4625 s +[2025-08-07 11:16:33,356] [ INFO] - first response time: 0.1143045425415039 s +[2025-08-07 11:16:33,356] [ INFO] - final response time: 0.4777836799621582 s +[2025-08-07 11:16:33,356] [ INFO] - RTF: 0.19402382942625715 +[2025-08-07 11:16:33,356] [ INFO] - Other info: front time: 0.06514096260070801 s, first am infer time: 0.008037090301513672 s, first voc infer time: 0.04112648963928223 s, +[2025-08-07 11:16:33,356] [ INFO] - Complete the synthesis of the audio streams +INFO: connection closed + +``` From ef36ad1ec1a7b38dd68cb07488f7556a8bfcb647 Mon Sep 17 00:00:00 2001 From: rainv123 <2148537152@qq.com> Date: Thu, 7 Aug 2025 16:50:10 +0800 Subject: [PATCH 3/6] paddlespeech-deploy.md --- paddlespeech-deploy.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/paddlespeech-deploy.md b/paddlespeech-deploy.md index 149c58f0..395838ca 100644 --- a/paddlespeech-deploy.md +++ b/paddlespeech-deploy.md @@ -12,20 +12,20 @@ Paddle 官方最新版本 前提条件:需要先根据paddle官方教程安装好PaddlePaddle ```https://www.paddlepaddle.org.cn/install``` ## 二、启动paddlespeech服务 -### 1.从paddlespeech官方仓库拉取源码 +### 2.1从paddlespeech官方仓库拉取源码 ```bash git clone https://github.com/PaddlePaddle/PaddleSpeech.git ``` -### 2.建立虚拟环境 +### 2.2建立虚拟环境 ```bash conda create -n paddle_env python=3.8 -y conda activate paddle_env ``` -### 3.进入paddlespeech目录 +### 2.3进入paddlespeech目录 ```bash cd PaddleSpeech ``` -### 4.安装paddlespeech +### 2.4安装paddlespeech ```bash pip install pytest-runner -i https://pypi.tuna.tsinghua.edu.cn/simple @@ -33,17 +33,17 @@ pip install pytest-runner -i https://pypi.tuna.tsinghua.edu.cn/simple pip install paddlepaddle -i https://mirror.baidu.com/pypi/simple pip install paddlespeech -i https://pypi.tuna.tsinghua.edu.cn/simple ``` -### 5.使用命令自动下载语音模型 +### 2.5使用命令自动下载语音模型 ```bash paddlespeech tts --input "你好,这是一次测试" ``` 此步骤会自动下载模型缓存至本地 .paddlespeech/models 目录 -### 6.修改tts_online_application.yaml配置 +### 2.6修改tts_online_application.yaml配置 参考目录 ```"PaddleSpeech\demos\streaming_tts_server\conf\tts_online_application.yaml"``` 选择```tts_online_application.yaml```文件用编辑器打开,设置```protocol```为```websocket``` -### 7.启动服务 +### 2.7启动服务 ```yaml paddlespeech_server start --config_file ./demos/streaming_tts_server/conf/tts_online_application.yaml ``` @@ -59,7 +59,7 @@ INFO: Uvicorn running on http://0.0.0.0:8092 (Press CTRL+C to quit) ``` ## 三、修改小智的配置文件 -### 1.```main/xiaozhi-server/core/providers/tts/paddle_speech.py``` +### 3.1```main/xiaozhi-server/core/providers/tts/paddle_speech.py``` ```py import asyncio import json @@ -214,7 +214,7 @@ class TTSProvider(TTSProviderBase): except Exception as e: raise Exception(f"Error during TTS WebSocket request: {e} while processing text: {text}") ``` -### 2.```main/xiaozhi-server/data/.config.yaml``` +### 3.2```main/xiaozhi-server/data/.config.yaml``` 使用单模块部署 ```yaml selected_module: @@ -230,7 +230,7 @@ TTS: volume: 1.0 # 音量,1.0 表示正常音量,>1 表示增大,<1 表示减小 save_path: ./streaming_tts.wav # 服务器生成的语音文件保存路径 ``` -### 3.启动xiaozhi服务 +### 3.3启动xiaozhi服务 ```py python app.py ``` From 6091eae7fcf3a07efd787dc34566fb2013949cf2 Mon Sep 17 00:00:00 2001 From: rainv123 <2148537152@qq.com> Date: Thu, 7 Aug 2025 17:07:52 +0800 Subject: [PATCH 4/6] paddlespeech-deploy.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本地部署xiaozhi调用paddlespeechTTS服务 --- paddlespeech-deploy.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/paddlespeech-deploy.md b/paddlespeech-deploy.md index 395838ca..149c58f0 100644 --- a/paddlespeech-deploy.md +++ b/paddlespeech-deploy.md @@ -12,20 +12,20 @@ Paddle 官方最新版本 前提条件:需要先根据paddle官方教程安装好PaddlePaddle ```https://www.paddlepaddle.org.cn/install``` ## 二、启动paddlespeech服务 -### 2.1从paddlespeech官方仓库拉取源码 +### 1.从paddlespeech官方仓库拉取源码 ```bash git clone https://github.com/PaddlePaddle/PaddleSpeech.git ``` -### 2.2建立虚拟环境 +### 2.建立虚拟环境 ```bash conda create -n paddle_env python=3.8 -y conda activate paddle_env ``` -### 2.3进入paddlespeech目录 +### 3.进入paddlespeech目录 ```bash cd PaddleSpeech ``` -### 2.4安装paddlespeech +### 4.安装paddlespeech ```bash pip install pytest-runner -i https://pypi.tuna.tsinghua.edu.cn/simple @@ -33,17 +33,17 @@ pip install pytest-runner -i https://pypi.tuna.tsinghua.edu.cn/simple pip install paddlepaddle -i https://mirror.baidu.com/pypi/simple pip install paddlespeech -i https://pypi.tuna.tsinghua.edu.cn/simple ``` -### 2.5使用命令自动下载语音模型 +### 5.使用命令自动下载语音模型 ```bash paddlespeech tts --input "你好,这是一次测试" ``` 此步骤会自动下载模型缓存至本地 .paddlespeech/models 目录 -### 2.6修改tts_online_application.yaml配置 +### 6.修改tts_online_application.yaml配置 参考目录 ```"PaddleSpeech\demos\streaming_tts_server\conf\tts_online_application.yaml"``` 选择```tts_online_application.yaml```文件用编辑器打开,设置```protocol```为```websocket``` -### 2.7启动服务 +### 7.启动服务 ```yaml paddlespeech_server start --config_file ./demos/streaming_tts_server/conf/tts_online_application.yaml ``` @@ -59,7 +59,7 @@ INFO: Uvicorn running on http://0.0.0.0:8092 (Press CTRL+C to quit) ``` ## 三、修改小智的配置文件 -### 3.1```main/xiaozhi-server/core/providers/tts/paddle_speech.py``` +### 1.```main/xiaozhi-server/core/providers/tts/paddle_speech.py``` ```py import asyncio import json @@ -214,7 +214,7 @@ class TTSProvider(TTSProviderBase): except Exception as e: raise Exception(f"Error during TTS WebSocket request: {e} while processing text: {text}") ``` -### 3.2```main/xiaozhi-server/data/.config.yaml``` +### 2.```main/xiaozhi-server/data/.config.yaml``` 使用单模块部署 ```yaml selected_module: @@ -230,7 +230,7 @@ TTS: volume: 1.0 # 音量,1.0 表示正常音量,>1 表示增大,<1 表示减小 save_path: ./streaming_tts.wav # 服务器生成的语音文件保存路径 ``` -### 3.3启动xiaozhi服务 +### 3.启动xiaozhi服务 ```py python app.py ``` From 235bdba42aed97bd523bfb9c3984804f7bcb0e87 Mon Sep 17 00:00:00 2001 From: rainv123 <2148537152@qq.com> Date: Fri, 8 Aug 2025 10:59:07 +0800 Subject: [PATCH 5/6] paddlespeech-deploy.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PaddleSpeechTTS服务集成xiaozhi服务 --- paddlespeech-deploy.md | 169 ++--------------------------------------- 1 file changed, 8 insertions(+), 161 deletions(-) diff --git a/paddlespeech-deploy.md b/paddlespeech-deploy.md index 149c58f0..6584efff 100644 --- a/paddlespeech-deploy.md +++ b/paddlespeech-deploy.md @@ -3,14 +3,12 @@ ## 一、基础环境要求 操作系统:Windows / Linux / WSL 2 -Python 版本:3.8 ~ 3.10 +Python 版本:3.9以上(请根据Paddle官方教程调整) -Paddle 官方最新版本 +Paddle 版本:官方最新版本 ```https://www.paddlepaddle.org.cn/install``` 依赖管理工具:conda 或 venv -前提条件:需要先根据paddle官方教程安装好PaddlePaddle ```https://www.paddlepaddle.org.cn/install``` - ## 二、启动paddlespeech服务 ### 1.从paddlespeech官方仓库拉取源码 ```bash @@ -18,7 +16,8 @@ git clone https://github.com/PaddlePaddle/PaddleSpeech.git ``` ### 2.建立虚拟环境 ```bash -conda create -n paddle_env python=3.8 -y +#请根据Paddle官方支持的python版本建立环境 ```https://www.paddlepaddle.org.cn/install``` +conda create -n paddle_env python=3.10 -y conda activate paddle_env ``` ### 3.进入paddlespeech目录 @@ -46,9 +45,10 @@ paddlespeech tts --input "你好,这是一次测试" ### 7.启动服务 ```yaml paddlespeech_server start --config_file ./demos/streaming_tts_server/conf/tts_online_application.yaml +#官方默认启动命令: +paddlespeech_server start --config_file ./conf/tts_online_application.yaml ``` -请根据你的```tts_online_application.yaml```的实际目录来启动命令 -看到如下日志即启动成功 +请根据你的```tts_online_application.yaml```的实际目录来启动命令,看到如下日志即启动成功 ``` Prefix dict has been built successfully. [2025-08-07 10:03:11,312] [ DEBUG] __init__.py:166 - Prefix dict has been built successfully. @@ -60,160 +60,7 @@ INFO: Uvicorn running on http://0.0.0.0:8092 (Press CTRL+C to quit) ## 三、修改小智的配置文件 ### 1.```main/xiaozhi-server/core/providers/tts/paddle_speech.py``` -```py -import asyncio -import json -import base64 -import aiohttp -import numpy as np -import io -import wave -import websockets -from core.providers.tts.base import TTSProviderBase -from config.logger import setup_logging -TAG = __name__ -logger = setup_logging() - - -class TTSProvider(TTSProviderBase): - def __init__(self, config, delete_audio_file): - super().__init__(config, delete_audio_file) - self.url = config.get("url", "ws://192.168.1.10:8092/paddlespeech/tts/streaming") - self.protocol = config.get("protocol", "websocket") - self.spk_id = config.get("spk_id", 0) - self.sample_rate = config.get("sample_rate", 24000) - self.speed = config.get("speed", 1.0) - self.volume = config.get("volume", 1.0) - self.save_path = config.get("save_path", "./streaming_tts.wav") - - async def pcm_to_wav(self, pcm_data: bytes, sample_rate: int = 24000, num_channels: int = 1, - bits_per_sample: int = 16) -> bytes: - """ - 将 PCM 数据转换为 WAV 文件并返回字节数据 - :param pcm_data: PCM 数据(原始字节流) - :param sample_rate: 音频采样率,默认为24000 - :param num_channels: 声道数,默认为单声道 - :param bits_per_sample: 每个样本的位数,默认为16 - :return: WAV 格式的字节数据 - """ - byte_data = np.frombuffer(pcm_data, dtype=np.int16) # 16位PCM - wav_io = io.BytesIO() - - with wave.open(wav_io, "wb") as wav_file: - wav_file.setnchannels(num_channels) - wav_file.setsampwidth(bits_per_sample // 8) - wav_file.setframerate(sample_rate) - wav_file.writeframes(byte_data.tobytes()) - - return wav_io.getvalue() - - async def text_to_speak(self, text, output_file): - if self.protocol == "websocket": - return await self.text_streaming(text, output_file) - elif self.protocol == "http": - return await self.text(text, output_file) - else: - raise ValueError("Unsupported protocol. Please use 'websocket' or 'http'.") - - async def text(self, text, output_file): - request_json = { - "text": text, - "spk_id": self.spk_id, - "speed": self.speed, - "volume": self.volume, - "sample_rate": self.sample_rate, - "save_path": self.save_path - } - - try: - async with aiohttp.ClientSession() as session: - async with session.post(self.url, json=request_json) as resp: - if resp.status == 200: - resp_json = await resp.json() - if resp_json.get("success"): - data = resp_json["result"] - audio_bytes = base64.b64decode(data["audio"]) - if output_file: - with open(output_file, "wb") as file_to_save: - file_to_save.write(audio_bytes) - else: - return audio_bytes - else: - raise Exception( - f"Error: {resp_json.get('message', 'Unknown error')} while processing text: {text}") - else: - raise Exception( - f"HTTP Error: {resp.status} - {await resp.text()} while processing text: {text}") - except Exception as e: - raise Exception(f"Error during TTS HTTP request: {e} while processing text: {text}") - - async def text_streaming(self, text, output_file): - try: - # 使用 websockets 异步连接到 WebSocket 服务器 - async with websockets.connect(self.url) as ws: - # 发送开始请求 - start_request = { - "task": "tts", - "signal": "start" - } - await ws.send(json.dumps(start_request)) - - # 接收开始响应并提取 session_id - start_response = await ws.recv() - start_response = json.loads(start_response) # 解析 JSON 响应 - if start_response.get("status") != 0: - raise Exception(f"连接失败: {start_response.get('signal')}") - - session_id = start_response.get("session") - - # 发送待合成的文本数据 - data_request = { - "text": text, - "spk_id": self.spk_id, - } - await ws.send(json.dumps(data_request)) - - audio_chunks = b"" - timeout_seconds = 60 # 设置超时 - try: - while True: - response = await asyncio.wait_for(ws.recv(), timeout=timeout_seconds) - response = json.loads(response) # 解析 JSON 响应 - status = response.get("status") - - if status == 2: # 最后一个数据包 - break - else: - # 拼接音频数据(base64 编码的 PCM 数据) - audio_chunks += base64.b64decode(response.get("audio")) - except asyncio.TimeoutError: - raise Exception(f"WebSocket 超时:等待音频数据超过 {timeout_seconds} 秒") - - # 将拼接后的 PCM 数据转换为 WAV 格式 - wav_data = await self.pcm_to_wav(audio_chunks) - - # 结束请求 - end_request = { - "task": "tts", - "signal": "end", - "session": session_id # 会话 ID 必须与开始请求中的一致 - } - await ws.send(json.dumps(end_request)) - - # 接收结束响应避免服务抛出异常 - await ws.recv() - - # 返回或保存音频数据 - if output_file: - with open(output_file, "wb") as file_to_save: - file_to_save.write(wav_data) - else: - return wav_data - - except Exception as e: - raise Exception(f"Error during TTS WebSocket request: {e} while processing text: {text}") -``` ### 2.```main/xiaozhi-server/data/.config.yaml``` 使用单模块部署 ```yaml @@ -237,7 +84,7 @@ python app.py 打开test目录下的test_page.html,测试连接和发送消息时paddlespeech端是否有输出日志 输出日志参考: -```b +``` INFO: 127.0.0.1:44312 - "WebSocket /paddlespeech/tts/streaming" [accepted] INFO: connection open [2025-08-07 11:16:33,355] [ INFO] - sentence: 哈哈,怎么突然找我聊天啦? From 747c432ea3b477d4ffec171f9963dab682191b1f Mon Sep 17 00:00:00 2001 From: FAN-yeB <1442100690@qq.com> Date: Fri, 8 Aug 2025 11:00:31 +0800 Subject: [PATCH 6/6] =?UTF-8?q?update=EF=BC=9Apaddlespeech=20tts=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E9=85=8D=E7=BD=AE=E6=95=99=E7=A8=8B=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paddlespeech-deploy.md => docs/paddlespeech-deploy.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename paddlespeech-deploy.md => docs/paddlespeech-deploy.md (100%) diff --git a/paddlespeech-deploy.md b/docs/paddlespeech-deploy.md similarity index 100% rename from paddlespeech-deploy.md rename to docs/paddlespeech-deploy.md