import time import wave import os from abc import ABC, abstractmethod import logging from typing import Optional, Tuple, List import uuid import opuslib from funasr import AutoModel from funasr.utils.postprocess_utils import rich_transcription_postprocess logger = logging.getLogger(__name__) class ASR(ABC): @abstractmethod def save_audio_to_file(self, opus_data: List[bytes], session_id: str) -> str: """解码Opus数据并保存为WAV文件""" pass @abstractmethod def speech_to_text(self, opus_data: List[bytes], session_id: str) -> Tuple[Optional[str], Optional[str]]: """将语音数据转换为文本""" pass class FunASR(ASR): def __init__(self, config: dict, delete_audio_file: bool): self.model_dir = config.get("model_dir") self.output_dir = config.get("output_dir") # 修正配置键名 self.delete_audio_file = delete_audio_file # 确保输出目录存在 os.makedirs(self.output_dir, exist_ok=True) self.model = AutoModel( model=self.model_dir, vad_kwargs={"max_single_segment_time": 30000}, disable_update=True, hub="hf" # device="cuda:0", # 启用GPU加速 ) def save_audio_to_file(self, opus_data: List[bytes], session_id: str) -> str: """将Opus音频数据解码并保存为WAV文件""" file_name = f"asr_{session_id}_{uuid.uuid4()}.wav" file_path = os.path.join(self.output_dir, file_name) decoder = opuslib.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.OpusError as e: logger.error(f"Opus解码错误: {e}", exc_info=True) 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 def speech_to_text(self, opus_data: List[bytes], session_id: str) -> Tuple[Optional[str], Optional[str]]: """语音转文本主处理逻辑""" file_path = None try: # 保存音频文件 start_time = time.time() file_path = self.save_audio_to_file(opus_data, session_id) logger.debug(f"音频文件保存耗时: {time.time() - start_time:.3f}s | 路径: {file_path}") # 语音识别 start_time = time.time() result = self.model.generate( input=file_path, cache={}, language="auto", use_itn=True, batch_size_s=60, ) text = rich_transcription_postprocess(result[0]["text"]) logger.debug(f"语音识别耗时: {time.time() - start_time:.3f}s | 结果: {text}") return text, file_path except Exception as e: logger.error(f"语音识别失败: {e}", exc_info=True) return None, None finally: # 文件清理逻辑 if self.delete_audio_file and file_path and os.path.exists(file_path): try: os.remove(file_path) logger.debug(f"已删除临时音频文件: {file_path}") except Exception as e: logger.error(f"文件删除失败: {file_path} | 错误: {e}") def create_instance(class_name: str, *args, **kwargs) -> ASR: """工厂方法创建ASR实例""" cls_map = { "FunASR": FunASR, # 可扩展其他ASR实现 } if cls := cls_map.get(class_name): return cls(*args, **kwargs) raise ValueError(f"不支持的ASR类型: {class_name}")