diff --git a/Services/MediaCaptureFrameSource.cs b/Services/MediaCaptureFrameSource.cs index 7c3c479..4d96803 100644 --- a/Services/MediaCaptureFrameSource.cs +++ b/Services/MediaCaptureFrameSource.cs @@ -43,14 +43,21 @@ public sealed class MediaCaptureFrameSource : ICameraFrameSource VideoDeviceId = _deviceId, StreamingCaptureMode = StreamingCaptureMode.Video, MemoryPreference = MediaCaptureMemoryPreference.Cpu, + SharingMode = MediaCaptureSharingMode.SharedReadOnly, }; await capture.InitializeAsync(settings); - var colorSource = capture.FrameSources - .FirstOrDefault(pair => pair.Value.Info.MediaStreamType == MediaStreamType.VideoPreview) - .Value; + var colorSource = capture.FrameSources.Values + .OrderBy(s => s.Info.MediaStreamType == MediaStreamType.VideoPreview ? 0 : 1) + .FirstOrDefault(s => s.Info.MediaStreamType is MediaStreamType.VideoPreview or MediaStreamType.VideoRecord); if (colorSource == null) - throw new InvalidOperationException($"No video preview source on camera '{_deviceId}'."); + { + var kinds = string.Join(", ", capture.FrameSources.Values + .Select(s => s.Info.MediaStreamType).Distinct()); + throw new InvalidOperationException( + $"Camera '{_deviceId}' exposes no video source (found: {kinds}). " + + "It may be locked by another app (e.g. NVIDIA Broadcast)."); + } reader = await capture.CreateFrameReaderAsync(colorSource, MediaEncodingSubtypes.Bgra8); reader.FrameArrived += OnFrameArrived; diff --git a/ai.md b/ai.md index 30d0b28..a63132f 100644 --- a/ai.md +++ b/ai.md @@ -84,10 +84,19 @@ C# / WPF (.NET 8) following MVVM: - **Seam-first:** everything above the WinRT layer speaks only `VideoFrame` (normalized tightly-packed BGRA8) + `CameraDeviceInfo`/`ICameraEnumerator`/`ICameraFrameSource` interfaces. Tests inject fakes; screen capture and background removal later feed the same seam. -- **CPU-first:** `MediaCaptureInitializationSettings { MemoryPreference = Cpu, StreamingCaptureMode = Video }`, - frames pulled via `CreateFrameReaderAsync(colorSource, MediaEncodingSubtypes.Bgra8)` — the pipeline does - any format conversion, so every `FrameArrived` yields a ready BGRA8 `SoftwareBitmap` (bytes read via +- **CPU-first:** `MediaCaptureInitializationSettings { MemoryPreference = Cpu, StreamingCaptureMode = Video, + SharingMode = SharedReadOnly }`, frames pulled via + `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). +- **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 + coexist with other apps that share the camera. +- **Known failure: NVIDIA Broadcast** — it opens the physical webcam exclusively, so `InitializeAsync` + fails with "camera in use" (or, if init slips through, the device exposes no preview source). Fix: + quit Broadcast while streaming, or pick its virtual "NVIDIA Broadcast" device from the picker and the + app captures the processed feed. This is a real-device finding (Logitech `VID_046D&PID_082D`). - **TFM:** `net8.0-windows10.0.19041.0` (app + tests) pulls the WinRT projection from the SDK reference packs — no NuGet package, no capability manifest (unpackaged desktop app works; the Windows privacy camera toggle still applies). `EnableWindowsTargeting` keeps WSL builds working.