diff --git a/CHANGELOG.md b/CHANGELOG.md index 846c374..e56f267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -- **refactor(rewriter): orthogonal HW-decode-text branch** (#40). The +- **refactor(rewriter): orthogonal HW-decode-text branch** (#41). The HW-decode-text path now runs through `extractGraphFacts + composeBurn(burnSpec{vaResident:true, …})` — the same orthogonal core the SW-reshape (#39) and HW-decode-bitmap (#37) branches already use. The @@ -21,7 +21,52 @@ `-map [4]` / `-map [6]` is retargeted via the shared `retargetMapLabel`. Orthogonal parity harness 1369/1369 over the 1583-entry argv corpus, no drift; 4 of the original `reFilter*` regex zoo gone in v1.6.1, last 2 gone - now. No fork change. + now. + +- **fix(ffmpeg): sched start_prepare per-output sinkless gate** (#41, patch + `0122`). Patch `0120` skipped `dst_finished` allocation in `start_prepare()` + for any decoder marked `sink_less` — correct when the decoder has only a + sink-less side-channel output, broken when the decoder has BOTH a + sink-less side-channel AND a real OST consumer (the dual-consumer case + exercised when an argv keeps Plex's `-map -f null -codec ass + nullfile` decode-sink alongside `-map_inlineass `). In that case, + the OST output's `dst_finished` stayed NULL while `nb_dst=1`, and the + first `sch_dec_send` for the OST dereferenced `&dst_finished[0]` → + SIGSEGV in `dec1:0:srt`. Fix: move the sink-less gate INSIDE the + per-output loop so `dst_finished` is allocated for real outputs and only + the genuinely-unconnected outputs are skipped. Live-validated on + plex-test with a minimal lavfi+SRT gdb repro (pre-patch SIGSEGV → + post-patch clean decode, 2 packets read, 2 frames decoded). + +- **fix(rewriter): keep inlineass decode-sink for sidecar bindings** (#41). + Patch `0120`'s sink-less self-decode (via `ist_inlineass_add`) works for + embedded subtitle streams — they share the demuxer with main video, + which is pumped by the scheduler fast enough to feed the sink-less + decoder. For sidecar inputs (`-i temp-0.srt -map_inlineass 1:s:0`) the + separate sidecar demuxer thread has no other downstream consumer; the + scheduler chokes after the first packet (1 packet / 7 bytes from a + 134 KiB SRT file, 0 frames decoded, libass track empty, subs render + blank). Fix: `stripInlineassDecodeSink` now gates on the `-map_inlineass` + binding's `file_idx == 0`. Sidecar bindings (`file_idx >= 1`) keep + Plex's emitted `-map -f null -codec ass nullfile` so the sidecar + demuxer has a real downstream consumer. Combined with patch `0122` + (above) the dual-consumer state is safe; sidecar SRT renders. Embedded + bindings still get the original `0120` strip (no extra null-mux + competing for the NFS read during pre-throttle buffer fill). + +- **refactor(rewriter): fold tryOptimizeRemux into Rewrite()** (#43). The + Optimize remux fast-path was a parallel function that re-ordered the + same helpers the main rewriter's tail already calls. Collapsed into one + path: detect `isRemux := isOptimizeRemux(args)` up-front, gate the + transcode block (decoder upgrade, init_hw_device, sub detection, filter + rewrite, encoder swap, SEI inject) and the VAAPI-only env writes (LIBVA + driver, inlineass-decode-sink strip) on `!isRemux`. Common tail (EAE + swap, manifest_name / segment_list / progressurl rewrites, loglevel / + nostats / env scrubs) runs unconditionally — already shared via + helpers, order-independent. Drops `decode:remux:` and + `encode:copy(passthrough)` change tags (helper-emitted tags suffice). + Parity-validated on 720 corpus entries (real-prod + Optimize-sweep + matrix): 0 argv / env / progressurl divergence. Net −47 LOC. ## v1.6.1 — 2026-05-26 diff --git a/docs/PACED_SELF_DECODE.md b/docs/PACED_SELF_DECODE.md index 28445a9..bb6df0a 100644 --- a/docs/PACED_SELF_DECODE.md +++ b/docs/PACED_SELF_DECODE.md @@ -90,9 +90,16 @@ So the scheduler gains an explicit *sink-less* concept. ### `fftools/ffmpeg_sched.{c,h}` - `SchDec` gains `int sink_less;`. - New API `void sch_dec_set_sinkless(Scheduler *sch, unsigned dec_idx);`. -- `start_prepare()`: skip the per-output "not connected to any sink" check when - `dec->sink_less` (the source check still applies — a sink-less decoder must - still be fed by the demuxer). +- `start_prepare()`: skip the per-output "not connected to any sink" check + when `dec->sink_less` AND the output has `nb_dst == 0` (the source check + still applies — a sink-less decoder must still be fed by the demuxer). + **Per-output gate** — patch `0122` refines this from the original + whole-decoder `if (dec->sink_less) continue` which skipped `dst_finished` + allocation for ALL outputs of the decoder, including real OST consumers + in the dual-consumer case (sidecar SRT path where the rewriter keeps + Plex's null-mux decode-sink). The original variant SIGSEGV'd in + `sch_dec_send` on the first decoded frame when `dst_finished` was NULL + for the real output. - `sch_dec_send()`: when the target output has `nb_dst == 0`, `av_frame_unref` the frame and `return 0` (consumed, discarded) instead of falling through to the `AVERROR_EOF` result. Universally safe — only sink-less outputs ever have diff --git a/docs/REWRITER.md b/docs/REWRITER.md index 0d152c3..3ce26e8 100644 --- a/docs/REWRITER.md +++ b/docs/REWRITER.md @@ -234,11 +234,21 @@ construction, and the OpenCL detour collapses into the canonical resolution tier below `render_height`. Static cues are unaffected. Same knob applies on the SW-reshape path (`composeBurn`'s `animatedTierDown` axis). - Plex's `-map_inlineass ` is **kept** — it drives the libass feed. -- Plex's `-map -f null -codec ass` decode-sink is **stripped** - (`stripInlineassDecodeSink`, gated on `-map_inlineass` still being present). - As of fork patch `0120` (v1.5.0) the `-map_inlineass` binding self-decodes - via a sink-less decoder paced by the demux, so the null-mux is redundant — - see `docs/PACED_SELF_DECODE.md`. +- Plex's `-map -f null -codec ass` decode-sink is **conditionally + stripped** by `stripInlineassDecodeSink`, gated on (a) `-map_inlineass` + still being present AND (b) the binding's `file_idx == 0` (embedded sub + stream — shares the main demuxer with video). As of fork patch `0120` + (v1.5.0) the `-map_inlineass` binding self-decodes via a sink-less + decoder paced by the demux; for embedded subs the shared demuxer is + pumped by main video, so the null-mux is redundant and competes with the + NFS read during the pre-throttle buffer fill (the original reason to + strip it). For **sidecar bindings** (`file_idx >= 1`, e.g. + `-map_inlineass 1:s:0` pointing at a PMS-staged `temp-0.srt`) the + sidecar has its own demuxer thread with no other downstream consumer; the + scheduler chokes after the first packet and the binding sees zero cues. + There the decode-sink is **kept** so the sidecar demuxer has a real + consumer (patch `0122` makes the dual-consumer state safe for + `dst_finished` allocation). See `docs/PACED_SELF_DECODE.md`. - Plex's full `inlineass` node is passed through **verbatim** — including the styling keys `language`/`overrides`/`outline`/`shadow`. As of patch `0119` the fork's `inlineass` parses them (`overrides` →