TASK 3 milestone 1: webcam capture (MediaCapture CPU-first, CameraManager refcount, picker, clip/mirror, schema v2, tests)
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="VideoFrame"/>. Frames arrive on a
|
||||
/// worker thread — marshal before touching WPF.
|
||||
/// </summary>
|
||||
public sealed class MediaCaptureFrameSource : ICameraFrameSource
|
||||
{
|
||||
private readonly string _deviceId;
|
||||
private MediaCapture? _capture;
|
||||
private MediaFrameReader? _frameReader;
|
||||
|
||||
public string DeviceId => _deviceId;
|
||||
|
||||
public event Action<VideoFrame>? 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user