Windows Community Toolkit 3.0 - CameraPreview

概述git

Windows Community Toolkit 3.0 於 2018 年 6 月 2 日 Release,同時正式改名爲 Windows Community Toolkit,原名爲 UWP Community Toolkit。顧名思義,3.0 版本會更注重整個 Windows 平臺的工具實現,而再也不只侷限於 UWP 應用,這從 Release Note 也能夠看出來:https://github.com/Microsoft/WindowsCommunityToolkit/releasesgithub

咱們從今年 3 月份開始陸續針對 Windows Community Toolkit 2.2 版本的特性和代碼實現作了分析,從本篇開始,咱們會對 3.0 版本作持續的分享,首先本篇帶來的關於 CameraPreview 相關的分享。算法

CameraPreview 控件容許在 MediaPlayerElement 中簡單預覽攝像機幀源組的視頻,開發者能夠在所選攝像機實時獲取 Video Frame 和 Bitmap,僅顯示支持彩色視頻預覽或視頻記錄流。express

這是一個很是有用的控件,以前在 Face++ 工做時,咱們作的不少事情都是對攝像頭傳出的視頻幀作人臉檢測或關鍵點標註等操做。因此該控件對攝像頭的控制,以及對視頻幀的傳出,就成了咱們工做的資源源頭,咱們對視頻幀作規範化,再進行算法處理,再把處理後的視頻幀反饋到視頻播放控件中,就能夠完成檢測,人臉美顏處理等不少操做。windows

Windows Community Toolkit Doc - CameraPreview async

Windows Community Toolkit Source Code - CameraPreviewide

Namespace: Microsoft.Toolkit.Uwp.UI.Controls; Nuget: Microsoft.Toolkit.Uwp.UI.Controls;工具

 

開發過程ui

代碼分析編碼

首先來看 CameraPreview 的類結構:

  • CameraPreview.Cpmstants.cs - 定義了 CameraPreview 的兩個常量字符串;
  • CameraPreview.Events.cs - 定義了 CameraPreview 的事件處理 PreviewFailed;
  • CameraPreview.Properties.cs - 定義了 CameraPreview 的依賴屬性 IsFrameSourceGroupButtonVisible;
  • CameraPreview.cs - CameraPreview 的主要處理邏輯;
  • CameraPreview.xaml - CameraPreview 的樣式文件;
  • PreviewFailedEventArgs.cs - 定義了 CameraPreview 的事件處理 PreviewFailed 的參數;

接下來咱們主要關注 CameraPreview.xaml 和 CameraPreview.cs 的代碼實現:

1. CameraPreview.xaml

CameraPreview 控件的樣式文件組成很簡單,就是用戶播放預覽視頻幀的 MediaPlayerElement 和 FrameSourceGroup 按鈕。

<Style TargetType="local:CameraPreview" >
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:CameraPreview">
                <Grid Background="{TemplateBinding Background}">
                    <MediaPlayerElement x:Name="MediaPlayerElementControl" HorizontalAlignment="Left">
                    </MediaPlayerElement>
                    <Button x:Name="FrameSourceGroupButton" Background="{ThemeResource SystemBaseLowColor}" 
                            VerticalAlignment="Top" HorizontalAlignment="Left" Margin="5">
                        <FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE89E;" Foreground="{ThemeResource SystemAltHighColor}" />
                    </Button>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

2. CameraPreview.cs

咱們先來看一下 CameraPreview 的類組成:

總體的處理邏輯很清晰:

  1. 經過 OnApplyTemplate(), InitializeAsync(), SetUIControls(), SetMediaPlayerSource() 等方法初始化控件,初始化攝像頭視頻源組,選擇視頻源賦值 MediaPleyerElement 作展現;
  2. 經過 StartAsync() 方法開始使用攝像頭視頻源,開發者用於展現和獲取每一幀圖像 Bitmap;
  3. 使用完成後,調用 Stop() 來結束並釋放攝像頭資源;

而 CameraPreview 類中出現了一個很重要的幫助類 CameraHelper,它的做用是對攝像頭資源的獲取和視頻幀的獲取/處理,它是 CameraPreview 中的核心部分,下面咱們來看 CameraHelper 的實現:

咱們看到 CameraHelper 類中包括了獲取攝像頭視頻源組,初始化和開始獲取視頻幀,接收視頻幀進行處理,釋放資源等方法,咱們來看幾個主要方法實現:

1. GetFrameSourceGroupsAsync()

獲取視頻源組的方法,使用 DeviceInformation 類獲取全部類別爲 VideoCapture 的設備,再使用 MediaFrameSourceGroup 類獲取全部 mediaFrameSourceGroup,在 groups 中獲取彩色視頻預覽和視頻錄製的全部 group。

public static async Task<IReadOnlyList<MediaFrameSourceGroup>> GetFrameSourceGroupsAsync()
{
    if (_frameSourceGroups == null)
    {
        var videoDevices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
        var groups = await MediaFrameSourceGroup.FindAllAsync();

        // Filter out color video preview and video record type sources and remove duplicates video devices.
        _frameSourceGroups = groups.Where(g => g.SourceInfos.Any(s => s.SourceKind == MediaFrameSourceKind.Color &&
                                                                    (s.MediaStreamType == MediaStreamType.VideoPreview || s.MediaStreamType == MediaStreamType.VideoRecord))
                                                                    && g.SourceInfos.All(sourceInfo => videoDevices.Any(vd => vd.Id == sourceInfo.DeviceInformation.Id))).ToList();
    }

    return _frameSourceGroups;
}

2. InitializeAndStartCaptureAsync()

使用 GetFrameSourceGroupsAsync() 和 InitializeMediaCaptureAsync() 對視頻源組和 MediaCapture 進行初始化;利用 MediaCapture 讀取選擇的視頻源組對應的預覽幀源,註冊 Reader_FrameArrived 事件,開始讀取操做,返回操做結果;

public async Task<CameraHelperResult> InitializeAndStartCaptureAsync()
{
    CameraHelperResult result;
    try
    {
        await semaphoreSlim.WaitAsync();
        ...
        result = await InitializeMediaCaptureAsync();

        if (_previewFrameSource != null)
        {
            _frameReader = await _mediaCapture.CreateFrameReaderAsync(_previewFrameSource);
            if (Windows.Foundation.Metadata.ApiInformation.IsPropertyPresent("Windows.Media.Capture.Frames.MediaFrameReader", "AcquisitionMode"))
            {
                _frameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Realtime;
            }

            _frameReader.FrameArrived += Reader_FrameArrived;

            if (_frameReader == null)
            {
                result = CameraHelperResult.CreateFrameReaderFailed;
            }
            else
            {
                MediaFrameReaderStartStatus statusResult = await _frameReader.StartAsync();
                if (statusResult != MediaFrameReaderStartStatus.Success)
                {
                    result = CameraHelperResult.StartFrameReaderFailed;
                }
            }
        }

        _initialized = result == CameraHelperResult.Success;
        return result;
    }
    ...
}

3. InitializeMediaCaptureAsync()

上面方法中使用的初始化 MediaCapture 的方法,首先獲取預覽幀源,獲取順序是彩色預覽 -> 視頻錄製;接着判斷它支持的格式,包括視頻幀率(>= 15 幀),媒體編碼格式的支持(Nv12,Bgra8,Yuy2,Rgb32),按照視頻寬高進行排序;對支持狀態進行判斷,若是狀態可用,則返回默認最高分辨率;同時該方法會對權限等進行判斷,對錯誤狀態返回對應狀態;只有狀態爲 CameraHelperResult.Success 時纔是正確狀態。

CameraHelperResult 中對應的錯誤狀態有:CreateFrameReaderFailed,StartFrameReaderFailed,NoFrameSourceGroupAvailable,NoFrameSourceAvailable,CameraAccessDenied,InitializationFailed_UnknownError,NoCompatibleFrameFormatAvailable。

private async Task<CameraHelperResult> InitializeMediaCaptureAsync()
{
    ...
    try
    {
        await _mediaCapture.InitializeAsync(settings);

        // Find the first video preview or record stream available
        _previewFrameSource = _mediaCapture.FrameSources.FirstOrDefault(source => source.Value.Info.MediaStreamType == MediaStreamType.VideoPreview
                                                                                && source.Value.Info.SourceKind == MediaFrameSourceKind.Color).Value;
        if (_previewFrameSource == null)
        {
            _previewFrameSource = _mediaCapture.FrameSources.FirstOrDefault(source => source.Value.Info.MediaStreamType == MediaStreamType.VideoRecord
                                                                                    && source.Value.Info.SourceKind == MediaFrameSourceKind.Color).Value;
        }

        if (_previewFrameSource == null)
        {
            return CameraHelperResult.NoFrameSourceAvailable;
        }

        // get only formats of a certain framerate and compatible subtype for previewing, order them by resolution
        _frameFormatsAvailable = _previewFrameSource.SupportedFormats.Where(format =>
            format.FrameRate.Numerator / format.FrameRate.Denominator >= 15 // fps
            && (string.Compare(format.Subtype, MediaEncodingSubtypes.Nv12, true) == 0
                || string.Compare(format.Subtype, MediaEncodingSubtypes.Bgra8, true) == 0
                || string.Compare(format.Subtype, MediaEncodingSubtypes.Yuy2, true) == 0
                || string.Compare(format.Subtype, MediaEncodingSubtypes.Rgb32, true) == 0))?.OrderBy(format => format.VideoFormat.Width * format.VideoFormat.Height).ToList();

        if (_frameFormatsAvailable == null || !_frameFormatsAvailable.Any())
        {
            return CameraHelperResult.NoCompatibleFrameFormatAvailable;
        }

        // set the format with the higest resolution available by default
        var defaultFormat = _frameFormatsAvailable.Last();
        await _previewFrameSource.SetFormatAsync(defaultFormat);
    }
    catch (UnauthorizedAccessException)
    { ... }
    catch (Exception)
    { ... }

    return CameraHelperResult.Success;
}

4. Reader_FrameArrived(sender, args)

獲取到視頻幀的處理,觸發 FrameArrived 事件,傳入 VideoFrame,開發者能夠對 frame 作本身的處理。

private void Reader_FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
{
    // TryAcquireLatestFrame will return the latest frame that has not yet been acquired.
    // This can return null if there is no such frame, or if the reader is not in the
    // "Started" state. The latter can occur if a FrameArrived event was in flight
    // when the reader was stopped.
    var frame = sender.TryAcquireLatestFrame();
    if (frame != null)
    {
        var vmf = frame.VideoMediaFrame;
        EventHandler<FrameEventArgs> handler = FrameArrived;
        var frameArgs = new FrameEventArgs() { VideoFrame = vmf.GetVideoFrame() };
        handler?.Invoke(sender, frameArgs);
    }
}

 

調用示例

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"      
    xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
    mc:Ignorable="d">    

    <StackPanel Orientation="Vertical" Margin="20">
    <controls:CameraPreview x:Name="CameraPreviewControl"> 
        </controls:CameraPreview>
        <Image x:Name="CurrentFrameImage" MinWidth="300" Width="400" HorizontalAlignment="Left"></Image>
    </StackPanel>
</Page>
// Initialize the CameraPreview control and subscribe to the events
CameraPreviewControl.PreviewFailed += CameraPreviewControl_PreviewFailed;
await CameraPreviewControl.StartAsync();
CameraPreviewControl.CameraHelper.FrameArrived += CameraPreviewControl_FrameArrived;

// Create a software bitmap source and set it to the Xaml Image control source.
var softwareBitmapSource = new SoftwareBitmapSource();
CurrentFrameImage.Source = softwareBitmapSource;

private void CameraPreviewControl_FrameArrived(object sender, FrameEventArgs e)
{
    var videoFrame = e.VideoFrame;
    var softwareBitmap = e.VideoFrame.SoftwareBitmap;
    var targetSoftwareBitmap = softwareBitmap;

    if (softwareBitmap != null)
    {
        if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 || softwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight)
        {
            targetSoftwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
        }

        await softwareBitmapSource.SetBitmapAsync(targetSoftwareBitmap);
    }
}

 

總結

到這裏咱們就把 Windows Community Toolkit 3.0 中的 CameraPreview 的源代碼實現過程講解完成了,但願能對你們更好的理解和使用這個擴展有所幫助。

相信你們在作到不少跟攝像頭有關的功能,好比人臉檢測,視頻直播的美顏處理,貼紙操做等操做時都會用到這個控件。若是你們有好玩的應用場景,歡迎多多交流,謝謝!

最後,再跟你們安利一下 WindowsCommunityToolkit 的官方微博:https://weibo.com/u/6506046490你們能夠經過微博關注最新動態。

衷心感謝 WindowsCommunityToolkit 的做者們傑出的工做,感謝每一位貢獻者,Thank you so much, ALL WindowsCommunityToolkit AUTHORS !!!

相關文章
相關標籤/搜索