fix(webcam): reader-output-subtype ladder + record WinRT per-call wrap rule

A browser grabbing the C920 kills our reader startup: the camera stays in
MJPG 1920x1080@30 (another app's format) and our OBS-refusal to re-negotiate
under SharedReadOnly contention (`SetMediaStreamPropertiesAsync` throws
'file in use / CaptureMode is SharedReadOnly') leaves it there.
CreateFrameReaderAsync(.., Bgra8) + StartAsync then refuses with
OutputFormatNotSupported — the MJPG-active source only exposes NV12 at the
reader level (clue: startup.log 19:01/19:05 sessions, same hardware that
started YUY2 640x480 fine at 08:52). Fresh launches showed no webcam and
'Add Webcam' failed.

Reader creation is now a per-candidate ladder (ReaderSubtypeCandidates):
Bgra8 for uncompressed cameras (unchanged fast path); NV12 then the
source-default for MJPG cameras — converted in OnFrameArrived like any
non-BGRA frame. Each candidate is allocated AND started under its OWN
catch: WinRT answers an unsupported subtype with a throw (E_INVALIDARG),
not a status, so a single rejected format must degrade to the next
candidate instead of aborting acquisition (creator rule — see MyMistakes
WINRT resource-allocation recipe). Rejections are logged and collected
into the final error.

Good Dog: 4 unit tests lock the candidate ordering (MJPG never Bgra8,
case-insensitive, uncompressed keeps Bgra8 first, unknown/null -> Bgra8).
301/301 green, 0 warnings. [no push]
This commit is contained in:
2026-09-15 19:25:39 -07:00
parent 64a5a6d06f
commit 94a934ffa9
5 changed files with 223 additions and 64 deletions
+12
View File
@@ -385,6 +385,18 @@ This replaces the old five-seeder cluster (`Seed{Starting,Brb,Ending,Chat}Backgr
`CreateFrameReaderAsync(colorSource, MediaEncodingSubtypes.Bgra8)` — the pipeline does any format
conversion, so every `FrameArrived` yields a ready BGRA8 `SoftwareBitmap` (bytes read via
`WindowsRuntimeMarshal.TryGetDataUnsafe`, not marshalled copies).
- **Reader-output subtype ladder (2026-09-15 webcam-take fix):** the reader is created per-candidate
by `ReaderSubtypeCandidates(activeSubtype)` — Bgra8 for uncompressed cameras (fast path), NV12 then
source-default for cameras sitting in MJPG. A camera another app left in MJPG mode cannot be read as
Bgra8 by a `MediaFrameReader` (`StartAsync` → `OutputFormatNotSupported`; the MJPG pipeline exposes
NV12, not BGRA) — exactly what a browser grabbing the webcam produces. When the app can't re-negotiate
(our `SetMediaStreamPropertiesAsync` refuses with "file in use / CaptureMode is SharedReadOnly" under
msedge + NVIDIA Broadcast contention), an MJPG-active reader otherwise aborts the whole acquisition.
Each candidate is allocated AND started under its OWN catch — WinRT answers an unsupported subtype
with a THROW (`E_INVALIDARG`), not a status; one rejected format must never abort the ladder. Failures
degrade to the next candidate; rejections are logged + collected into the final error; only the outer
catch nets device-level errors (see `MyMistakes.md` → WINRT RESOURCE-ALLOCATION RECIPE). Anything not
Bgra8 is converted in `OnFrameArrived`, which already handled non-BGRA software bitmaps.
- **Source pick, not first hit:** the frame reader is bound to the first source that is `VideoPreview`
(preferred) or `VideoRecord`, not blindly the first preview source. If a camera exposes neither, the
failure names the device and the stream types it *does* expose. `SharedReadOnly` lets the capture