Skip to content

Fix loading and crashes on OBS 30+ (drop FFmpeg dependency) - #53

Open
Mike-iLeech wants to merge 3 commits into
synap5e:masterfrom
Mike-iLeech:fix/obs-30-plus
Open

Mike-iLeech wants to merge 3 commits into
synap5e:masterfrom
Mike-iLeech:fix/obs-30-plus

Conversation

@Mike-iLeech

@Mike-iLeech Mike-iLeech commented Sep 27, 2026 •

Copy link
Copy Markdown

Makes the plugin load and run on OBS Studio 30, 31 and 32.

Fixes #52, fixes #48 (also relevant to #42, #45).

Root cause

The 1.5.2 release links avcodec-59.dll / avutil-57.dll, which only ship with OBS 28. Every newer OBS ships different FFmpeg DLL names, so the plugin DLL fails to load and the filter silently disappears from the filter list. FFmpeg was only used to encode PNG, so this PR replaces it with the single-header, public-domain stb_image_write.h — the plugin now has no dependency on OBS's FFmpeg at all (the DLL imports only obs.dll, wininet and the system CRT).

Crash fixes

Once loading worked, a few crashes showed up on current OBS (one of them hit in real use, crash report: bmalloc ← screenshot_filter_tick):

  • 0x0 source (e.g. a media source restarting): the frame buffer was allocated with size 0, and OBS 30+ aborts on bmalloc(0).
  • Heap overflow on some widths: the buffer had only 128 bytes of per-row slack, while the D3D11 staging row pitch can be padded more (e.g. 853×480). Rows are now copied into a tightly packed buffer.
  • Use-after-free on destroy: destroy waited only 5 s for the writer thread, then freed the filter even if it was still encoding/uploading. It now joins the thread; WinINet timeouts bound uploads.
  • Lock-order deadlock: tick/destroy took the filter mutex before the graphics context, render the other way around.
  • Shared memory is written under the mutex with a size check; destination string is copied (the settings string could be freed while in use); the hotkey is unregistered on destroy and not re-registered while the filter is being removed; mutex created before the writer thread starts.

Smaller fixes

  • os_fopen instead of fopen so non-ASCII (e.g. Cyrillic) paths work.
  • Folder mode: no more _1.raw.png names; overlapping snprintf fixed (also in the HTTP headers).
  • Shared memory header size (16 bytes, not 12), mbstowcs overflow of the shmem name.

Behaviour change

Raw output and shared memory frames are now tightly packed (linesize = width * 4) instead of carrying the driver's row pitch. Shared memory consumers that honour the linesize header field are unaffected; raw file/HTTP consumers never received the pitch, so this makes the data predictable.

Build / testing

  • CMakeLists.txt no longer needs FFmpeg or w32-pthreads; version bumped to 1.6.0. buildspec.json still builds against OBS 29.1.2 headers, which is fine since the plugin API used is unchanged.
  • Tested: loaded into real obs.dll of OBS 31.0.4 and 32.2.2 (filter registers, defaults and properties OK), PNG output verified pixel-by-pixel with a padded stride, folder naming incl. a Cyrillic path; an earlier build of this branch (loading + 0x0 crash fix) was used on OBS 31.0.4 with a media source.
  • Prebuilt DLL + PDB: https://github.com/Mike-iLeech/obs-screenshot-plugin/releases/tag/1.6.0

🤖 Generated with Claude Code

Mike-iLeech and others added 3 commits September 28, 2026 00:29
The 1.5.2 release links avcodec-59.dll/avutil-57.dll, which only ship
with OBS 28. On newer OBS the DLL fails to load and the filter silently
disappears from the filter list (synap5e#48, synap5e#52).

- Encode PNG with the bundled stb_image_write.h instead of FFmpeg, so
  the plugin no longer depends on the FFmpeg version shipped with OBS.
- Fix crash when the source reports 0x0 (e.g. a media source
  restarting): OBS 30+ aborts on bmalloc(0).
- Fix heap overflow when the staging surface row pitch exceeds
  width * 4 + 128 (odd widths such as 853x480): copy rows into a
  tightly packed buffer, linesize is now width * 4.
- Write named shared memory under the mutex so a resize cannot unmap
  or shrink it mid-copy; check the frame fits the mapping.
- Create the mutex before starting the writer thread, close the thread
  handle on destroy, handle create() failure and NULL data in
  filter_remove.
- Fix aliasing snprintf when building HTTP headers, shmem header size
  (16 bytes, not 12), wide shmem name conversion.
- Remove FFmpeg and w32-pthreads from the CMake build; bump to 1.6.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- destroy now waits for the writer thread to exit instead of giving up
  after 5 s and freeing the filter while it may still be encoding or
  uploading; WinINet connect/send/receive timeouts (5 s) bound how long
  an unreachable server can block it.
- Take the graphics context before the filter mutex in tick and destroy,
  matching render, to avoid a lock-order deadlock when another thread
  renders the source (e.g. obs-websocket screenshots).
- Keep a private copy of the destination string; the settings string can
  be freed when the user edits properties while the writer uses it.
- Folder output: fix overlapping snprintf and "_1.raw.png" names on
  retries (now "_1.png"), check localtime() result.
- Use the CRT allocator for stb so an allocation failure while encoding
  does not abort OBS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Only register the capture hotkey while the filter has a parent, and
  unregister it in destroy, so a hotkey can't fire on a freed filter.
- Open files with os_fopen: OBS passes UTF-8 paths and fopen expects
  the ANSI code page, so non-ASCII folders (e.g. Cyrillic) failed.
- Allocate the frame buffer only when the staging surface was created
  and compute its size in size_t (sources above 16384 px).
- Terminate the shared memory name, limit WinINet connect retries to 1
  (connect timeout is per attempt), User-Agent 1.6.0.
- Route stb allocations back through bmalloc: stb does not handle a
  failed realloc in release builds, a clean OBS abort is better than
  heap corruption. (Corrects the previous commit.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OBS 31, plugin not working. OBS 30

1 participant