Skip to content

Custom Patches

Martin Vallevand edited this page Jul 13, 2026 · 1 revision

Custom Patches

This build system supports applying custom patches to FFmpeg and mpv source before compiling, via CUSTOM_PATCH_DIR. This page documents the mechanism and the patches currently shipped with this project.


Directory layout

CUSTOM_PATCH_DIR must contain two subdirectories:

CUSTOM_PATCH_DIR/
├── ffmpeg/
│   └── *.patch
└── mpv/
    └── *.patch

Every .patch file found is applied via patch -p1 against the respective source tree before building. Patches are applied in alphabetical order.

CUSTOM_PATCH_DIR=~/patches ./build-libmpv-mingw64.sh v0.41.0

Windows (build-libmpv-mingw64.sh): supported natively — this is where these patches were developed and tested.

Linux / macOS build scripts: not yet verified to support this mechanism. If you need patch support on those platforms, check whether your copy of build-libmpv-linux.sh / build-libmpv-linux-next.sh / build-libmpv-macos.sh has an equivalent apply_patches() function before assuming this works there.


Version gating

A patch can optionally declare a minimum version it applies to, via a header comment on one of its first few lines:

# min-version: 0.40.0
--- a/player/playloop.c
+++ b/player/playloop.c
...

If the version being built doesn't meet the requirement, the patch is skipped — loudly, logged, not silently — rather than either failing to apply (which aborts the build) or, worse, applying somewhere it was never verified against. This matters because several of the patches below depend on source structure that only exists from v0.40.0 onward; reusing the same CUSTOM_PATCH_DIR to build an older tag (e.g. v0.37.0) alongside v0.41.0 would otherwise be unsafe.

The comment line doesn't interfere with patch's own parsing — patch already ignores anything before the --- a/... line.

Patches with no min-version header apply unconditionally to every version built.


Patches shipped

FFmpeg

Patch Fixes
mpegvideo_parser_resync_valve.patch Bounds mpeg1_find_frame_end's parser state — without this, malformed/unusual MPEG-2 streams could cause the parser's internal buffer to grow without ever finding a frame boundary. Adds an 8MiB safety valve that forces a resync if no frame boundary is found within that window.
combine_frame_shrink.patch ff_combine_frame's internal buffer (pc->buffer) grows to accommodate the largest frame seen, but was never shrunk back down afterward. Proactively frees and resets it once it's no longer needed, instead of holding the high-water-mark size for the rest of the session.

Both were found and confirmed via targeted diagnostic instrumentation during a memory investigation — not merely theoretical; both showed measurable effect on real content.

mpv

Patch Min version Fixes
idle_packet_pool_clear.patch 0.40.0 Backport of upstream commit 4a815734e3d183dc63fecc08b8f9f8c396c53647 ("player/playloop: clear demux packet pool on idle"). The demux packet pool is deliberately kept populated across file switches (avoids reallocation cost), but was never cleared when mpv genuinely goes idle (no file queued) — meaning it just sat at its high-water-mark indefinitely. Requires idle=yes to actually reach the code path this touches (see note below).
pool_incremental_gc.patch 0.40.0 Backport of upstream commit 09900a0ff016c24409a4e6f95012260ed1107d7e ("demux/packet_pool: incrementally free old packets on pop"), upstream PR #17889. Unlike the idle-clear patch above, this one works during normal, active playback — every packet-pool pop call now also frees a small batch of excess pooled packets, so the pool self-balances continuously rather than only shrinking when idle. Doesn't depend on idle=yes. Fixes upstream issue #17323 ("memleak watching hls tv streams").
fence_leak_fix.patch 0.40.0 Backport of upstream commit f74adc4243bd9e1a8ee96d187c939e6232510650 ("opengl/context: require swap_buffers param for FenceSync"), fixes upstream #17217. vo_libmpv (the embedded Render API backend — used by any application driving mpv_render_context_render() directly, as opposed to the standalone mpv player) leaked one GLsync fence object into a heap-allocated array on every single render call, since the function that normally consumes/frees them is never invoked for this backend. This was often the single largest contributor to memory growth of everything found — confirmed against a real, independently-reported upstream issue with matching symptoms (heap growth specific to repeated mpv_render_context_render() calls, confirmed via VMMap to be heap memory, not GPU-driver memory).

None of the three mpv patches are present in v0.37.0 or v0.38.0 — the underlying refactors they build on (demux/packet_pool.c's introduction, and the swapchain-API changes the fence fix responds to) postdate those releases. The min-version: 0.40.0 header on each patch enforces this automatically.

idle=yes note: idle_packet_pool_clear.patch only has an effect if the mpv instance actually has player_idle_mode enabled — libmpv's default is no. Without it, the code path this patch touches never runs at all. If you're embedding libmpv and want this fix to matter, set mpv_set_option_string(mpv_, "idle", "yes") (or the equivalent in your mpv.conf) during initialization. Testing showed no effect on active playback or channel-change behavior — the idle-loop path is only reached when nothing is queued to play next, and mpv's own event wakeup (mp_wakeup_core) correctly resumes immediately once a new loadfile command arrives.


Getting the patch files

Download from the wiki repo alongside the SDK/runtime tarballs:

File Link
mpegvideo_parser_resync_valve.patch download
combine_frame_shrink.patch download
idle_packet_pool_clear.patch download
pool_incremental_gc.patch download
fence_leak_fix.patch download

(These links assume the patch files are added to the wiki repo the same way the SDK/runtime tarballs are — clone the wiki locally, add the files, commit and push. See the "Clone this wiki locally" link on any wiki page.)


A note on rebuilding after changing patches

The build script's stamp-based caching only tracks mpv/FFmpeg version and the debug-symbols flag — it does not detect changes to patch file contents. If you add, remove, or edit a patch and rerun the build without clearing the relevant cached tree, the stale build will be reused silently, with the old patch set (or no patches at all) still in effect.

Force a clean rebuild whenever the patch set changes:

rm -rf work/deps-ffmpeg7      # or deps-ffmpeg6, matching the FFmpeg version affected
git -C work/src/ffmpeg reset --hard
git -C work/src/ffmpeg clean -fd

For mpv itself, build_libmpv_tag() already does a full git reset --hard / git clean -fd before reapplying patches on every run, so no manual step is needed there — only the FFmpeg side has this caching gap.

Clone this wiki locally