Files
xiaoxia-saas/deploy/gpu_worker/MUSELIP_ANALYSIS.md
T
xiaoxia 8ebccd61d9 fix: 集成真实MuseTalk推理 + TTS style/speed/volume参数全链路修复
MuseTalk推理修复(P0):
- 替换_run_inference() stub代码为真实MuseTalk推理逻辑
- 新增音频预处理:任意格式→16kHz mono 16bit WAV
- 模型懒加载(VAE+UNet+PE+Whisper),首次推理后复用
- 使用mirror indexing循环帧,消除视频循环边界跳变
- bbox_shift参数可通过请求配置(范围-5~5)
- FP16推理支持,节省RTX2060显存
- /health接口增加模型加载状态信息

TTS参数透传修复(P0):
- schemas新增style字段(TTSSynthesizeRequest/TTSPreviewRequest/CreateLipsyncJobRequest/AiAvatarTtsPreviewRequest)
- schemas新增volume字段(TTSSynthesizeRequest/CreateLipsyncJobRequest)
- cosyvoice_service新增STYLE_INSTRUCTION_MAP和build_style_instruction()
- submit_synthesize_task新增style/pitch参数支持
- workflow start_synthesis/resynthesize/submit_segments全链路传递style/volume/pitch
- tts.py路由/lipsync.py路由/lipsync_service.py全链路透传新参数
- lipsync_tts.py Celery task支持style/volume参数
2026-09-20 15:52:53 +08:00

8.3 KiB
Raw Blame History

MuseTalk 口型精度问题分析与修复方案

问题总结

运维逐帧分析确认:

  • ✅ 音频对齐正常(0ms偏移,相关系数0.99997)
  • ❌ 口型只跟音频能量张合,没有音素级精度(发o/u没有圆唇,发m/b/p没有闭唇,静音段嘴巴还张着)
  • ❌ 5s循环边界有视觉跳变(帧间差异是正常的3-4倍)

根因分析

根因 1:musetalk_server.py 的 _run_inference() 是 STUB 代码(最严重)

deploy/gpu_worker/musetalk_server.py 第 343-466 行:

# 2. 模拟 MuseTalk 推理:输入原视频帧 + 全量音频,输出音频时长的无声画面。
# TODO: 替换为 MuseTalk 真实推理逻辑。
logger.warning("使用示例推理逻辑,未实际调用 MuseTalk 模型")

当前代码从未调用 MuseTalk 模型。整个推理流程是:

  1. 从视频提取帧(ffmpeg)
  2. 生成一段循环原视频的无声画面(ffmpeg -stream_loop)
  3. 用 ffmpeg -c:v copy 封装音频

根本没有 MuseTalk 推理! 所以"口型只跟能量张合"可能是因为 MuseTalk 的实际推理代码没被调用。

根因 2:音频格式未预处理

gpu_worker.py 把音频下载为 input_audio.bin(第 360 行),直接透传给 musetalk_server.py。 musetalk_server.py 保存为 input_audio.wav(第 527 行)但没有做任何格式转换。

CosyVoice TTS 输出的音频格式:

  • 采样率: 22050Hz(cosyvoice_sample_rate = 22050)
  • 格式: MP3(cosyvoice_format = "mp3")

MuseTalk 期望的输入:

  • 采样率: 16000Hz
  • 声道: mono
  • 格式: WAV(16bit PCM)

22050Hz MP3 → 16kHz mono 16bit WAV 的重采样转换完全没有做。

这会导致 MuseTalk 的 whisper feature extractor 拿到错误采样率的音频,提取的 mel 频谱特征频率错位,音素识别完全错误。

根因 3:循环边界无过渡帧

_mux_video_with_audio() 的兜底路径(第 241-286 行)用 ffmpeg -stream_loop -1 直接循环视频,循环边界处:

  • 最后一帧 → 第一帧:没有过渡,硬切
  • 帧间差异是正常帧的 3-4 倍

修复方案

修复 1:实现真正的 MuseTalk 推理调用

_run_inference() 必须调用真实的 MuseTalk 模型。参考 MuseTalk 官方推理流程:

def _run_inference_real(video_path, audio_path, output_path):
    """真正的 MuseTalk 推理 — 替换 stub。"""
    # 1. 音频预处理:转换为 16kHz mono 16bit WAV
    preprocessed_audio = audio_path.parent / "audio_16k_mono.wav"
    _preprocess_audio(audio_path, preprocessed_audio)
    
    # 2. 调用 MuseTalk 推理
    # MuseTalk 代码在 ~/projects/MuseTalk/ 下
    import sys
    muse_dir = Path.home() / "projects" / "MuseTalk"
    if str(muse_dir) not in sys.path:
        sys.path.insert(0, str(muse_dir))
    
    from musetalk.utils.utils import get_file_type
    from musetalk.whisper.audio2feature import load_audio
    
    # MuseTalk 内部推理 API(具体取决于部署的 MuseTalk 版本)
    # 典型调用:
    # model = MuseTalkModel(...)
    # result = model.infer(video=video_path, audio=preprocessed_audio)

修复 2:音频预处理(必须)

在 musetalk_server.py 中添加 _preprocess_audio() 函数:

def _preprocess_audio(input_path: Path, output_path: Path, target_sr: int = 16000) -> None:
    """将输入音频转换为 MuseTalk 要求的格式:16kHz mono 16bit WAV。
    
    MuseTalk 的 whisper audio2feature 要求 16kHz 采样率,
    当前 TTS 输出 22050Hz MP3,不转换会导致 mel 频谱错位、
    音素特征提取错误,口型只跟能量不跟音素。
    """
    cmd = [
        "ffmpeg", "-y",
        "-i", str(input_path),
        "-ar", str(target_sr),    # 重采样到 16kHz
        "-ac", "1",                # 单声道
        "-sample_fmt", "s16",      # 16bit PCM
        "-f", "wav",               # WAV 格式
        str(output_path),
    ]
    _run_ffmpeg(cmd, timeout=60)
    
    if not output_path.exists() or output_path.stat().st_size < 100:
        raise RuntimeError(f"音频预处理失败: {output_path}")
    
    # 验证输出格式
    try:
        import wave
        with wave.open(str(output_path), 'rb') as wf:
            sr = wf.getframerate()
            ch = wf.getnchannels()
            sw = wf.getsampwidth()
            if sr != target_sr or ch != 1 or sw != 2:
                logger.warning("音频格式异常: sr=%d ch=%d sw=%d", sr, ch, sw)
    except Exception as e:
        logger.warning("无法验证 WAV 格式: %s", e)
    
    logger.info("音频预处理完成: %s → %s (%dHz mono 16bit WAV)", 
                input_path.name, output_path.name, target_sr)

修复 3:循环边界 crossfade

在兜底循环路径中,使用 ffmpeg xfade 滤镜在循环边界处混合:

def _mux_with_loop_crossfade(video_path, audio_path, output_path, crossfade_frames=3):
    """循环视频 + crossfade 过渡,减少循环边界跳变。"""
    fps = _get_video_fps(video_path)
    crossfade_duration = crossfade_frames / fps  # 通常 0.12s (3帧@25fps)
    video_duration = _get_media_duration(video_path)
    
    # 使用 tpad 滤镜在视频末尾添加 crossfade
    # 方案:用 concat 把 [视频 + 视频前N帧crossfade] 拼起来
    cmd = [
        "ffmpeg", "-y",
        "-stream_loop", "-1",
        "-i", str(video_path),
        "-i", str(audio_path),
        "-filter_complex",
        f"[0:v]split=2[v1][v2];"
        f"[v2]trim=0:{crossfade_duration},setpts=PTS-STARTPTS+{video_duration - crossfade_duration}/TB[xfade_in];"
        f"[v1][xfade_in]xfade=transition=fade:duration={crossfade_duration}:offset={video_duration - crossfade_duration}[looped];"
        f"[looped]format=yuv420p[out]",
        "-map", "[out]",
        "-map", "1:a:0",
        "-c:v", encoder,
        "-preset", preset,
        "-c:a", "aac", "-b:a", "128k",
        "-t", f"{audio_duration:.3f}",
        str(output_path),
    ]

但更简单的方案:如果 MuseTalk 正确处理了全量音频(输出时长=音频时长),就不需要兜底循环了。crossfade 只是兜底路径的优化。

修复 4:推理参数

MuseTalk 的关键推理参数:

  • bbox_shift: 口型区域偏移量,默认 0,范围 [-5, 5]。影响口型位置
  • batch_size: 推理批次大小,RTX2060 6G 显存建议 4-8
  • 视频帧率:MuseTalk 期望 25fps,当前已正确获取 fps

完整修复优先级

优先级 修复 影响
P0 _run_inference() 调用真实 MuseTalk 模型 没有这个其他都没意义
P0 音频预处理:22050Hz MP3 → 16kHz mono 16bit WAV 音素特征正确提取
P1 循环边界 crossfade 减少视觉跳变
P2 bbox_shift 参数暴露为可配置 微调口型位置

在 RTX2060 上调试需要什么

  1. 确认 MuseTalk 推理代码:

    # 在 RTX2060 上
    ls ~/projects/MuseTalk/
    cat ~/projects/MuseTalk/musetalk_server.py  # 看实际部署版本
    
  2. 验证音频格式:

    # 在 musetalk_server.py 的推理前添加日志
    ffprobe -v error -show_entries stream=sample_rate,channels,codec_name,bits_per_sample \
      -of default /tmp/musetalk_*/input_audio.wav
    
  3. 提取中间结果对比:

    # 对比 TTS 原始音频 vs 预处理后的 16kHz WAV
    # 用 whisper 提取 mel 特征并可视化
    python -c "
    import whisper
    model = whisper.load_model('tiny')
    audio = whisper.load_audio('/tmp/test_audio.wav')  # 预处理后的
    audio = whisper.pad_or_trim(audio)
    mel = whisper.log_mel_spectrogram(audio).to(model.device)
    # 保存 mel 特征供可视化
    import numpy as np
    np.save('/tmp/mel_features.npy', mel.cpu().numpy())
    "
    
  4. MuseTalk 推理中间帧:

    # 在 MuseTalk 推理代码中保存中间帧到 /tmp/debug_frames/
    # 对比输入帧 vs 推理后帧 vs 原视频帧
    

建议的下一步

  1. 先在 RTX2060 上 cat ~/projects/MuseTalk/musetalk_server.py 确认实际部署的代码
  2. 确认 MuseTalk 是否真的被调用(看日志有没有 "使用示例推理逻辑" 的 warning)
  3. 如果确认是 stub,需要把真正的 MuseTalk 推理逻辑集成到 _run_inference()
  4. 同时修复音频预处理(P0),这个无论推理代码如何都是必须的