HumansXドキュメントセンター 公式サイトへ ↗

オーディオシステム技術ドキュメント

オーディオシステムの概要

ASR-LLM-TTS バックエンドのオーディオシステムは、音声認識の入力、音声合成の出力、BGM の再生を担当します。多層アーキテクチャ設計を採用し、複数デバイスの管理、同時再生の制御、インテリジェントなデバイス切り替えに対応しています。

️ アーキテクチャ設計

オーディオシステムのアーキテクチャ図

┌─────────────────────────────────────────────────────────────┐
│                    音频系统架构                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────┐    ┌─────────────────┐                │
│  │   ASR Service   │    │  TTS Player     │                │
│  │  (语音识别)     │    │  (语音合成)     │                │
│  └─────────┬───────┘    └─────────┬───────┘                │
│            │                      │                        │
│            ▼                      ▼                        │
│  ┌─────────────────┐    ┌─────────────────┐                │
│  │  Microphone     │    │ AudioNewPlayer  │                │
│  │  (麦克风输入)   │    │ (音频播放器)    │                │
│  └─────────────────┘    └─────────┬───────┘                │
│                                   │                        │
│                         ┌─────────▼───────┐                │
│                         │ Audio Device    │                │
│                         │ Manager         │                │
│                         │ (设备管理器)    │                │
│                         └─────────┬───────┘                │
│                                   │                        │
│            ┌──────────────────────┼──────────────────────┐ │
│            │                      │                      │ │
│            ▼                      ▼                      ▼ │
│  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐ │
│  │  Music Player   │    │  TTS Output     │    │  System Audio   │ │
│  │  (背景音乐)     │    │  (语音输出)     │    │  (系统音频)     │ │
│  └─────────────────┘    └─────────────────┘    └─────────────────┘ │
│            │                      │                      │ │
│            ▼                      ▼                      ▼ │
│  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐ │
│  │   pygame mixer  │    │   sounddevice   │    │   PulseAudio    │ │
│  │   (音乐播放)    │    │   (实时播放)    │    │   (系统音频)    │ │
│  └─────────────────┘    └─────────────────┘    └─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

主要コンポーネント

コンポーネント ファイルパス 役割
AudioNewPlayer utils/audio/audio_player.py リアルタイム音声ストリーミングプレーヤー
AudioDeviceManager utils/audio/audio_device_manager.py 複数デバイスの協調管理
MusicPlayer utils/function_call/function_tools_impl.py BGM プレーヤー
ASR Service asr/asr_service.py 音声認識サービス
TTS Player app/tts_player.py TTS 再生制御

AudioNewPlayer リアルタイム音声プレーヤー

主な特長

  • 非同期ストリーミング再生:非同期ジェネレーターからの音声データ受信に対応
  • インテリジェントなバッファリング戦略:プリバッファリングの仕組みによりスムーズな再生を確保
  • ステートマシンによる管理:再生状態をきめ細かく追跡
  • 音量制御:リアルタイムの音量調整
  • デバイスへの自動適応:最適なオーディオデバイスを自動選択

プレーヤーのステートマシン

class PlayerState(Enum):
    IDLE = "idle"                    # 空闲状态
    BUFFERING = "buffering"          # 缓冲中
    PLAYING = "playing"              # 播放中
    PAUSED = "paused"                # 暂停
    STOPPING = "stopping"            # 停止中
    STOPPED = "stopped"              # 已停止
    ERROR = "error"                  # 错误状态

非同期再生の実装

class AudioNewPlayer:
    async def play_stream(self, audio_generator, sample_rate: int, channels: int = 1):
        """流式播放音频(完全异步)"""
        try:
            # 1. 初始化播放队列
            self._audio_queue = queue.Queue(maxsize=self.buffer_size)
            self._set_state(PlayerState.BUFFERING)

            # 2. 启动音频输入任务
            input_task = asyncio.create_task(
                self._feed_audio_queue(audio_generator)
            )

            # 3. 等待预缓冲完成
            await self._wait_for_prebuffer_or_error()

            # 4. 创建音频输出流
            self._stream = sd.OutputStream(
                samplerate=sample_rate,
                channels=channels,
                dtype=np.float32,
                callback=self._audio_callback,
                blocksize=self.blocksize,
                device=self.device,
                finished_callback=self._stream_finished_callback
            )

            # 5. 开始播放
            self._stream.start()
            self._set_state(PlayerState.PLAYING)

            # 6. 等待播放完成
            await self._wait_for_playback_finished_or_stopped()

        except Exception as e:
            logger.error(f"播放过程中发生错误: {e}", exc_info=True)
            self._set_state(PlayerState.ERROR)
            raise AudioPlayerError(f"播放失败: {e}")
        finally:
            await self._cleanup_playback()

バッファリング戦略

プリバッファリングの仕組み:

async def _wait_for_prebuffer_or_error(self):
    """等待预缓冲完成或出错"""
    while True:
        if self._error_event.is_set():
            error_msg = self._error_message or "未知播放错误"
            raise AudioPlayerError(f"预缓冲阶段出错: {error_msg}")

        queue_size = self._audio_queue.qsize()
        if queue_size >= self.min_prebuffer:
            logger.info(f"预缓冲完成,队列大小: {queue_size}")
            break

        await asyncio.sleep(0.01)  # 短暂等待

オーディオコールバック関数:

def _audio_callback(self, outdata, frames, time, status):
    """sounddevice音频回调函数"""
    if status:
        logger.warning(f"音频回调状态警告: {status}")

    try:
        # 从队列中获取音频数据
        if not self._audio_queue.empty():
            audio_chunk = self._audio_queue.get_nowait()

            # 应用音量控制
            if self._volume != 1.0:
                audio_chunk = audio_chunk * self._volume

            # 填充输出缓冲区
            if len(audio_chunk) == frames:
                outdata[:] = audio_chunk.reshape(-1, 1)
            else:
                # 处理长度不匹配的情况
                outdata[:] = self._resize_audio_chunk(audio_chunk, frames)
        else:
            # 队列为空,输出静音
            outdata.fill(0)

    except queue.Empty:
        outdata.fill(0)
    except Exception as e:
        logger.error(f"音频回调函数错误: {e}")
        outdata.fill(0)

️ AudioDeviceManager デバイスマネージャー

デバイスタイプの定義

class AudioDeviceType(Enum):
    TTS_PLAYER = "tts_player"        # TTS播放器
    MUSIC_PLAYER = "music_player"    # 背景音乐播放器
    SYSTEM_AUDIO = "system_audio"    # 系统音频

デバイスアクセス権の管理

class AudioDeviceManager:
    def __init__(self):
        self._device_locks: Dict[AudioDeviceType, asyncio.Lock] = {
            device_type: asyncio.Lock() for device_type in AudioDeviceType
        }
        self._active_devices: Dict[AudioDeviceType, bool] = {}
        self._volume_callbacks: Dict[AudioDeviceType, Callable] = {}
        self._original_volumes: Dict[AudioDeviceType, float] = {}

    def request_device_access_sync(
        self, 
        device_type: AudioDeviceType, 
        device_info: dict,
        force_stop_others: bool = False
    ) -> bool:
        """同步方式请求设备访问权限"""
        try:
            if force_stop_others:
                # 停止其他设备的播放
                for other_type in AudioDeviceType:
                    if other_type != device_type and self._active_devices.get(other_type):
                        self._stop_device_sync(other_type)

            # 标记设备为活跃状态
            self._active_devices[device_type] = True
            logger.info(f"设备 {device_type.value} 获得访问权限")
            return True

        except Exception as e:
            logger.error(f"请求设备访问权限失败: {e}")
            return False

    def release_device_access_sync(self, device_type: AudioDeviceType):
        """同步方式释放设备访问权限"""
        try:
            self._active_devices[device_type] = False

            # 恢复其他设备的原始音量
            for other_type in AudioDeviceType:
                if other_type != device_type:
                    original_volume = self._original_volumes.get(other_type)
                    if original_volume is not None:
                        callback = self._volume_callbacks.get(other_type)
                        if callback:
                            callback(original_volume)

            logger.info(f"设备 {device_type.value} 释放访问权限")

        except Exception as e:
            logger.error(f"释放设备访问权限失败: {e}")

音量の協調制御の仕組み

def register_volume_callback(
    self, 
    device_type: AudioDeviceType, 
    callback: Callable[[float], None]
):
    """注册音量控制回调"""
    self._volume_callbacks[device_type] = callback
    logger.info(f"注册 {device_type.value} 的音量控制回调")

def set_original_volume(self, device_type: AudioDeviceType, volume: float):
    """设置设备的原始音量"""
    self._original_volumes[device_type] = volume
    logger.info(f"设置 {device_type.value} 原始音量: {volume}")

def update_music_player_volume(self, volume: float):
    """更新音乐播放器音量并记录"""
    self.set_original_volume(AudioDeviceType.MUSIC_PLAYER, volume)
    logger.info(f"音乐播放器音量已更新: {volume}")

MusicPlayer BGM システム

pygame mixer の初期化

def _init_pygame_mixer(self):
    """初始化pygame mixer,动态尝试多个设备"""
    potential_devices = [
        'UACDemoV1.0',      # RDKX5 USB喇叭描述名
        'UACDemoV10',       # 替代描述名
        'hw:0,0',           # ALSA硬件设备
        'plughw:0,0',       # ALSA插件设备
        None                # 系统默认设备
    ]

    os.environ['SDL_AUDIODRIVER'] = 'pulse'

    for device in potential_devices:
        try:
            logger.info(f"尝试初始化设备: {device or '默认'}")

            pygame.mixer.pre_init(
                frequency=44100,
                size=-16,
                channels=2,
                buffer=4096
            )

            if device:
                pygame.mixer.init(devicename=device)
            else:
                pygame.mixer.init()

            logger.info(f"✅ pygame mixer 初始化成功: {device or '默认'}")
            return

        except pygame.error as e:
            logger.warning(f"设备 {device} 初始化失败: {e}")
            pygame.mixer.quit()

    raise RuntimeError("无法初始化pygame mixer")

インテリジェントな楽曲マッチング

def _find_music_by_name(self, music_name: str, music_list: List[str]) -> Optional[str]:
    """智能音乐名称匹配"""
    # 1. 完全匹配
    if music_name in music_list:
        return music_name

    # 2. 去扩展名匹配
    name_without_ext = os.path.splitext(music_name)[0]
    for music_file in music_list:
        if os.path.splitext(music_file)[0] == name_without_ext:
            return music_file

    # 3. 前缀匹配
    for music_file in music_list:
        if os.path.splitext(music_file)[0].startswith(name_without_ext):
            return music_file

    # 4. 包含匹配
    for music_file in music_list:
        if name_without_ext in os.path.splitext(music_file)[0]:
            return music_file

    return None

再生監視の仕組み

def _monitor_music_playback(self):
    """监控音乐播放状态,播放结束时释放设备权限"""
    while not self._stop_monitor.wait(1.0):  # 每秒检查一次
        try:
            if not pygame.mixer.music.get_busy():
                # 音乐播放结束
                logger.info("检测到音乐播放结束")
                self.current_music = None

                # 释放设备访问权限
                self._release_device_access_sync()

                # 恢复原始音量
                pygame.mixer.music.set_volume(self._volume)
                break

        except Exception as e:
            logger.error(f"监控播放状态错误: {e}")
            break

ASR 音声認識システム

マルチプロセスアーキテクチャ

# 在server.py中启动ASR服务
if input_mode == "asr":
    asr_config_obj = config_loader.get_asr_config()
    if asr_config_obj:
        asr_result_queue = multiprocessing.Queue(maxsize=20)

        asr_service_process = multiprocessing.Process(
            target=run_asr_service,
            args=(asr_config_obj, None, asr_result_queue),
            name="ASRServiceProcess",
            daemon=True
        )
        asr_service_process.start()

        # 启动监听任务
        asr_listener_task = asyncio.create_task(
            listen_and_forward_asr_results()
        )

VAD による発話区間検出

def run_asr_service(asr_config: ASRConfig, audio_queue, result_queue):
    """ASR服务主函数"""
    try:
        # 1. 初始化VAD模型
        vad_model = init_vad_model(asr_config.vad_model_path)

        # 2. 初始化ASR模型  
        asr_recognizer = init_asr_recognizer(asr_config)

        # 3. 初始化音频输入
        microphone = init_microphone(asr_config)

        logger.info("ASR服务启动完成")

        # 4. 主处理循环
        audio_buffer = []
        vad_state = VadState.SILENCE

        for audio_chunk in microphone.stream():
            # VAD检测
            is_speech = vad_model.predict(audio_chunk)

            if vad_state == VadState.SILENCE and is_speech:
                # 检测到语音开始
                vad_state = VadState.SPEECH
                audio_buffer = [audio_chunk]
                logger.debug("检测到语音开始")

            elif vad_state == VadState.SPEECH:
                audio_buffer.append(audio_chunk)

                if not is_speech:
                    # 检测到语音结束
                    vad_state = VadState.SILENCE

                    # 进行ASR识别
                    if len(audio_buffer) > MIN_AUDIO_LENGTH:
                        audio_data = np.concatenate(audio_buffer)
                        recognition_result = asr_recognizer.recognize(audio_data)

                        if recognition_result.strip():
                            result_queue.put(recognition_result)
                            logger.info(f"ASR识别结果: {recognition_result}")

                    audio_buffer = []

    except Exception as e:
        logger.error(f"ASR服务运行错误: {e}", exc_info=True)
        result_queue.put(None)  # 发送错误信号

オーディオ設定ツール

対話型設定ツール

# utils/audio_config/setup_audio_devices.py
class AudioDeviceSetup:
    def __init__(self):
        self.config_manager = AudioDeviceConfigManager()

    def run_interactive_setup(self):
        """交互式音频设备配置"""
        print("🎵 音频设备配置工具")
        print("=" * 50)

        while True:
            self._display_menu()
            choice = input("请选择操作: ").strip()

            if choice == "1":
                self._scan_and_display_devices()
            elif choice == "2":
                self._configure_output_device()
            elif choice == "3":
                self._configure_input_device()
            elif choice == "4":
                self._test_audio_output()
            elif choice == "5":
                self._show_current_config()
            elif choice == "0":
                break
            else:
                print("❌ 无效选择,请重试")

    def _scan_and_display_devices(self):
        """扫描并显示音频设备"""
        print("\n🔍 正在扫描音频设备...")

        input_devices = self.config_manager.get_input_devices()
        output_devices = self.config_manager.get_output_devices()

        print(f"\n📥 发现 {len(input_devices)} 个输入设备:")
        for i, device in enumerate(input_devices):
            status = "✅ 活跃" if device.get('active') else "⭕ 非活跃"
            print(f"  {i+1}. {device['name']} {status}")

        print(f"\n📤 发现 {len(output_devices)} 个输出设备:")
        for i, device in enumerate(output_devices):
            status = "✅ 活跃" if device.get('active') else "⭕ 非活跃"
            print(f"  {i+1}. {device['name']} {status}")

デバイス状態のチェック

# utils/audio_config/check_audio_devices.py
def check_audio_status():
    """检查音频设备状态"""
    print("🔊 音频设备状态检查")
    print("=" * 40)

    try:
        # 1. 检查PulseAudio状态
        print("\n1️⃣ PulseAudio 服务状态:")
        pa_status = subprocess.run(['pulseaudio', '--check'], 
                                 capture_output=True, text=True)
        if pa_status.returncode == 0:
            print("   ✅ PulseAudio 服务正常运行")
        else:
            print("   ❌ PulseAudio 服务未运行")

        # 2. 检查默认设备
        print("\n2️⃣ 默认音频设备:")

        default_sink = get_default_sink()
        if default_sink:
            print(f"   🔊 输出设备: {default_sink}")
        else:
            print("   ❌ 未设置默认输出设备")

        default_source = get_default_source()
        if default_source:
            print(f"   🎤 输入设备: {default_source}")
        else:
            print("   ❌ 未设置默认输入设备")

        # 3. 检查项目配置
        print("\n3️⃣ 项目配置检查:")
        check_project_config()

    except Exception as e:
        print(f"❌ 检查过程中发生错误: {e}")

トラブルシューティングガイド

よくある質問

1. 音声を再生しても音が出ない

症状:TTS や音楽を再生しても音が出力されない 対処手順:

# 1. 检查音频设备状态
python utils/audio_config/check_audio_devices.py

# 2. 测试系统音频
pactl info

# 3. 检查音量设置
pactl list sinks

# 4. 测试音频播放器
python test/audio_tests/test_audio_player_new.py

2. ASR による認識が機能しない

症状:音声認識が反応しない、または誤認識率が高い 対処手順:

# 1. 检查麦克风设备
arecord -l

# 2. 测试录音功能
python test/audio_tests/test_recording_simple.py

# 3. 检查VAD模型
ls -la Backend/model/vad/

# 4. 查看ASR日志
tail -f backend.log | grep ASR

3. デバイスの競合

症状:複数の音声コンポーネントが同時に動作しない 解決方法:

# 在代码中使用AudioDeviceManager协调访问
access_granted = audio_device_manager.request_device_access_sync(
    AudioDeviceType.MUSIC_PLAYER,
    device_info={"music_name": music_name},
    force_stop_others=True  # 强制停止其他播放器
)

パフォーマンス最適化の推奨事項

1. バッファサイズのチューニング

# config.yaml中的音频配置
audio_player:
  blocksize: 1024      # 减小延迟,增加CPU使用
  buffer_size: 50      # 增加缓冲,提高稳定性
  min_prebuffer: 2     # 预缓冲大小

2. デバイス選択の最適化

# 优先选择低延迟设备
preferred_devices = [
    "hw:0,0",           # 硬件设备,延迟最低
    "plughw:0,0",       # 插件设备,兼容性好
    None                # 系统默认,稳定性好
]

3. メモリ使用量の最適化

# 音频数据块大小控制
OPTIMAL_CHUNK_SIZE = 1024 * 4  # 4KB
MAX_BUFFER_SIZE = 1024 * 1024  # 1MB

def optimize_audio_buffer(audio_data):
    """优化音频缓冲区使用"""
    if len(audio_data) > MAX_BUFFER_SIZE:
        # 分块处理大音频数据
        return split_audio_chunks(audio_data, OPTIMAL_CHUNK_SIZE)
    return audio_data

オーディオシステムは ASR-LLM-TTS の中核となる基盤であり、綿密に設計されたアーキテクチャと最適化戦略により、音声インタラクションの滑らかさと安定性を確保しています。