using System; using System.Linq; using System.Runtime.InteropServices; using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; using Windows.Graphics.Imaging; using Windows.Media.Capture; using Windows.Media.Capture.Frames; using Windows.Media.MediaProperties; using ytLive.Helpers; namespace ytLive.Services; /// /// CPU-first MediaCapture source. Requests BGRA8 frames from the camera's video /// preview source; the capture pipeline does any format conversion, so every /// frame arrives as a normalized . Frames arrive on a /// worker thread — marshal before touching WPF. /// public sealed class MediaCaptureFrameSource : ICameraFrameSource { private readonly string _deviceId; private MediaCapture? _capture; private MediaFrameReader? _frameReader; public string DeviceId => _deviceId; public event Action? FrameAvailable; public MediaCaptureFrameSource(string deviceId) { _deviceId = deviceId; } public async Task StartAsync() { var capture = new MediaCapture(); MediaFrameReader? reader = null; try { var settings = new MediaCaptureInitializationSettings { VideoDeviceId = _deviceId, StreamingCaptureMode = StreamingCaptureMode.Video, MemoryPreference = MediaCaptureMemoryPreference.Cpu, SharingMode = MediaCaptureSharingMode.SharedReadOnly, }; await capture.InitializeAsync(settings); 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) { 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; await reader.StartAsync(); _capture = capture; _frameReader = reader; } catch { if (reader != null) { reader.FrameArrived -= OnFrameArrived; reader.Dispose(); } capture.Dispose(); throw; } } public async Task StopAsync() { var reader = _frameReader; _frameReader = null; if (reader != null) { reader.FrameArrived -= OnFrameArrived; try { await reader.StopAsync(); } catch (Exception ex) { AppLog.Write($"MediaCaptureFrameSource: stop frame reader failed: {ex.Message}"); } reader.Dispose(); } var capture = _capture; _capture = null; capture?.Dispose(); } private void OnFrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args) { using var frame = sender.TryAcquireLatestFrame(); var videoFrame = frame?.VideoMediaFrame?.SoftwareBitmap; if (videoFrame == null) return; var bitmap = videoFrame.BitmapPixelFormat == BitmapPixelFormat.Bgra8 ? videoFrame : SoftwareBitmap.Convert(videoFrame, BitmapPixelFormat.Bgra8); try { using var buffer = bitmap.LockBuffer(BitmapBufferAccessMode.Read); using var reference = buffer.CreateReference(); if (WindowsRuntimeMarshal.TryGetDataUnsafe(reference, out var pixelsPtr, out var capacity)) { var pixels = new byte[capacity]; Marshal.Copy(pixelsPtr, pixels, 0, (int)capacity); FrameAvailable?.Invoke(new VideoFrame(bitmap.PixelWidth, bitmap.PixelHeight, pixels)); } } catch (Exception ex) { AppLog.Write($"MediaCaptureFrameSource: frame copy failed: {ex.Message}"); } finally { if (!ReferenceEquals(bitmap, videoFrame)) bitmap.Dispose(); } } }