Skip to content

refactor: external ffmpeg.exe encoding (replay + recording) - #1

Merged
fraa2a merged 17 commits into
mainfrom
refactor/ffmpeg-external
Aug 3, 2026
Merged

refactor: external ffmpeg.exe encoding (replay + recording)#1
fraa2a merged 17 commits into
mainfrom
refactor/ffmpeg-external

Conversation

@fraa2a

@fraa2a fraa2a commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Refactor dell'encoding verso ffmpeg.exe esterno per isolare i GPL x264/x265 dal binario ARR.

Stato: DRAFT — non mergiare finché lo smoke test manuale su Windows non conferma.

Progresso:

  • P1 — infrastruttura subprocess (locate/launch ffmpeg.exe) — già su main
  • P2 (parte 1) — modulo SegmentReplay (video stdin + audio named pipe → segmenti rotanti)
  • P2 (parte 2) — cablaggio nel pacer/replay
  • P3 — recording manuale subprocess
  • P4 — detection encoder + display vendor
  • P5 — licenza ARR + packaging

Verifica: la CI compila (no vcpkg locale). Smoke test runtime (save replay + audio sync, recording) richiede Windows.

🤖 Generated with Claude Code

fraa2a and others added 17 commits July 15, 2026 05:16
Adds SegmentReplay: drives one external ffmpeg.exe that reads raw video
(stdin) + raw mixed audio (a Windows named pipe) and writes short rotating
mpegts segments to a scratch dir (-f segment -segment_wrap). Only the last
few seconds live on disk, so RAM stays flat and the GPL software encoders
run out-of-process. save_clip() concatenates the most recent segments via
stream copy (no re-encode).

Standalone module (no libav*), not wired into the pipeline yet — next
commit replaces the in-process replay ring feed. Keeps multi-track audio
via the named pipe, unlike the video-only mod replay.

Ref: PLAN refactor P2 (part 1).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rework SegmentReplay to preserve Monolith's multi-track audio layout
(e.g. game on track 1 + mic on track 2 as separate streams) instead of a
single mixed track. Each configured track gets its own named pipe, ffmpeg
audio input, explicit -map, and output AAC stream; push_audio takes the
stream index to route PCM to the right pipe.

Ref: PLAN refactor P2 (part 2).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds RecordingProcess: same external-ffmpeg, multi-track named-pipe model
as SegmentReplay, but writes one continuous output file instead of
rotating segments. Raw video on stdin + one audio pipe per track, CBR
encode, mkv/mp4 with faststart. Standalone module (no libav*), not wired
into the pipeline yet — the main.cpp rewire (which must switch replay and
recording together, since both consume the shared in-process encoder) is
the next commit.

Ref: PLAN refactor P3 (engine).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With the external ffmpeg encoder, running replay + recording together
means two encoder processes (each frame encoded twice) — heavy on CPU
encoders. Default to one at a time: starting a manual/auto recording
suspends the replay buffer, stopping restores it. An advanced setting
(allow_concurrent_capture, off by default) permits both, with a UI note
about the higher CPU/RAM cost.

- New g_replay_suspended_for_recording flag + replay_active() helper,
  AND-ed with the user's g_replay_enabled setting so the preference is
  never overwritten. Suspend clears the ring; restore re-arms the gate.
- Hook suspend/restore into all four start/stop paths (auto + manual).
- save_replay command + tray menu item are gated/greyed while suspended.
- settings: advanced.allow_concurrent_capture (persisted) + Advanced UI
  toggle with the cost warning.

This layer works on the current in-process encoder and is independent of
the encoder swap that follows.

Ref: PLAN refactor P2/P3 (capture mutex).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The suspend/restore helpers were defined after auto_record_stop but used
earlier in auto_record_start (C3861 in the CI build). Move them above
auto_record_start. Also drop the unused wide_to_utf8 in ffmpeg_process
(C4505).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds ffmpeg_available_encoders() (parses `ffmpeg -encoders`, whole-word
match against Monolith's candidate list — robust vs the fragile fixed
flag-width parse) and ffmpeg_resolve_encoder() (device+codec -> concrete
encoder, with CPU+H.265 falling back to hardware HEVC then H.264 since
there is no software H.265 unless libx265 is present). These replace the
in-process avcodec probes once main.cpp is rewired to the external
encoder, and feed the UI's vendor display (Point 3).

Ref: PLAN refactor P4 (engine).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Record landed work (main), branch state (PR #1), decisions taken, and the
remaining main.cpp core rewire + P5 packaging.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the in-process libav encoders and muxers (VideoEncoder/AudioEncoder,
ReplayBuffer/ManualRecorder packet tee) with the external SegmentReplay and
RecordingProcess engines. The pacer now feeds raw BGRA (stride-compacted) to
whichever engine(s) are running; every audio track routes through a
TrackMixer that converts WASAPI PCM to the canonical f32le the engines'
named pipes expect. ffmpeg does encode + mux out-of-process.

- vpipe_* coordinator: push_video/push_audio fan out to the active engine(s);
  a paused recording is skipped (pause = cut wall-clock time).
- Engine lifecycle resolved lazily on the first captured frame so g_enc_w/h
  reflect the real (possibly downscaled) size; recording can be armed before
  the pipeline is live and starts when the first frame arrives.
- Encoder resolution + availability now via ffmpeg_available_encoders /
  ffmpeg_resolve_encoder against a located ffmpeg.exe (probed in media_start).
- Recording facade (rec_start/stop/pause/resume/state) preserves call sites;
  RecState replaces recording::RecordingState. save_replay runs the segment
  concat off the tray thread.
- Multi-track audio preserved (one pipe/stream per track). Perf log trimmed
  to capture+pacer (encoder-internal timings are out-of-process now).
- Drop replay-buffer/recording includes from main; those libs are no longer
  used by the recorder (removal + LGPL vcpkg pass comes in P5).

Compiles-only via CI; runtime a/v behaviour needs the real-machine smoke test.

Ref: PLAN refactor P2+P3+P4 core rewire.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AudioTrack is nested in SegmentReplayConfig (not SegmentReplay). Fix the
type name at all uses, and convert SegmentReplayConfig::AudioTrack to
RecordingProcessConfig::AudioTrack when handing the plan to the recording
engine (distinct types with the same shape).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With encode/mux moved to an external ffmpeg.exe, the in-process libav
build only needs decode (thumbnail/duration) and swresample (TrackMixer),
neither of which is GPL. Drop gpl/x264/x265 (and the HW-encoder features,
now provided by the external ffmpeg) from vcpkg.json so the shipped binary
links only LGPL libav — clearing the way for an all-rights-reserved
license. GPL x264/x265 live in the separate ffmpeg.exe process.

- vcpkg.json: default-features off, keep avcodec/avformat/swscale/swresample.
- Remove libs/replay-buffer and libs/recording (in-process muxers) from the
  build and the recorder's link line; they are superseded by SegmentReplay/
  RecordingProcess. The orphaned VideoEncoder/AudioEncoder in encoding.cpp
  remain (alongside TrackMixer) as never-invoked dead code.
- Document the decision as ADR-0017.

Deployment note: ffmpeg.exe(+ffprobe.exe) must be bundled next to the exe
or on PATH; first-run download is a possible later addition.

Ref: PLAN refactor P5.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Record the finished main.cpp core rewire and P5 licensing work, plus the
step-by-step real-machine smoke test to run before merging PR #1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FFmpeg is required for all encoding and is not bundled in the installer, so
fetch it on first launch (background thread, after gamelist init). Mirrors
the Record-able mod's approach: HTTPS download of gyan.dev's
release-essentials build, SHA-256 verification against the upstream
.sha256, extraction of ffmpeg.exe+ffprobe.exe via Windows' built-in
tar.exe (no new zip dependency) into %LocalAppData%\Monolith\ffmpeg\bin.

- ffmpeg_fetch.cpp: WinHTTP download, BCrypt SHA-256, tar.exe extract.
- locate_ffmpeg now also searches the download dir (order: user path ->
  next to exe -> download dir -> PATH).
- The tray/UI loop never blocks; media_start re-resolves ffmpeg on each
  (re)start, so a clip/recording before the download finishes retries once
  it's ready. No-op when ffmpeg is already present.

Licensing: downloading the GPL ffmpeg from upstream at runtime (not
redistributed by us) keeps Monolith's own binary clean.

Ref: PLAN refactor P5 (first-run download).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
C3861 in the CI build — SHCreateDirectoryExW needs <shlobj.h>.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
External ffmpeg.exe encoding refactor (ADR-0017): replay + recording now
run through an external ffmpeg, in-process build is LGPL, ffmpeg is
auto-downloaded on first run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Allow pre-release version tags so the ffmpeg-refactor build can ship as an
rc without pushing an auto-update to existing users:

- Accept vX.Y.Z-rcN tags; expose numeric (for VERSIONINFO/AppVersion) and
  full (for file name/display) versions plus a prerelease flag.
- Installer: MonolithFullVersion (with suffix) drives OutputBaseFilename +
  AppVersion; numeric MonolithVersion stays for VersionInfoVersion.
- Release: mark prerelease, do NOT make_latest, and skip appcast generation
  for pre-releases so WinSparkle clients aren't offered the untested build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Stream Deck plugin manifest requires X.Y.Z (rejects the -rcN suffix).
Use the numeric version for the plugin package + its verify/publish path;
the installer keeps the full pre-release version.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The LGPL ffmpeg build no longer produces libx264/libx265 DLLs, so the
lib*.dll glob matched nothing and ISCC aborted ("No files found"). Encode/
mux now run in an external ffmpeg.exe; only the av*/sw* LGPL runtime DLLs
remain to bundle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@fraa2a
fraa2a merged commit a670345 into main Aug 3, 2026
2 checks passed
@fraa2a
fraa2a deleted the refactor/ffmpeg-external branch August 3, 2026 07:23
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.

1 participant