Audio-Visual Flamingo 答非所问,先别急着归因到模型能力。同一个视频有时描述准确、有时跑偏,更常见的情况是每次送进模型的输入并不是同一份:音频被重采样过、视频抽帧密度不同,或者音视频时长本身没有对齐。排查时建议按“先打印输入参数,再排除生成随机性,然后只改音频采样率,只改帧率或抽帧间隔,最后核对音视频时长”的顺序走。每一步只动一个变量,输出才有可比性。
判断方向:把每次输入的音频采样率、视频帧率或抽帧间隔、音视频时长记录成同一种格式,再固定温度和随机种子跑三次。三次输出稳定但内容偏,优先检查单变量输入差异;三次输出本身就不一致,先处理随机性。适用场景是同一素材重复跑结果不稳定;边界是模型侧期望的采样率和帧数,要以实际预处理代码或配置为准,不能凭猜测。
把每次输入的参数打印出来
打印动作放在预处理函数入口,不要等模型输出后再回查。下面是一段可替换的骨架:用 ffprobe 读容器层参数,用解码后的音频信息核对实际采样率,模型侧参数从自己的配置对象里取。字段名可以改,但每次运行必须输出同一组字段,才能横向对比。
import json, subprocess
import soundfile as sf
def probe(path):
raw = subprocess.check_output([
"ffprobe", "-v", "error", "-print_format", "json",
"-show_streams", "-show_format", path
])
info = json.loads(raw)
audio = next((s for s in info["streams"] if s["codec_type"] == "audio"), {})
video = next((s for s in info["streams"] if s["codec_type"] == "video"), {})
# 音频已抽成 wav/flac 时,用 soundfile 读解码后的真实采样率
try:
af = sf.info(path)
decoded_sr, channels = af.samplerate, af.channels
except Exception:
decoded_sr, channels = None, None
return {
"path": path,
"audio_sr_container": audio.get("sample_rate"),
"audio_sr_decoded": decoded_sr,
"audio_channels": channels,
"audio_duration": float(audio.get("duration", 0) or 0),
"video_fps": video.get("r_frame_rate"),
"video_avg_fps": video.get("avg_frame_rate"),
"video_duration": float(video.get("duration", 0) or 0),
"format_duration": float(info["format"].get("duration", 0) or 0),
}
record = {
**probe(video_path),
"model_audio_sr": cfg.audio_sample_rate,
"model_sample_fps": cfg.video_sample_fps,
"model_num_frames": cfg.num_frames,
"clip_sec": cfg.clip_seconds,
}
print(json.dumps(record, ensure_ascii=False))
字段含义看三组就够:audio_sr_container 与 audio_sr_decoded 是否一致,能暴露“容器写的是 44.1k、实际却按 16k 喂进去”的情况;video_fps 与 model_sample_fps 是否对得上,能暴露抽帧密度变化;audio_duration、video_duration、format_duration 三者是否接近,能暴露尾部缺失或轨道偏移。记录模板建议直接落成一行 CSV 或 JSON,字段包括:run_id,path,audio_sr_container,audio_sr_decoded,audio_channels,audio_duration,video_fps,video_duration,format_duration,model_audio_sr,model_sample_fps,model_num_frames,clip_sec,output_ok。其中 output_ok 先由人工标记“描述是否对得上画面”,后续排查只对比这一列和参数列的差异。
直接读 mp4 时 soundfile 可能不认,这时以 ffprobe 或实际解码库读到的值为准,不要混用两套口径。模型侧期望采样率通常写在预处理配置或音频编码器前面那一层,需要结合环境确认;如果找不到配置项,就在预处理函数里加断言,把实际送进模型的张量采样率打印一次。
同一素材重复跑三次看输出是否稳定
输入参数记录好之后,先确认输出漂移是不是随机性造成的。如果同一份输入跑三次,描述每次都不一样,这时去查采样率或帧率,很容易把随机生成误判成输入错位。先把生成侧固定住:关闭采样,或者把温度设为 0,top_p 设为 1,并固定随机种子。设置位置通常在模型 generate 的生成配置里;如果走服务接口,就看请求体里的 temperature、top_p、seed 字段。
gen_config = {
"do_sample": False, # 或保留采样但 temperature=0
"temperature": 0.0,
"top_p": 1.0,
"seed": 1234,
"max_new_tokens": 128,
}
三次输出按固定格式对照,不要只凭印象说“差不多”:
- 运行 1:seed=1234,temperature=0,输出摘要记一句,标出是否提到画面上实际存在的物体。
- 运行 2:同参数再跑,比较输出是否逐字一致;不一致说明生成侧仍有随机源,例如采样未真正关闭或分布式种子不同。
- 运行 3:同参数再跑,若三次一致但内容仍然偏,才进入输入参数的单变量排查。
需要留意的边界是:温度设 0 后输出稳定,不代表输入参数正确,只代表随机性被压住了;反过来,三次输出一致也不能证明采样率没问题。这一步的作用只是把“随机漂移”和“系统性偏差”分开。
只改音频采样率再跑
确认生成稳定后,先隔离音频侧。保持视频抽帧方式、帧数、提示词完全不变,只把音频重采样到另一个采样率,再对比输出。可以用 ffmpeg 抽出单声道 16k 音频,也可以用 sox 做同样的重采样,选一种并记录命令。
ffmpeg -i input.mp4 -vn -ac 1 -ar 16000 input_16k.wav
sox input.wav -r 16000 -c 1 input_sox.wav
对照记录建议写成两行:第一行是原始采样率(例如容器里是 44.1k),第二行是重采样后的 16k,两行都填同一段视频、同一组抽帧参数、同一个 seed。比较时看输出是否从“答非所问”变成“基本对得上”,或者反过来。如果只改音频采样率输出就明显变化,说明音频侧的采样率或声道下混是可疑变量;如果输出基本不变,音频采样率不是主因,继续查画面侧。风险边界是重采样本身会改变音频内容,尤其是立体声下混为单声道时,不能把差异全部归因于采样率数值。
只改帧率或抽帧间隔再跑
画面侧的隔离方式同理:音频文件保持不动,只改抽帧密度。ffmpeg 可以按固定 fps 抽帧,也可以按帧间隔抽帧。下面两种写法选一种,不要同时改分辨率或裁剪。
# 每秒抽 1 帧
ffmpeg -i input.mp4 -vf fps=1 frames/%04d.jpg
# 每隔 15 帧取 1 帧
ffmpeg -i input.mp4 -vf "select='not(mod(n,15))'" -vsync vfr frames/%04d.jpg
模型侧的 num_frames 和这里的抽帧 fps 是两件事:前者是送进模型的帧数上限,后者是取帧密度。记录格式建议固定为:条件 | 抽帧方式 | 实际帧数 | 覆盖时长 | 输出摘要 | 是否答非所问。例如“fps=1,抽 12 帧,覆盖 12 秒”和“fps=2,抽 24 帧,覆盖 12 秒”两行对照。如果帧数变了但覆盖时长不变,输出仍偏,说明问题不在抽帧密度;如果帧数没变而覆盖时长变了,要怀疑视频时长读取或截断位置不一致。
核对音视频时长是否一致
最后核对时长。用音频流时长和视频流时长做差,也可以用容器总时长做二次确认:
diff = abs(audio_duration - video_duration)
# 同时看轨道起始偏移
ffprobe -v error -show_entries stream=index,codec_type,start_time,duration \
-of json input.mp4
时长差的计算方式要固定:统一用秒,保留三位小数,分别记录音频流、视频流和容器时长。阈值不要拍脑袋定一个绝对秒数,建议先按“一个视频帧间隔”或业务可接受的对齐误差来设;超过阈值就当作可疑输入处理。常见动作有三个:按较短时长裁剪,让音视频等长;给缺失的一端补静音或补黑帧;重新封装时加 -shortest,避免尾部拖长。如果 start_time 本身不为 0 且两条轨道不一致,优先怀疑封装偏移,而不是采样率。
排查顺序可以压成一张表,避免来回跳:
| 顺序 | 检查项 | 判定信号 | 下一步 |
|---|---|---|---|
| 1 | 打印输入参数 | 同一素材的参数记录字段缺失或每次不同 | 先补齐记录,再谈对比 |
| 2 | 三次重复跑 | 输出逐字漂移 | 固定温度、top_p、seed,仍漂移则查生成配置 |
| 3 | 只改音频采样率 | 输出随采样率变化明显 | 核对模型侧期望采样率与声道处理 |
| 4 | 只改帧率或抽帧间隔 | 输出随帧数或覆盖时长变化 | 核对 num_frames 与实际抽帧密度 |
| 5 | 核对音视频时长 | 时长差超过设定阈值,或 start_time 不一致 | 裁剪、补齐或重新封装后再跑 |
这三类不一致的判定信号并不相同:采样率错位常表现为声音内容还在但描述偏向另一段语义;帧率或抽帧间隔错位常表现为画面细节漏看、动作顺序错乱;时长不一致则更容易出现尾部内容对不上、描述后半段跑偏。按上面的顺序逐项排除,比一上来就换模型或改提示词更可控。