mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-27 09:33:55 +08:00
update:所有ASR均改为PCM直接识别,通过delete_audio参数判断要不要储存wav
This commit is contained in:
@@ -9,8 +9,8 @@ logger = setup_logging()
|
|||||||
|
|
||||||
class ASRProviderBase(ABC):
|
class ASRProviderBase(ABC):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def save_audio_to_file(self, opus_data: List[bytes], session_id: str) -> str:
|
def save_audio_to_file(self, pcm_data: List[bytes], session_id: str) -> str:
|
||||||
"""解码Opus数据并保存为WAV文件"""
|
"""PCM数据保存为WAV文件"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ class ASRProvider(ASRProviderBase):
|
|||||||
self.cluster = config.get("cluster")
|
self.cluster = config.get("cluster")
|
||||||
self.access_token = config.get("access_token")
|
self.access_token = config.get("access_token")
|
||||||
self.output_dir = config.get("output_dir")
|
self.output_dir = config.get("output_dir")
|
||||||
|
self.delete_audio_file = delete_audio_file
|
||||||
|
|
||||||
self.host = "openspeech.bytedance.com"
|
self.host = "openspeech.bytedance.com"
|
||||||
self.ws_url = f"wss://{self.host}/api/v2/asr"
|
self.ws_url = f"wss://{self.host}/api/v2/asr"
|
||||||
@@ -98,21 +99,11 @@ class ASRProvider(ASRProviderBase):
|
|||||||
# 确保输出目录存在
|
# 确保输出目录存在
|
||||||
os.makedirs(self.output_dir, exist_ok=True)
|
os.makedirs(self.output_dir, exist_ok=True)
|
||||||
|
|
||||||
def save_audio_to_file(self, opus_data: List[bytes], session_id: str) -> str:
|
def save_audio_to_file(self, pcm_data: List[bytes], session_id: str) -> str:
|
||||||
"""将Opus音频数据解码并保存为WAV文件"""
|
"""PCM数据保存为WAV文件"""
|
||||||
file_name = f"asr_{session_id}_{uuid.uuid4()}.wav"
|
file_name = f"asr_{session_id}_{uuid.uuid4()}.wav"
|
||||||
file_path = os.path.join(self.output_dir, file_name)
|
file_path = os.path.join(self.output_dir, file_name)
|
||||||
|
|
||||||
decoder = opuslib_next.Decoder(16000, 1) # 16kHz, 单声道
|
|
||||||
pcm_data = []
|
|
||||||
|
|
||||||
for opus_packet in opus_data:
|
|
||||||
try:
|
|
||||||
pcm_frame = decoder.decode(opus_packet, 960) # 960 samples = 60ms
|
|
||||||
pcm_data.append(pcm_frame)
|
|
||||||
except opuslib_next.OpusError as e:
|
|
||||||
logger.bind(tag=TAG).error(f"Opus解码错误: {e}", exc_info=True)
|
|
||||||
|
|
||||||
with wave.open(file_path, "wb") as wf:
|
with wave.open(file_path, "wb") as wf:
|
||||||
wf.setnchannels(1)
|
wf.setnchannels(1)
|
||||||
wf.setsampwidth(2) # 2 bytes = 16-bit
|
wf.setsampwidth(2) # 2 bytes = 16-bit
|
||||||
@@ -274,6 +265,12 @@ class ASRProvider(ASRProviderBase):
|
|||||||
pcm_data = self.decode_opus(opus_data, session_id)
|
pcm_data = self.decode_opus(opus_data, session_id)
|
||||||
combined_pcm_data = b"".join(pcm_data)
|
combined_pcm_data = b"".join(pcm_data)
|
||||||
|
|
||||||
|
# 判断是否保存为WAV文件
|
||||||
|
if self.delete_audio_file:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.save_audio_to_file(pcm_data, session_id)
|
||||||
|
|
||||||
# 直接使用PCM数据
|
# 直接使用PCM数据
|
||||||
# 计算分段大小 (单声道, 16bit, 16kHz采样率)
|
# 计算分段大小 (单声道, 16bit, 16kHz采样率)
|
||||||
size_per_sec = 1 * 2 * 16000 # nchannels * sampwidth * framerate
|
size_per_sec = 1 * 2 * 16000 # nchannels * sampwidth * framerate
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class ASRProvider(ASRProviderBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def save_audio_to_file(self, opus_data: List[bytes], session_id: str) -> str:
|
def save_audio_to_file(self, opus_data: List[bytes], session_id: str) -> str:
|
||||||
"""将Opus音频数据解码并保存为WAV文件"""
|
"""PCM数据保存为WAV文件"""
|
||||||
file_name = f"asr_{session_id}_{uuid.uuid4()}.wav"
|
file_name = f"asr_{session_id}_{uuid.uuid4()}.wav"
|
||||||
file_path = os.path.join(self.output_dir, file_name)
|
file_path = os.path.join(self.output_dir, file_name)
|
||||||
|
|
||||||
@@ -72,20 +72,40 @@ class ASRProvider(ASRProviderBase):
|
|||||||
wf.writeframes(b"".join(pcm_data))
|
wf.writeframes(b"".join(pcm_data))
|
||||||
|
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decode_opus(opus_data: List[bytes], session_id: str) -> List[bytes]:
|
||||||
|
|
||||||
|
decoder = opuslib_next.Decoder(16000, 1) # 16kHz, 单声道
|
||||||
|
pcm_data = []
|
||||||
|
|
||||||
|
for opus_packet in opus_data:
|
||||||
|
try:
|
||||||
|
pcm_frame = decoder.decode(opus_packet, 960) # 960 samples = 60ms
|
||||||
|
pcm_data.append(pcm_frame)
|
||||||
|
except opuslib_next.OpusError as e:
|
||||||
|
logger.bind(tag=TAG).error(f"Opus解码错误: {e}", exc_info=True)
|
||||||
|
|
||||||
|
return pcm_data
|
||||||
|
|
||||||
async def speech_to_text(self, opus_data: List[bytes], session_id: str) -> Tuple[Optional[str], Optional[str]]:
|
async def speech_to_text(self, opus_data: List[bytes], session_id: str) -> Tuple[Optional[str], Optional[str]]:
|
||||||
"""语音转文本主处理逻辑"""
|
"""语音转文本主处理逻辑"""
|
||||||
file_path = None
|
file_path = None
|
||||||
try:
|
try:
|
||||||
# 保存音频文件
|
# 合并所有opus数据包
|
||||||
start_time = time.time()
|
pcm_data = self.decode_opus(opus_data, session_id)
|
||||||
file_path = self.save_audio_to_file(opus_data, session_id)
|
combined_pcm_data = b"".join(pcm_data)
|
||||||
logger.bind(tag=TAG).debug(f"音频文件保存耗时: {time.time() - start_time:.3f}s | 路径: {file_path}")
|
|
||||||
|
# 判断是否保存为WAV文件
|
||||||
|
if self.delete_audio_file:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.save_audio_to_file(pcm_data, session_id)
|
||||||
|
|
||||||
# 语音识别
|
# 语音识别
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
result = self.model.generate(
|
result = self.model.generate(
|
||||||
input=file_path,
|
input=combined_pcm_data,
|
||||||
cache={},
|
cache={},
|
||||||
language="auto",
|
language="auto",
|
||||||
use_itn=True,
|
use_itn=True,
|
||||||
|
|||||||
@@ -84,10 +84,21 @@ class ASRProvider(ASRProviderBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def save_audio_to_file(self, opus_data: List[bytes], session_id: str) -> str:
|
def save_audio_to_file(self, opus_data: List[bytes], session_id: str) -> str:
|
||||||
"""将Opus音频数据解码并保存为WAV文件"""
|
"""PCM数据保存为WAV文件"""
|
||||||
file_name = f"asr_{session_id}_{uuid.uuid4()}.wav"
|
file_name = f"asr_{session_id}_{uuid.uuid4()}.wav"
|
||||||
file_path = os.path.join(self.output_dir, file_name)
|
file_path = os.path.join(self.output_dir, file_name)
|
||||||
|
|
||||||
|
with wave.open(file_path, "wb") as wf:
|
||||||
|
wf.setnchannels(1)
|
||||||
|
wf.setsampwidth(2) # 2 bytes = 16-bit
|
||||||
|
wf.setframerate(16000)
|
||||||
|
wf.writeframes(b"".join(pcm_data))
|
||||||
|
|
||||||
|
return file_path
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decode_opus(opus_data: List[bytes], session_id: str) -> List[bytes]:
|
||||||
|
|
||||||
decoder = opuslib_next.Decoder(16000, 1) # 16kHz, 单声道
|
decoder = opuslib_next.Decoder(16000, 1) # 16kHz, 单声道
|
||||||
pcm_data = []
|
pcm_data = []
|
||||||
|
|
||||||
@@ -98,13 +109,7 @@ class ASRProvider(ASRProviderBase):
|
|||||||
except opuslib_next.OpusError as e:
|
except opuslib_next.OpusError as e:
|
||||||
logger.bind(tag=TAG).error(f"Opus解码错误: {e}", exc_info=True)
|
logger.bind(tag=TAG).error(f"Opus解码错误: {e}", exc_info=True)
|
||||||
|
|
||||||
with wave.open(file_path, "wb") as wf:
|
return pcm_data
|
||||||
wf.setnchannels(1)
|
|
||||||
wf.setsampwidth(2) # 2 bytes = 16-bit
|
|
||||||
wf.setframerate(16000)
|
|
||||||
wf.writeframes(b"".join(pcm_data))
|
|
||||||
|
|
||||||
return file_path
|
|
||||||
|
|
||||||
def read_wave(self, wave_filename: str) -> Tuple[np.ndarray, int]:
|
def read_wave(self, wave_filename: str) -> Tuple[np.ndarray, int]:
|
||||||
"""
|
"""
|
||||||
@@ -136,7 +141,8 @@ class ASRProvider(ASRProviderBase):
|
|||||||
try:
|
try:
|
||||||
# 保存音频文件
|
# 保存音频文件
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
file_path = self.save_audio_to_file(opus_data, session_id)
|
pcm_data = self.decode_opus(opus_data, session_id)
|
||||||
|
file_path = self.save_audio_to_file(pcm_data, session_id)
|
||||||
logger.bind(tag=TAG).debug(f"音频文件保存耗时: {time.time() - start_time:.3f}s | 路径: {file_path}")
|
logger.bind(tag=TAG).debug(f"音频文件保存耗时: {time.time() - start_time:.3f}s | 路径: {file_path}")
|
||||||
|
|
||||||
# 语音识别
|
# 语音识别
|
||||||
|
|||||||
@@ -31,21 +31,11 @@ class ASRProvider(ASRProviderBase):
|
|||||||
os.makedirs(self.output_dir, exist_ok=True)
|
os.makedirs(self.output_dir, exist_ok=True)
|
||||||
|
|
||||||
def save_audio_to_file(self, opus_data: List[bytes], session_id: str) -> str:
|
def save_audio_to_file(self, opus_data: List[bytes], session_id: str) -> str:
|
||||||
"""将Opus音频数据解码并保存为WAV文件"""
|
"""PCM数据保存为WAV文件"""
|
||||||
|
|
||||||
file_name = f"tencent_asr_{session_id}_{uuid.uuid4()}.wav"
|
file_name = f"tencent_asr_{session_id}_{uuid.uuid4()}.wav"
|
||||||
file_path = os.path.join(self.output_dir, file_name)
|
file_path = os.path.join(self.output_dir, file_name)
|
||||||
|
|
||||||
decoder = opuslib_next.Decoder(16000, 1) # 16kHz, 单声道
|
|
||||||
pcm_data = []
|
|
||||||
|
|
||||||
for opus_packet in opus_data:
|
|
||||||
try:
|
|
||||||
pcm_frame = decoder.decode(opus_packet, 960) # 960 samples = 60ms
|
|
||||||
pcm_data.append(pcm_frame)
|
|
||||||
except opuslib_next.OpusError as e:
|
|
||||||
logger.bind(tag=TAG).error(f"Opus解码错误: {e}", exc_info=True)
|
|
||||||
|
|
||||||
with wave.open(file_path, "wb") as wf:
|
with wave.open(file_path, "wb") as wf:
|
||||||
wf.setnchannels(1)
|
wf.setnchannels(1)
|
||||||
wf.setsampwidth(2) # 2 bytes = 16-bit
|
wf.setsampwidth(2) # 2 bytes = 16-bit
|
||||||
@@ -85,6 +75,12 @@ class ASRProvider(ASRProviderBase):
|
|||||||
|
|
||||||
# 将Opus音频数据解码为PCM
|
# 将Opus音频数据解码为PCM
|
||||||
pcm_data = self.decode_opus(opus_data)
|
pcm_data = self.decode_opus(opus_data)
|
||||||
|
|
||||||
|
# 判断是否保存为WAV文件
|
||||||
|
if self.delete_audio_file:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.save_audio_to_file(pcm_data, session_id)
|
||||||
|
|
||||||
# 将音频数据转换为Base64编码
|
# 将音频数据转换为Base64编码
|
||||||
base64_audio = base64.b64encode(pcm_data).decode('utf-8')
|
base64_audio = base64.b64encode(pcm_data).decode('utf-8')
|
||||||
|
|||||||
Reference in New Issue
Block a user