update:audio_to_data改成异步方法

This commit is contained in:
hrz
2025-12-13 22:29:10 +08:00
parent 8b3a4ad163
commit 8b2bbec0b9
4 changed files with 63 additions and 45 deletions
@@ -101,7 +101,7 @@ async def checkWakeupWords(conn, text):
} }
# 获取音频数据 # 获取音频数据
opus_packets = audio_to_data(response.get("file_path")) opus_packets = await audio_to_data(response.get("file_path"))
# 播放唤醒词回复 # 播放唤醒词回复
conn.client_abort = False conn.client_abort = False
@@ -123,7 +123,7 @@ async def max_out_size(conn):
text = "不好意思,我现在有点事情要忙,明天这个时候我们再聊,约好了哦!明天不见不散,拜拜!" text = "不好意思,我现在有点事情要忙,明天这个时候我们再聊,约好了哦!明天不见不散,拜拜!"
await send_stt_message(conn, text) await send_stt_message(conn, text)
file_path = "config/assets/max_output_size.wav" file_path = "config/assets/max_output_size.wav"
opus_packets = audio_to_data(file_path) opus_packets = await audio_to_data(file_path)
conn.tts.tts_audio_queue.put((SentenceType.LAST, opus_packets, text)) conn.tts.tts_audio_queue.put((SentenceType.LAST, opus_packets, text))
conn.close_after_chat = True conn.close_after_chat = True
@@ -142,7 +142,7 @@ async def check_bind_device(conn):
# 播放提示音 # 播放提示音
music_path = "config/assets/bind_code.wav" music_path = "config/assets/bind_code.wav"
opus_packets = audio_to_data(music_path) opus_packets = await audio_to_data(music_path)
conn.tts.tts_audio_queue.put((SentenceType.FIRST, opus_packets, text)) conn.tts.tts_audio_queue.put((SentenceType.FIRST, opus_packets, text))
# 逐个播放数字 # 逐个播放数字
@@ -150,7 +150,7 @@ async def check_bind_device(conn):
try: try:
digit = conn.bind_code[i] digit = conn.bind_code[i]
num_path = f"config/assets/bind_code/{digit}.wav" num_path = f"config/assets/bind_code/{digit}.wav"
num_packets = audio_to_data(num_path) num_packets = await audio_to_data(num_path)
conn.tts.tts_audio_queue.put((SentenceType.MIDDLE, num_packets, None)) conn.tts.tts_audio_queue.put((SentenceType.MIDDLE, num_packets, None))
except Exception as e: except Exception as e:
conn.logger.bind(tag=TAG).error(f"播放数字音频失败: {e}") conn.logger.bind(tag=TAG).error(f"播放数字音频失败: {e}")
@@ -162,5 +162,5 @@ async def check_bind_device(conn):
text = f"没有找到该设备的版本信息,请正确配置 OTA地址,然后重新编译固件。" text = f"没有找到该设备的版本信息,请正确配置 OTA地址,然后重新编译固件。"
await send_stt_message(conn, text) await send_stt_message(conn, text)
music_path = "config/assets/bind_not_found.wav" music_path = "config/assets/bind_not_found.wav"
opus_packets = audio_to_data(music_path) opus_packets = await audio_to_data(music_path)
conn.tts.tts_audio_queue.put((SentenceType.LAST, opus_packets, text)) conn.tts.tts_audio_queue.put((SentenceType.LAST, opus_packets, text))
@@ -17,7 +17,12 @@ async def sendAudioMessage(conn, sentenceType, audios, text):
if sentenceType == SentenceType.FIRST: if sentenceType == SentenceType.FIRST:
# 同一句子的后续消息加入流控队列,其他情况立即发送 # 同一句子的后续消息加入流控队列,其他情况立即发送
if hasattr(conn, "audio_rate_controller") and conn.audio_rate_controller and getattr(conn, "audio_flow_control", {}).get("sentence_id") == conn.sentence_id: if (
hasattr(conn, "audio_rate_controller")
and conn.audio_rate_controller
and getattr(conn, "audio_flow_control", {}).get("sentence_id")
== conn.sentence_id
):
conn.audio_rate_controller.add_message( conn.audio_rate_controller.add_message(
lambda: send_tts_message(conn, "sentence_start", text) lambda: send_tts_message(conn, "sentence_start", text)
) )
@@ -120,7 +125,8 @@ def _get_or_create_rate_controller(conn, frame_duration, is_single_packet):
# 判断是否需要重置:单包模式且 sentence_id 变化,或者控制器不存在 # 判断是否需要重置:单包模式且 sentence_id 变化,或者控制器不存在
need_reset = ( need_reset = (
is_single_packet is_single_packet
and getattr(conn, "audio_flow_control", {}).get("sentence_id") != conn.sentence_id and getattr(conn, "audio_flow_control", {}).get("sentence_id")
!= conn.sentence_id
) or not hasattr(conn, "audio_rate_controller") ) or not hasattr(conn, "audio_rate_controller")
if need_reset: if need_reset:
@@ -138,7 +144,9 @@ def _get_or_create_rate_controller(conn, frame_duration, is_single_packet):
} }
# 启动后台发送循环 # 启动后台发送循环
_start_background_sender(conn, conn.audio_rate_controller, conn.audio_flow_control) _start_background_sender(
conn, conn.audio_rate_controller, conn.audio_flow_control
)
return conn.audio_rate_controller, conn.audio_flow_control return conn.audio_rate_controller, conn.audio_flow_control
@@ -152,6 +160,7 @@ def _start_background_sender(conn, rate_controller, flow_control):
rate_controller: 速率控制器 rate_controller: 速率控制器
flow_control: 流控状态 flow_control: 流控状态
""" """
async def send_callback(packet): async def send_callback(packet):
# 检查是否应该中止 # 检查是否应该中止
if conn.client_abort: if conn.client_abort:
@@ -165,7 +174,9 @@ def _start_background_sender(conn, rate_controller, flow_control):
rate_controller.start_sending(send_callback) rate_controller.start_sending(send_callback)
async def _send_audio_with_rate_control(conn, audio_list, rate_controller, flow_control, send_delay): async def _send_audio_with_rate_control(
conn, audio_list, rate_controller, flow_control, send_delay
):
""" """
使用 rate_controller 发送音频包 使用 rate_controller 发送音频包
@@ -235,7 +246,7 @@ async def send_tts_message(conn, state, text=None):
stop_tts_notify_voice = conn.config.get( stop_tts_notify_voice = conn.config.get(
"stop_tts_notify_voice", "config/assets/tts_notify.mp3" "stop_tts_notify_voice", "config/assets/tts_notify.mp3"
) )
audios = audio_to_data(stop_tts_notify_voice, is_opus=True) audios = await audio_to_data(stop_tts_notify_voice, is_opus=True)
await sendAudio(conn, audios) await sendAudio(conn, audios)
# 等待所有音频包发送完成 # 等待所有音频包发送完成
await _wait_for_audio_completion(conn) await _wait_for_audio_completion(conn)
+42 -35
View File
@@ -4,6 +4,7 @@ import json
import copy import copy
import wave import wave
import socket import socket
import asyncio
import requests import requests
import subprocess import subprocess
import numpy as np import numpy as np
@@ -268,56 +269,62 @@ def audio_to_data_stream(
pcm_to_data_stream(raw_data, is_opus, callback) pcm_to_data_stream(raw_data, is_opus, callback)
def audio_to_data(audio_file_path: str, is_opus: bool = True) -> list[bytes]: async def audio_to_data(audio_file_path: str, is_opus: bool = True) -> list[bytes]:
""" """
将音频文件转换为Opus/PCM编码的帧列表 将音频文件转换为Opus/PCM编码的帧列表
Args: Args:
audio_file_path: 音频文件路径 audio_file_path: 音频文件路径
is_opus: 是否进行Opus编码 is_opus: 是否进行Opus编码
""" """
# 获取文件后缀名
file_type = os.path.splitext(audio_file_path)[1]
if file_type:
file_type = file_type.lstrip(".")
# 读取音频文件,-nostdin 参数:不要从标准输入读取数据,否则FFmpeg会阻塞
audio = AudioSegment.from_file(
audio_file_path, format=file_type, parameters=["-nostdin"]
)
# 转换为单声道/16kHz采样率/16位小端编码(确保与编码器匹配) def _sync_audio_to_data():
audio = audio.set_channels(1).set_frame_rate(16000).set_sample_width(2) # 获取文件后缀名
file_type = os.path.splitext(audio_file_path)[1]
if file_type:
file_type = file_type.lstrip(".")
# 读取音频文件,-nostdin 参数:不要从标准输入读取数据,否则FFmpeg会阻塞
audio = AudioSegment.from_file(
audio_file_path, format=file_type, parameters=["-nostdin"]
)
# 获取原始PCM数据(16位小端 # 转换为单声道/16kHz采样率/16位小端编码(确保与编码器匹配
raw_data = audio.raw_data audio = audio.set_channels(1).set_frame_rate(16000).set_sample_width(2)
# 初始化Opus编码器 # 获取原始PCM数据(16位小端)
encoder = opuslib_next.Encoder(16000, 1, opuslib_next.APPLICATION_AUDIO) raw_data = audio.raw_data
# 编码参数 # 初始化Opus编码器
frame_duration = 60 # 60ms per frame encoder = opuslib_next.Encoder(16000, 1, opuslib_next.APPLICATION_AUDIO)
frame_size = int(16000 * frame_duration / 1000) # 960 samples/frame
datas = [] # 编码参数
# 按帧处理所有音频数据(包括最后一帧可能补零) frame_duration = 60 # 60ms per frame
for i in range(0, len(raw_data), frame_size * 2): # 16bit=2bytes/sample frame_size = int(16000 * frame_duration / 1000) # 960 samples/frame
# 获取当前帧的二进制数据
chunk = raw_data[i : i + frame_size * 2]
# 如果最后一帧不足,补零 datas = []
if len(chunk) < frame_size * 2: # 按帧处理所有音频数据(包括最后一帧可能补零)
chunk += b"\x00" * (frame_size * 2 - len(chunk)) for i in range(0, len(raw_data), frame_size * 2): # 16bit=2bytes/sample
# 获取当前帧的二进制数据
chunk = raw_data[i : i + frame_size * 2]
if is_opus: # 如果最后一帧不足,补零
# 转换为numpy数组处理 if len(chunk) < frame_size * 2:
np_frame = np.frombuffer(chunk, dtype=np.int16) chunk += b"\x00" * (frame_size * 2 - len(chunk))
# 编码Opus数据
frame_data = encoder.encode(np_frame.tobytes(), frame_size)
else:
frame_data = chunk if isinstance(chunk, bytes) else bytes(chunk)
datas.append(frame_data) if is_opus:
# 转换为numpy数组处理
np_frame = np.frombuffer(chunk, dtype=np.int16)
# 编码Opus数据
frame_data = encoder.encode(np_frame.tobytes(), frame_size)
else:
frame_data = chunk if isinstance(chunk, bytes) else bytes(chunk)
return datas datas.append(frame_data)
return datas
loop = asyncio.get_running_loop()
# 在单独的线程中执行同步的音频处理操作
return await loop.run_in_executor(None, _sync_audio_to_data)
def audio_bytes_to_data_stream( def audio_bytes_to_data_stream(