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, }; await capture.InitializeAsync(settings); var colorSource = capture.FrameSources .FirstOrDefault(pair => pair.Value.Info.MediaStreamType == MediaStreamType.VideoPreview) .Value; if (colorSource == null) throw new InvalidOperationException($"No video preview source on camera '{_deviceId}'."); 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(); } } }