Triển khai bắt khung hình từ camera bằng MediaCapture trong ứng dụng WPF trên Windows 11 với tích hợp UWP

Khi phát triển ứng dụng giao diện lớn (large-screen) trên nền tảng Windows, việc thu thập và hiển thị luồng video từ thiết bị đầu vào như camera HDMI hoặc USB thường yêu cầu độ trễ thấp và khả năng tương thích cao với phần cứng. Trong khi các thư viện dựa trên UVC như AForge.NET vẫn được sử dụng rộng rãi, cách tiếp cận hiện đại hơn — đặc biệt trên Windows 11 — là tận dụng MediaCapture, API gốc của nền tảng UWP, thông qua cơ chế lưu trữ chéo (interop) trong ứng dụng WPF.

Để sử dụng MediaCapture trong môi trường .NET Framework hoặc .NET 5+, cần thêm hai gói NuGet bắt buộc:

<PackageReference Include="Microsoft.Toolkit.Wpf.UI.XamlHost" Version="7.1.2" />
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.26100.1" />

Các gói này cho phép nhúng các điều khiển UWP (như CaptureElement) vào cửa sổ WPF và cung cấp quyền truy cập vào các API hệ thống mới nhất của Windows.

1. Phát hiện và khởi tạo thiết bị camera

Hàm sau liệt kê tất cả thiết bị quay phim có sẵn, lọc theo tên gợi ý (ví dụ: chứa chuỗi "HDMI"), rồi khởi tạo luồng bắt khung với cấu hình tối ưu:

private readonly SemaphoreSlim _initLock = new(1, 1);
private MediaCapture _capture;
private MediaFrameReader _frameReader;
private bool _isStreaming;

public async Task<IReadOnlyList<DeviceInformation>> EnumerateVideoSourcesAsync(string keyword = "HDMI")
{
    var devices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
    return devices.Where(d => d.Name.Contains(keyword, StringComparison.OrdinalIgnoreCase)).ToList();
}

private async Task<bool> InitializeCameraAsync(string deviceId)
{
    try
    {
        await _initLock.WaitAsync();
        if (_isStreaming) return true;

        _capture = new MediaCapture();
        var config = new MediaCaptureInitializationSettings
        {
            VideoDeviceId = deviceId,
            StreamingCaptureMode = StreamingCaptureMode.Video,
            MemoryPreference = MediaCaptureMemoryPreference.Cpu
        };

        _capture.Failed += (_, args) => 
            Log.Warning($"MediaCapture thất bại: {args.Message} (Code: {args.Code})");

        await _capture.InitializeAsync(config);

        var firstSource = _capture.FrameSources?.FirstOrDefault().Value;
        if (firstSource != null)
        {
            _frameReader = await _capture.CreateFrameReaderAsync(firstSource);
            _frameReader.FrameArrived += OnFrameReceived;
            await _frameReader.StartAsync();
            _isStreaming = true;
        }

        return _isStreaming;
    }
    catch (UnauthorizedAccessException)
    {
        Log.Error("Ứng dụng chưa được cấp quyền truy cập camera.");
        return false;
    }
    catch (Exception ex)
    {
        Log.Error($"Khởi tạo camera thất bại: {ex}");
        return false;
    }
    finally
    {
        _initLock.Release();
    }
}

2. Bắt đầu luồng video và xử lý đồng bộ hóa UI

MediaCapture hoạt động trên luồng nền, việc gọi hàm khởi tạo từ luồng UI WPF phải được đảm bảo an toàn thông qua Dispatcher.InvokeAsync:

public async Task<int> LaunchVideoStreamAsync()
{
    try
    {
        var result = await Application.Current.Dispatcher.InvokeAsync(async () =>
        {
            var candidates = await EnumerateVideoSourcesAsync();
            if (!candidates.Any())
            {
                Log.Warning("Không tìm thấy thiết bị đầu vào video.");
                return -1;
            }

            var success = await InitializeCameraAsync(candidates[0].Id);
            return success ? 0 : -1;
        });

        return await result.TimeoutAfter(TimeSpan.FromSeconds(6));
    }
    catch (OperationCanceledException)
    {
        Log.Error("Khởi tạo luồng video bị hủy do timeout.");
        return -2;
    }
    catch (Exception ex)
    {
        Log.Error($"Lỗi không mong muốn khi khởi chạy luồng: {ex}");
        return -3;
    }
}

3. Nhúng CaptureElement vào giao diện WPF

Sử dụng WindowsXamlHost để đặt một lưới UWP chứa CaptureElement — thành phần chính để hiển thị luồng trực tiếp:

<!-- Trong XAML -->
<xamlHost:WindowsXamlHost x:Name="CameraHost" Visibility="{Binding IsCameraActive, Converter={StaticResource BoolToVisibility}}" />

Phần code-behind tạo và gắn kết điều khiển:

private void SetupUwpCaptureView()
{
    var grid = new Windows.UI.Xaml.Controls.Grid();
    var capture = new Windows.UI.Xaml.Controls.CaptureElement
    {
        Stretch = Windows.UI.Xaml.Media.Stretch.UniformToFill
    };

    // Gắn nguồn phát từ MediaCapture
    capture.Source = _capture?.Preview;

    grid.Children.Add(capture);

    // Thêm nút điều khiển tùy chọn (ví dụ: phóng to)
    var zoomButton = new Windows.UI.Xaml.Controls.Button
    {
        Content = "🔍",
        Width = 48,
        Height = 48,
        HorizontalAlignment = Windows.UI.Xaml.HorizontalAlignment.Right,
        VerticalAlignment = Windows.UI.Xaml.VerticalAlignment.Top,
        Margin = new Windows.UI.Xaml.Thickness(0, 16, 16, 0)
    };
    zoomButton.Click += (_, _) => ToggleFullscreen();

    grid.Children.Add(zoomButton);

    CameraHost.Child = grid;
}

4. Xử lý khung hình nhận được

Sự kiện FrameArrived được xử lý ở mức nền — bạn có thể sao chép dữ liệu khung vào bộ đệm CPU/GPU, phân tích bằng ML.NET, hoặc chuyển sang mã hóa:

private void OnFrameReceived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
{
    var frame = sender.TryAcquireLatestFrame();
    if (frame?.VideoFrame == null) return;

    using var softwareBitmap = frame.VideoFrame.SoftwareBitmap;
    if (softwareBitmap == null) return;

    // Ví dụ: chuyển sang BitmapSource để binding vào WPF Image
    var bitmapSource = SoftwareBitmapSourceConverter.ToBitmapSource(softwareBitmap);
    
    Application.Current.Dispatcher.Invoke(() =>
    {
        PreviewImage.Source = bitmapSource; // Giả sử PreviewImage là <Image> trong WPF
    });
}

Lưu ý: Việc sao chép từng khung hình qua SoftwareBitmap có thể ảnh hưởng đến hiệu năng — nên cân nhắc dùng Direct3D11CaptureFramePool hoặc IMFMediaBuffer nếu cần tối ưu độ trễ cực thấp.

Thẻ: MediaCapture UWP WPF Windows11 interop

Đăng vào ngày 16 tháng 7 lúc 00:07