☰
FFmpeg aresample 重采样详解:解决录屏音画不同步的音频格式统一方案
2026/10/14 1:35:50 网站建设 项目流程

1. 为什么录屏输出前一定要过一遍重采样

上一篇文章我们把采集到的 PCM 直接封装进容器,结果在播放器里出了两个问题:一是声音明显变调,二是画面和声音越看越不同步。后来查清楚了,不怪采集设备,怪我自己没在滤镜阶段把音频参数统一。今天这篇,我把aresample重采样 PCM 这件事从头到尾理一遍。它不复杂,但如果你只是记了一条命令,遇到 44.1kHz、16bit、mono、双声道混音、音画漂移这些情况,还是会翻车。

1.1 采集端 PCM 的多样性

先说一个很多人容易忽略的事实:声卡采集出来的 PCM,并不替你保证格式。同样是桌面麦克风,有的驱动默认 44.1kHz / s16 / mono,有的驱动默认 48kHz / float / interleaved。再加上系统混音、虚拟声卡、HDMI 音频采集,同一个项目里出现三四种音频参数非常正常。

而你的视频轨通常是一个固定的帧率,比如 30fps;容器和编码器又希望音频轨的采样率、位深、声道数是确定的。你需要在一条流水线里把这些不一致全部抹平。aresample就是专门干这个的滤镜,它内部依赖 libswresample,能把采样率、采样格式、声道布局一次都转到位。

如果这部分不做,最典型的后果就是把明明是 48kHz 采出来的 PCM 当成 44.1kHz 封装,播放时整体音调升高、语速变快;或者反过来,声音变慢发闷。这种问题往往在电脑自带的播放器里不明显,一放到剪辑软件或直播推流软件里就暴露了。

1.2 aresample 与 -ar 的分工

很多人问我:那直接写-ar 48000不就行了?这两个东西确实都能让最终文件变成 48kHz,但位置不一样。

-ar是输出参数,它位于编码器之前,由 FFmpeg 自动插入一个重采样步骤;而aresample是滤镜,你可以把它放在滤镜图的任意位置。录屏项目里经常要先接volume、dynaudnorm、amix这些滤镜,这些滤镜对参数的敏感度不一样,所以我习惯在滤镜图里先用aresample把格式收口,再往下走。如果你只用-ar,它管不到滤镜中间环节,遇到复杂 filter_complex 的时候,格式匹配问题依然会出现。

另一个区别是aresample能顺带处理音画时间对齐。靠-ar只改采样率,改不了音频流和视频流之间的时间戳漂移。所以我的原则是:简单输出用-ar没问题,但录屏项目里一旦涉及多路音频、混音、长时间录制,统一在滤镜图里用aresample收口,后面才不会被格式问题追着跑。

2. aresample 核心参数拆解

2.1 参数怎么写

最简单的写法是aresample=48000,第一个裸参数就是输出采样率,含义是把当前 PCM 重采样到 48kHz。FFmpeg 里这个 option 的规范名是sample_rate,但直接传裸参数不写名字也没问题。

如果要同时管位深和声道,就带选项:

ffmpeg -f pulse -i default \ -af "aresample=48000:osf=s16:ochl=stereo" \ -c:a pcm_s16le mic.wav

这里osf是输出采样格式,ochl是输出声道布局。注意这些选项是aresample自带的,不是aformat,所以写在同一个滤镜后面,用冒号分隔,别用逗号。如果你把场景和声道解耦,比如先重采样再做声道纠正,也可以拆成两个滤镜节点:

-af "aresample=48000,aformat=sample_fmts=s16:channel_layouts=stereo"

不过这种写法有点绕,一般没有必要。直接让aresample一次性把采样率、位深、声道都定了,才是最省事的做法。

2.2 resampler 选 swr 还是 soxr

aresample默认的重采样器是 swr,质量足够好。如果你编译 FFmpeg 时启用了 libsoxr,也可以指定resampler=soxr。soxr 在高倍率采样率转换时精度略好,比如 48kHz 转 8kHz 这种跨度大的场景,改善会更明显。但在录屏场景里,44.1kHz 和 48kHz 之间只是不到 9% 的倍率变化,swr 和 soxr 的听感差异很小。

不要因为网上吹 soxr 就非要全局开启。soxr不可用时 FFmpeg 会直接报错,反而打断录屏任务。想知道当前构建支持什么,一条命令就能查:

ffmpeg -h filter=aresample 2>&1 | grep -A3 resampler

如果你看到Available resamplers: swr,那就不用纠结,默认就很好。如果你的发行版编译了 soxr,也只在特定任务里显式开启,不要写进通用脚本。

2.3 osf 和 ochl 的关系

osf和ochl这两项经常跟采样率一起出现。osf=s16表示最终输出 16bit 整数 PCM,ochl=stereo表示最终输出双声道。

举个例子:录制的时候麦克风输入是 mono,但你要做两声道视频,那就让aresample顺手把它提升成 stereo。mono 源转 stereo 是两个声道复制同样的样本,这是 libswresample 默认行为,日常录屏完全够用。反过来,如果系统声音是 5.1 而视频只需要双声道,ochl=stereo会自动做 downmix。这个 downmix 用的是默认矩阵,效果中规中矩,适合普通录屏;如果你要做专业的音频混音,再单独用pan滤镜精调。

有一点要注意:如果osf=s16处理完之后你又接了别的滤镜,而那个滤镜内部要求 float 格式,FFmpeg 会自动再插一个转换节点。这种隐式转换会拖慢一点速度,但不会把音质毁掉。真正需要担心的是电平问题,下面说。

2.4 电平与削波控制

我踩过比较多的地方是浮点转 s16。很多采集设备的默认格式是 float32,峰值完全可能超过 1.0,一旦转成 s16 就会削波。削波之后先是尖锐的爆音,然后整体变闷,波形看起来像被砍了头。

aresample本身不做电平补偿,它只做格式转换。所以如果你知道输入信号偏大,在它之前先加一个volume或者dynaudnorm,会安全很多。比如:

ffmpeg -f pulse -i default \ -af "volume=0.8,aresample=48000:osf=s16:ochl=stereo" \ -c:a pcm_s16le safe.wav

这里volume=0.8相当于先留出 20% 的 headroom,再做 s16 转换,削波概率大幅下降。当然,如果源本身声音很小,volume反而会放大底噪,所以没有万能值,只能说“先用音量分析工具看峰值,再决定要不要压”。

3. 录屏专用:用 async 解决音画时间漂移

3.1 为什么音频会漂

录屏项目里,音频设备时钟和视频帧率并不是同一个时钟。桌面抓取按视频时间戳推进,声卡按自己的晶振推进,两者总有一点偏差。短录 5 分钟可能只有几十毫秒差异;录 1 小时,漂移可能累积到 1 秒以上。如果不做处理,封装时 FFmpeg 可能直接按第一个时间戳对齐,后面越来越偏。

这个问题在电影文件、普通音视频合成里不明显,因为素材文件的时间戳是写死的。但录屏是边采边封,采集时间和播放时间天然存在抖动,所以必须有一个机制来动态补偿。

3.2 async 参数怎么配

aresample的async参数就是干这个的。推荐写成:

-af "aresample=48000:osf=s16:ochl=stereo:async=1:first_pts=0"

async=1的意思是:当音频时间戳比视频时间戳短的时候补静音,比视频长的时候丢样本,让两条轨在总体时长上对齐。first_pts=0是把第一条音频样本的时间戳归零,避免一开始就有固定偏移。

很多网上命令还会跟max_soft_comp、min_hard_comp一起出现,这两个参数控制软补偿和硬补偿的边界。默认值在录屏场景里基本够用,我建议先别乱调。如果录出来的长视频仍然有轻微偏移,再考虑把max_soft_comp调大一点,让它在更长时间里慢慢拉齐,不要瞬间硬切。

3.3 多路音频合并前必须重采样

如果同一个项目要录麦克风和系统音箱两路音频,不要先把两路一路存下来再混合。正确做法是两路各自先用aresample统一格式,再进amix混合。否则amix很容易报错,提示 “Input link parameters do not match”,或者混出来要么缺声道、要么音量翻倍。

我自己常用的一路麦克风一路系统声音的写法是这样:

-filter_complex "\ [1:a]aresample=48000:osf=s16:ochl=stereo:async=1:first_pts=0[mic];\ [2:a]aresample=48000:osf=s16:ochl=stereo:async=1:first_pts=0[desk];\ [mic][desk]amix=inputs=2:duration=longest:normalize=0[aout]"

这里normalize=0很关键。默认情况下amix会降低音量防止混音削波,但在录屏场景里,两个输入通常都不是同时满电平,反而需要保留原始响度。关掉归一化之后,输出音量更接近实际听到的效果。

4. 完整命令与验证

4.1 单音频源录屏命令

先给一个只有一路音频的最简命令,以 Linux 桌面为例。视频用 x11grab 抓屏,音频用 PulseAudio 的默认输入源:

ffmpeg -y \ -f x11grab -framerate 30 -video_size 1920x1080 -i :0.0 \ -f pulse -i default \ -map 0:v -map 1:a \ -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p \ -af "aresample=48000:osf=s16:ochl=stereo:async=1:first_pts=0" \ -c:a aac -b:a 192k \ desktop_record.mkv

这条命令里,-map 0:v -map 1:a明确指定画面来自第一个输入、声音来自第二个输入,避免多输入时 FFmpeg 自作主张。-af里的aresample把所有音频统一成 48kHz / 16bit / stereo,同时做时间对齐。这样出来的文件,AAC 编码器拿到的输入格式是一致的,不会再插入额外的隐式转换。

4.2 双音频源混合录屏命令

更常见的是我和别人开语音会议时要同时录系统声音和麦克风,这时要用-filter_complex:

ffmpeg -y \ -f x11grab -framerate 30 -video_size 1920x1080 -i :0.0 \ -f pulse -i default \ -f pulse -i monitor_source \ -filter_complex "\ [1:a]aresample=48000:osf=s16:ochl=stereo:async=1:first_pts=0[mic];\ [2:a]aresample=48000:osf=s16:ochl=stereo:async=1:first_pts=0[desk];\ [mic][desk]amix=inputs=2:duration=longest:normalize=0[aout]" \ -map 0:v -map "[aout]" \ -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p \ -c:a aac -b:a 192k \ demo_mix.mkv

先各自aresample再amix,最大的好处是格式明确。不管第二个输入是 mono 还是 stereo、是 44.1k 还是 48k,经过第一轮重采样后都变成同一个标准,amix只是在做叠加,不会再因为格式不一致翻车。

4.3 用 ffprobe 验证结果

录完不要急着发,先用ffprobe看输出文件的音频流:

ffprobe -v error \ -show_entries stream=index,codec_type,sample_rate,channels \ -select_streams a:0 demo_mix.mkv

正常会看到类似:

[STREAM] index=1 codec_type=audio sample_rate=48000 channels=2 [/STREAM]

只要sample_rate=48000、channels=2,说明重采样和目标声道布局已经生效。这里不用看sample_fmt,因为显示的是解码器输出格式,不是编码前格式,看到fltp或s16都不代表你的aresample配错了。

如果还想检查音量有没有削波,可以再跑一条:

ffmpeg -i demo_mix.mkv -af volumedetect -f null -

输出里的max_volume越接近 0dB 越危险。遇到max_volume: 0.0 dB或“clipping”字样,基本就是前面说的削波问题,要回头加volume或调低采集电平。

5. 常见问题与排查技巧

5.1 问题速查表

现象可能原因处理方式
输出后频繁爆音浮点转 s16 时削波重采样前加volume或dynaudnorm,留出至少 1dB 余量
声音整体偏高或偏低采集端采样率标签与实际数据不匹配确认设备真实采样率,再用aresample统一到目标值
resampler=soxr报错编译时没有启用 libsoxr改回默认 swr,或用ffmpeg -h filter=aresample确认支持项
filter_complex 报参数不匹配amix前各路采样率、位深、声道不一致每路先各自aresample,再进amix
长时间录屏后音画漂移音频时钟与视频帧率不同步用aresample=async=1:first_pts=0做动态对齐
-ar和滤镜都写了 48000,仍然感觉双重转换滤镜输出不是 48k,编码器前又转了一次检查滤镜链输出参数,尽量只在一处指定目标格式

5.2 排查思路实录

别一上来就怀疑aresample。我在项目里碰到的很多问题,最后查出来都是别的原因。

有一次录完声音出现明显的“水声”,我以为是重采样算法问题,换 swr、换 soxr、调 cutoff 都没用。最后发现是麦克风采集时系统里开了回声消除,那个处理模块本身改变了相位,和重采样一点关系都没有。

还有一次amix混出来一边声音大一边声音小,原因是第一路是 stereo、第二路是 mono,mono 被复制成 stereo 后音量感不同。解决办法就是在进amix之前强制ochl=stereo,并且检查输入源的电平差异。

排查的顺序建议是:先用ffprobe看输入源的真实参数,再确认滤镜链里每个关键节点前后的格式,最后看输出文件的时长和音画差。如果这三层都没有问题,那aresample几乎没有可能出错。

6. 关于采样率选择,我的一点实操心得

录屏类项目我一般把内部标准音轨定为 48kHz / 16bit / stereo。原因有两个:几乎所有主流视频容器和常用编码器都支持 48kHz;其次,从 44.1kHz 或 48kHz 相互转换时,48k 作为中间标准能减少二次重采样。

麦克风如果是 mono,我会在滤镜里显式提升到 stereo;系统声音如果是 5.1,我会先 downmix 到 stereo。这个规则写进项目脚本后,再没出现因为音频格式不一致导致的返工。

还有一个实用小技巧:把重采样参数单独抽成变量,不要在每个命令里手写。比如脚本里写:

AUDIO_FILTER="aresample=48000:osf=s16:ochl=stereo:async=1:first_pts=0"

录屏命令、单独录麦克风、推流命令全都引用同一个变量。以后如果要改成 44.1kHz,只改一处就行。踩过几次坑之后,你会发现大部分音频问题都不是“重采样没做”,而是“参数没有统一”。aresample就是那把把乱掉的采样率、位深、声道和时钟拉回同一标准轨道的尺子,用对了,后面的活就顺了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询