Files
xiaozhi-esp32-server/main/xiaozhi-server/core/handle/sendAudioHandle.py
T

148 lines
4.5 KiB
Python
Raw Normal View History

2025-02-02 23:01:14 +08:00
import json
import asyncio
import time
2025-05-26 02:20:38 +08:00
from core.providers.tts.dto.dto import SentenceType
2025-05-24 12:11:13 +08:00
from core.utils.util import get_string_no_punctuation_or_emoji, analyze_emotion
2025-05-26 10:44:35 +08:00
from loguru import logger
2025-02-02 23:01:14 +08:00
2025-02-18 00:07:19 +08:00
TAG = __name__
2025-05-24 12:11:13 +08:00
emoji_map = {
"neutral": "😶",
"happy": "🙂",
"laughing": "😆",
"funny": "😂",
"sad": "😔",
"angry": "😠",
"crying": "😭",
"loving": "😍",
"embarrassed": "😳",
"surprised": "😲",
"shocked": "😱",
"thinking": "🤔",
"winking": "😉",
"cool": "😎",
"relaxed": "😌",
"delicious": "🤤",
"kissy": "😘",
"confident": "😏",
"sleepy": "😴",
"silly": "😜",
"confused": "🙄",
}
2025-02-02 23:01:14 +08:00
2025-05-26 02:20:38 +08:00
async def sendAudioMessage(conn, sentenceType, audios, text):
2025-03-05 17:26:32 +08:00
# 发送句子开始消息
2025-06-08 11:50:09 +08:00
conn.logger.bind(tag=TAG).info(f"发送音频消息: {sentenceType}, {text}")
2025-05-24 12:11:13 +08:00
if text is not None:
emotion = analyze_emotion(text)
emoji = emoji_map.get(emotion, "🙂") # 默认使用笑脸
await conn.websocket.send(
json.dumps(
{
"type": "llm",
"text": emoji,
"emotion": emotion,
"session_id": conn.session_id,
}
)
)
2025-05-26 16:12:38 +08:00
pre_buffer = False
if conn.tts.tts_audio_first_sentence and text is not None:
conn.logger.bind(tag=TAG).info(f"发送第一段语音: {text}")
conn.tts.tts_audio_first_sentence = False
pre_buffer = True
2025-02-02 23:01:14 +08:00
2025-05-24 12:11:13 +08:00
await send_tts_message(conn, "sentence_start", text)
2025-05-26 16:12:38 +08:00
await sendAudio(conn, audios, pre_buffer)
2025-05-24 12:11:13 +08:00
await send_tts_message(conn, "sentence_end", text)
# 发送结束消息(如果是最后一个文本)
2025-05-26 02:20:38 +08:00
if conn.llm_finish_task and sentenceType == SentenceType.LAST:
2025-05-24 12:11:13 +08:00
await send_tts_message(conn, "stop", None)
2025-05-30 15:47:32 +08:00
conn.client_is_speaking = False
2025-05-24 12:11:13 +08:00
if conn.close_after_chat:
await conn.close()
# 播放音频
async def sendAudio(conn, audios, pre_buffer=True):
2025-05-26 02:20:38 +08:00
if audios is None or len(audios) == 0:
return
2025-03-10 15:56:28 +08:00
# 流控参数优化
2025-05-24 12:11:13 +08:00
frame_duration = 60 # 帧时长(毫秒),匹配 Opus 编码
2025-03-10 15:56:28 +08:00
start_time = time.perf_counter()
2025-05-24 12:11:13 +08:00
play_position = 0
last_reset_time = time.perf_counter() # 记录最后的重置时间
2025-03-01 17:09:01 +08:00
2025-05-24 12:11:13 +08:00
# 仅当第一句话时执行预缓冲
if pre_buffer:
pre_buffer_frames = min(3, len(audios))
for i in range(pre_buffer_frames):
await conn.websocket.send(audios[i])
remaining_audios = audios[pre_buffer_frames:]
else:
remaining_audios = audios
# 播放剩余音频帧
for opus_packet in remaining_audios:
2025-03-01 01:54:55 +08:00
if conn.client_abort:
2025-06-04 11:41:04 +08:00
break
2025-03-01 17:09:01 +08:00
2025-05-24 12:11:13 +08:00
# 每分钟重置一次计时器
if time.perf_counter() - last_reset_time > 60:
await conn.reset_timeout()
last_reset_time = time.perf_counter()
# 计算预期发送时间
2025-03-01 17:09:01 +08:00
expected_time = start_time + (play_position / 1000)
current_time = time.perf_counter()
delay = expected_time - current_time
if delay > 0:
await asyncio.sleep(delay)
2025-02-02 23:01:14 +08:00
await conn.websocket.send(opus_packet)
2025-03-10 15:56:28 +08:00
2025-05-24 12:11:13 +08:00
play_position += frame_duration
2025-02-02 23:01:14 +08:00
async def send_tts_message(conn, state, text=None):
"""发送 TTS 状态消息"""
2025-03-31 23:26:23 +08:00
message = {"type": "tts", "state": state, "session_id": conn.session_id}
if text is not None:
message["text"] = text
2025-02-02 23:01:14 +08:00
2025-05-24 12:11:13 +08:00
# TTS播放结束
if state == "stop":
2025-05-24 12:11:13 +08:00
# 播放提示音
tts_notify = conn.config.get("enable_stop_tts_notify", False)
if tts_notify:
stop_tts_notify_voice = conn.config.get(
"stop_tts_notify_voice", "config/assets/tts_notify.mp3"
)
audios, _ = conn.tts.audio_to_opus_data(stop_tts_notify_voice)
await sendAudio(conn, audios)
# 清除服务端讲话状态
conn.clearSpeakStatus()
2025-02-02 23:01:14 +08:00
2025-05-24 12:11:13 +08:00
# 发送消息到客户端
await conn.websocket.send(json.dumps(message))
2025-02-11 13:51:44 +08:00
async def send_stt_message(conn, text):
2025-05-29 23:56:34 +08:00
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
2025-02-11 13:51:44 +08:00
"""发送 STT 状态消息"""
stt_text = get_string_no_punctuation_or_emoji(text)
await conn.websocket.send(
2025-03-31 23:26:23 +08:00
json.dumps({"type": "stt", "text": stt_text, "session_id": conn.session_id})
)
2025-05-30 15:47:32 +08:00
conn.client_is_speaking = True
2025-02-11 13:51:44 +08:00
await send_tts_message(conn, "start")