时间戳错乱、音画不同步?virtual-audio-capture-grabber-device 的 FillBuffer 时间戳与断流调试手记

发布时间:2026/8/27 16:41:41
时间戳错乱、音画不同步?virtual-audio-capture-grabber-device 的 FillBuffer 时间戳与断流调试手记 时间戳错乱、音画不同步virtual-audio-capture-grabber-device 的 FillBuffer 时间戳与断流调试手记【免费下载链接】virtual-audio-capture-grabber-devicefree audio capture device to capture all the wave out sound that is playing on your speakers (i.e. record what you hear) for Windows Vista. Releases downloadable in this package:项目地址: https://gitcode.com/gh_mirrors/vi/virtual-audio-capture-grabber-devicevirtual-audio-capture-grabber-device 是一款 WindowsVista上的免费音频采集设备它通过 WASAPI loopback 捕获正在从扬声器播放的声音即录制你听到的内容并把结果以名为virtual-audio-capturer的 DirectShow 音频采集设备提供给 VLC、ffmpeg 等程序。当你遇到录出来的音频时间戳错乱、与画面不同步、或者录制中途断流时问题几乎都藏在两个文件里负责给数据包打时间戳的FillBuffer和负责搬运采样数据的 loopback 捕获线程。本文带你沿着源码把这套机制拆开看一遍。一、先搞懂数据流声音是怎么流出来的整个设备是一条三段式流水线构建方式见 how_to_setup_code.txt阶段位置职责1. 捕获loopback-capture.cppWASAPI loopback 抓取混合音频塞进 1MB 共享缓冲2. 中转pBufLocal1MB 环形区 信号量临界区捕获线程与 DirectShow 线程之间的解耦带3. 出样directshow_fillbuffer.cpp从缓冲取数据、计算并打上时间戳交给图形引擎两个关键背景知识是后面一切问题的根源捕获线程是独立的propagateBufferForever线程以THREAD_PRIORITY_TIME_CRITICAL优先级无限循环拉取音频见 loopback-capture.cppFillBuffer 是被下游拉动的VLC/ffmpeg 要数据时DirectShow 才回调FillBuffer它调用LoopbackCaptureTakeFromBuffer取走当前累积的所有数据——取到的字节数是不定长的时间戳必须自己算。二、时间戳错乱的四大成因以及源码里的对策2.1 首包倒推起点为什么第一段可能从过去开始FillBuffer的核心时间戳逻辑在 directshow_fillbuffer.cpp。每取到一个数据包先按字节数换算时长sampleTimeUsed 总字节数 × 10,000,000 / nAvgBytesPerSec对于第一个包bVeryFirstPacket作者的理论是启动瞬间比如 ffmpeg 慢启动积压的大包如果被标成现在才开始会让后续所有时间戳整体后移。所以首包会被倒推一段让起点略微在过去rtStart - sampleTimeUsed见 directshow_fillbuffer.cpp。 负起点在图形刚启动时是预期内的源码注释也专门提醒了这一点L122。2.2 断流后的重对齐用 MAX() 防时间倒流一旦检测到断流bDiscontinuityDetected起点取的是rtStart MAX(上一个包的结束时间, 当前时钟 − 本包时长)即 directshow_fillbuffer.cpp 的MAX(m_rtPreviousSampleEndTime, rtStart - sampleTimeUsed)。这个MAX保证时间戳单调不减——这正是 ffmpeg 抱怨Que input is backward in time / Audio timestamp 329016 329026 invalid的直接解药ChangeLog.txt 里 0.3.6 版本就是专门修这个的。更妙的是无断流时根本不查钟直接接力rtStart m_rtPreviousSampleEndTime前一个包结束处见 directshow_fillbuffer.cpp。源码注释里还留着一段被注释掉的max(rtStart, cur_time)——注释明确写着希望注释掉它能避免 ffmpeg 的时间倒流报错。这是作者用真枪实弹换来的教训。2.3 与图形时钟的 ±1 tick 漂移校正打完全包时间戳后代码还会和图形参考时钟对一次表directshow_fillbuffer.cpp落后时钟超过 1 tick → 结束时间 1超前时钟超过 100ms → 结束时间 −1。这种每秒最多纠 1 tick的蜗牛式校正避免时间戳突然跳变是音画不同步缓慢漂移问题里最容易被忽视的一环。2.4 别忘了 SetSyncPoint 与 PrerollPCM 音频的每个包都要SetSyncPoint(TRUE)directshow_fillbuffer.cppSetPreroll(FALSE)告诉下游这不是预热数据真用。少一个调用某些播放器就会把数据丢弃或错位。三、断流Discontinuity到底是怎么发生的断流不是玄学源码里能数出4 个触发点全部在 loopback-capture.cpp1MB 缓冲溢出捕获线程写入时若pBufLocalCurrentEndLocation expectedMaxBufferSize直接清空缓冲、totalOverflows并置bDiscontinuityDetected trueloopback-capture.cpp。这通常意味着下游VLC/编码器消费太慢比如播放器暂停时。WASAPI 报DATA_DISCONTINUITY标志音频引擎自己告诉你流断了CPU 跟不上、设备切换等处理见 loopback-capture.cpp。首个包上的虚假断流WASAPI 首包常带断流标志代码会识别为 spurious glitch 并容忍L453-L456。暂停/恢复LoopbackCaptureClear会清空缓冲并主动置断流标志让下一包重新对齐时间戳loopback-capture.cppFillBuffer在图形未 Running 期间也会循环清空缓冲等待directshow_fillbuffer.cpp。断流包还会被打上SetDiscontinuity(TRUE)directshow_fillbuffer.cpp让下游知道这里有个空洞。如果你看到画面卡住、声音突然跳一下十有八九是这一路标志在起作用。 静默线程为什么没声音反而更容易出问题WASAPI loopback 在系统完全静音时不产生数据流就死了。对策见 silence_background_thread.cpp另起一个渲染客户端持续往默认播放设备灌静音帧AUDCLNT_BUFFERFLAGS_SILENT保证 loopback 捕获端永远有数据可拉。这就是 loopback-capture.cpp 里SILENT标志预期内、不用告警的原因。调试时若发现纯静音时段后时间戳跳跃先怀疑这个线程没跑起来。四、实操调试清单从日志到命令行项目自带一套ShowOutput调试日志调试时按下面的关键词排查日志关键词含义参考overfilled buffer, cancelling/flushing1MB 缓冲溢出断流已置位loopback-capture.cppIAudioCaptureClient::discontinuity GetBuffer set flagsWASAPI 原生断流loopback-capture.cppaudio discontinuity detectedFillBuffer 正在做断流重对齐directshow_fillbuffer.cpptimestamping audio packet as X - Y逐包时间戳可直接验证单调性directshow_fillbuffer.cppsleeping till graph running图形未 Running缓冲被持续清空directshow_fillbuffer.cpp结束时统计信息total reads / discontinuity blips / overflows由 outputStats() 写入last_output配置项——断流次数多就查下游性能溢出次数多就查缓冲区缓冲大小由 directshow_stuff.cpp 的DecideBufferSize与expectedMaxBufferSize协商默认 1MB。命令行最小复现环境参考 startup_debug_options 里作者自己的调试命令GraphEditWindows SDK 自带挂上virtual-audio-capturer单设备排除一切下游因素ffmpeg 录 5 秒-f dshow -i audiovirtual-audio-capturer -t 5 yo.wav用ffprobe看时间戳是否单调VLC 直连dshow:// :dshow-vdevnone :dshow-adevvirtual-audio-capturer :dshow-caching50。⚠️ README.TXT 明确提醒VLC 的 dshow 缓存至少要 40–50ms否则可能失败——这是被反复踩过的坑。五、关键文件速查文件作用source_code/acam/directshow_fillbuffer.cpp时间戳计算、断流重对齐、防倒流source_code/acam/loopback-capture.cppWASAPI loopback 捕获、1MB 中转缓冲、溢出处理source_code/acam/silence_background_thread.cpp静音保活线程source_code/acam/acam.hCVCam/CVCamStream 滤镜骨架、m_rtPreviousSampleEndTime状态位source_code/acam/directshow_stuff.cpp缓冲区协商、状态机Run/Pause/StopChangeLog.txt0.3.6 修时间倒流、0.4.5/0.4.6 async fix 等历史坑TODO作者自留的时间戳带最大结束时间改进方向六、一句话总结这套设备的时间戳哲学可以概括为三点能接力就不查钟前包结束后包开始、断流就重对齐且只允许单调递增MAX 倒推首包、漂移就蜗牛式校正±1 tick。调不通时先打开ShowOutput日志数一数blips和overflows再对着上面四张表逐层排除——时间戳错乱与断流绝大多数都能在这条链路上找到真凶。【免费下载链接】virtual-audio-capture-grabber-devicefree audio capture device to capture all the wave out sound that is playing on your speakers (i.e. record what you hear) for Windows Vista. Releases downloadable in this package:项目地址: https://gitcode.com/gh_mirrors/vi/virtual-audio-capture-grabber-device创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考