Skip to content

Make a media source with no decoder loud (T1.13 / #473 step 3) - #494

Merged
iamfatness merged 1 commit into
mainfrom
fix/media-decoder-loud
Sep 12, 2026
Merged

iamfatness merged 1 commit into
mainfrom
fix/media-decoder-loud

Conversation

@iamfatness

Copy link
Copy Markdown
Owner

Addresses #473 (step 3). docs/BACKLOG.md T1.13. I am not closing #473 — see the last section.

An installed beta drew a placeholder for a ProRes Show Input for a whole session. media-core.log carried the compositor's NO matching frame every 5 s and nothing else: no ffmpeg process, no reason. A dev launch of the same code decoded it fine.

What the investigation established

This rules out the obvious guesses, including the issue's own next steps:

  • Media Foundation genuinely cannot decode this ProRes — SetCurrentMediaType(RGB32) returns MF_E_TOPO_CODEC_NOT_FOUND (0xC00D5212). Falling through to FFmpeg is correct.
  • Resolution is not the difference. COREVIDEO_FFMPEG_BIN_DIR turns out to be a persistent User variable on the owner's box (C:\ffmpeg\bin), so both launches get it; with it cleared, SearchPathW still finds the app-local ffmpeg.exe that StartCoreVideo.cmd installs on first run. The issue's "diff the launcher environments" is a dead end.
  • The app-local runtime was installed an hour before the failing session (provenance file at 08:37, failure at 09:45).
  • The exact command the core builds decodes the real asset: exit 0, a full 1920×1080×4 BGRA frame.

Why the trigger is still unknown, and what that costs

The code could not say. Three silent surfaces:

  1. FFmpeg's stderr went to NUL (startup.hStdError). With -loglevel error FFmpeg names the cause in one line, and we threw it away. It now goes to a per-decoder temp file whose last 2 KB are logged when the decoder dies. A temp file and not the stdout pipe — stdout carries raw BGRA frames, and a stderr line spliced into one would corrupt a frame.
  2. A spawn that succeeded and then died reported nothing. launch() returned true, readLoop read 0 bytes and broke, and the source showed a placeholder forever. From outside that is indistinguishable from "ffmpeg was never spawned", which is how the issue describes it. reportEarlyExit() now fires once per decoder with the child's exit code and stderr tail — and only when we did not ask it to stop, so a pause, a resume or a teardown stays quiet.
  3. Every openVideoReader failure reached only warnings_, which never lands in media-core.log. The branch decision, the resolved ffmpeg path and which of the three rules found it, and a total failure to get any decoder are all [media-decoder] lines now. So is the 16-decoder cap refusal in OwnedMediaFrameSource — plausible on a slot-9 source in a full show, and from outside it looks exactly like a broken decoder (rate-limited per source so a busy show cannot flood the log).

Verification

Reproduced the class rather than unit-testing it, since this is an observability change. A truncated .mov driven through the real core previously produced only the compositor's placeholder line. It now produces:

[media-decoder] ...truncated.mov: Media Foundation declined (...), trying FFmpeg
[media-decoder] ffmpeg started pid=25332 exe=C:\ffmpeg\bin\ffmpeg.exe (via COREVIDEO_FFMPEG_BIN_DIR) poster=0 loop=0 for ...
[media-decoder] ffmpeg STOPPED DELIVERING exit=3199971767 for ... ffmpeg: [in#0] moov atom not found  Error opening input: Invalid data found when processing input

903 native tests, 0 failures.

What this does not do

It does not fix the ProRes defect, and #473 should stay open. It did not reproduce on the current install — everything works there now. I could have guessed at a cause and shipped something plausible; instead the next occurrence will name itself in one line, and that is what the issue's own step 3 asked for. Please leave the issue open until a recurrence tells us what it actually was.

🤖 Generated with Claude Code

https://claude.ai/code/session_014yuH7EMvWCWdkhvevrtMyJ

An installed beta drew a placeholder for a ProRes Show Input for a whole
session. media-core.log carried the compositor's "NO matching frame" every 5 s
and nothing else: no ffmpeg process, no reason. A dev launch of the same code
decoded it fine.

What the investigation established, which rules out the obvious guesses:

- Media Foundation genuinely cannot decode this ProRes (SetCurrentMediaType
  RGB32 returns MF_E_TOPO_CODEC_NOT_FOUND). Falling through to FFmpeg is right.
- Resolution is NOT the difference. COREVIDEO_FFMPEG_BIN_DIR is a persistent
  USER variable on the owner's box, so both launches get it; with it cleared,
  SearchPathW still finds the app-local ffmpeg.exe that StartCoreVideo.cmd
  installs on first run. The issue's "diff the launcher environments" is a dead
  end.
- The app-local runtime was installed an hour BEFORE the failing session.
- The exact command the core builds decodes the real asset: exit 0, a full
  1920x1080x4 BGRA frame.

The trigger was never identified, because the code could not say. Three silent
surfaces, all closed:

1. FFmpeg's stderr went to NUL. With -loglevel error it names the cause in one
   line and we threw it away. It now goes to a per-decoder temp file whose last
   2 KB are logged when the decoder dies. A temp file and not the stdout pipe:
   stdout carries raw BGRA frames and a stderr line spliced into one would
   corrupt it.
2. A spawn that succeeded and then died reported NOTHING: launch() returned
   true, readLoop read 0 bytes and broke, and the source showed a placeholder
   forever. reportEarlyExit() now fires once per decoder with the exit code and
   the stderr tail, and only when we did not ask it to stop, so a pause, a
   resume or a teardown stays quiet.
3. Every openVideoReader failure reached only warnings_, which never lands in
   media-core.log. The branch decision, the resolved ffmpeg path and which rule
   found it, and a total failure to get any decoder are [media-decoder] lines
   now. So is the 16-decoder cap refusal in OwnedMediaFrameSource, which from
   outside looks exactly like a broken decoder (rate-limited per source).

Verified by reproducing the class rather than by a unit test: a truncated .mov
driven through the real core previously produced only the compositor line. It
now produces the branch, "ffmpeg started pid=... exe=... (via
COREVIDEO_FFMPEG_BIN_DIR)", and "ffmpeg STOPPED DELIVERING exit=... ffmpeg:
moov atom not found". 903 native tests, 0 failures.

The original ProRes defect is NOT fixed and #473 should stay open. It did not
reproduce on the current install. The next occurrence will name itself in one
line; until then there is nothing honest to fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014yuH7EMvWCWdkhvevrtMyJ
iamfatness added a commit that referenced this pull request Sep 12, 2026
The owner ran beta-2026-09-12-aac2d98 in a real meeting on 2026-09-12 and it
checked out. That closes the "live check with the batch beta" that T1.14-T1.17
were each owing, plus T1.11 step 2 and the T1.9 re-check that had never
happened. Those rows now say live-checked instead of tests-only, because the
distinction is the whole point of the Done table.

T1.12 and T1.13 stay open with their PRs named. T1.13 is explicit that #494
closes the observability hole ONLY and the ProRes defect itself never
reproduced, so nobody later reads a merged PR as a fixed defect.


Claude-Session: https://claude.ai/code/session_014yuH7EMvWCWdkhvevrtMyJ

Co-authored-by: Claude <noreply@anthropic.com>
@iamfatness
iamfatness merged commit 8a06a93 into main Sep 12, 2026
13 of 19 checks passed
@iamfatness
iamfatness deleted the fix/media-decoder-loud branch September 12, 2026 14:05
iamfatness pushed a commit that referenced this pull request Sep 12, 2026
#494 (media decoder is loud) and this branch (#469 recording folder) both added
a section at the same anchor. Neither replaces the other.
iamfatness added a commit that referenced this pull request Sep 12, 2026
Nine items closed and two partial, all shipped in beta-2026-09-12-e1223d3 and
none of them live-checked:

  T1.12 #475  join-time prompts answered            #493
  T1.13 #473  media decoder failures are loud       #494  (observability ONLY)
  T2.6  #438  packaging spec reconciled             #504
  T2.7  #474  one shortcut, uninstall cleans up     #497
  T2.8  #469  recordings resolve absolute           #496
  T2.9  #466  recording lifecycle survives a stream #499
  T2.10 #468  sender health stops lying             #499
  T3.2  #440  WebView2 message                      #504
  T3.7  #470  ISOs armed with capture off warn      #502
  T3.8  #482  ISO frames conform to the writer      #503
  T2.1  #433  PARTIAL — WER dumps are HKLM-only
  T3.5  #476  PARTIAL — the rest is a product call

Each row keeps the thing that is expensive to rediscover rather than just a tick:
T2.8 records that the issue's premise was wrong (the default was already
absolute; a persisted relative preference bypassed it), T3.8 records that its
end-to-end test is a guard which passes either way, T2.6 records that dropping
MSIX cost auto-update, and T2.1 records the measurement proving a per-user
installer cannot enable crash dumps.

Tier 1 now has ONE open item, and it is waiting on a decision rather than
effort: T1.11 step 1 needs a change to the Take path itself, because a plain cut
never captures the outgoing scene. That is written into the row so the next
session does not rediscover it and ship it blind.

Two entries came out of the work rather than the list: #505 (the per-layer
border controls are vestigial — the core forces borderStyle="none" on every
route layer, so they cannot do anything) and the T1.11 ruling.

The honest header: the binding constraint is now T0.1 and a live check, not
engineering capacity.


Claude-Session: https://claude.ai/code/session_014yuH7EMvWCWdkhvevrtMyJ

Co-authored-by: Claude <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.

2 participants