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:
@@ -0,0 +1,50 @@
|
||||
using Windows.Media.MediaProperties;
|
||||
using Xunit;
|
||||
using ytLive.Services;
|
||||
|
||||
namespace ytLive.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Take-2026-09-15 fix: a camera that another app has left in MJPG mode can't be
|
||||
/// read as Bgra8 by a MediaFrameReader (StartAsync answers OutputFormatNotSupported) —
|
||||
/// exactly what a browser grabbing the webcam causes. The reader-subtype ladder in
|
||||
/// MediaCaptureFrameSource picks an output the source can actually produce; these
|
||||
/// tests lock the ordering so the fix can't regress into the silent empty webcam box.
|
||||
/// </summary>
|
||||
public sealed class MediaCaptureFrameSourceTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReaderSubtypeCandidates_MjpgActive_NeverAsksBgra8()
|
||||
{
|
||||
var candidates = MediaCaptureFrameSource.ReaderSubtypeCandidates("MJPG");
|
||||
Assert.DoesNotContain(candidates, s => s == MediaEncodingSubtypes.Bgra8);
|
||||
Assert.Equal(MediaEncodingSubtypes.Nv12, candidates[0]);
|
||||
Assert.Null(candidates[^1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReaderSubtypeCandidates_MjpgActive_IsCaseInsensitive()
|
||||
{
|
||||
var candidates = MediaCaptureFrameSource.ReaderSubtypeCandidates("mjpg");
|
||||
Assert.Equal(MediaEncodingSubtypes.Nv12, candidates[0]);
|
||||
Assert.DoesNotContain(candidates, s => s == MediaEncodingSubtypes.Bgra8);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReaderSubtypeCandidates_UncompressedActive_KeepsBgra8FastPath()
|
||||
{
|
||||
var yuy2 = MediaCaptureFrameSource.ReaderSubtypeCandidates("YUY2");
|
||||
var nv12 = MediaCaptureFrameSource.ReaderSubtypeCandidates("NV12");
|
||||
Assert.Equal(MediaEncodingSubtypes.Bgra8, yuy2[0]);
|
||||
Assert.Equal(MediaEncodingSubtypes.Bgra8, nv12[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReaderSubtypeCandidates_UnknownActive_DefaultsToBgra8()
|
||||
{
|
||||
Assert.Equal(MediaEncodingSubtypes.Bgra8,
|
||||
MediaCaptureFrameSource.ReaderSubtypeCandidates(null)[0]);
|
||||
Assert.Equal(MediaEncodingSubtypes.Bgra8,
|
||||
MediaCaptureFrameSource.ReaderSubtypeCandidates("")[0]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user