Windows Community Toolkit 3.0 - CameraPreview

来源:https://www.cnblogs.com/shaomeng/archive/2018/07/15/9281089.html
-Advertisement-
Play Games

概述 Windows Community Toolkit 3.0 於 2018 年 6 月 2 日 Release,同時正式更名為 Windows Community Toolkit,原名為 UWP Community Toolkit。顧名思義,3.0 版本會更註重整個 Windows 平臺的工具實 ...


概述

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/releases

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

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

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

Windows Community Toolkit Doc - CameraPreview 

Windows Community Toolkit Source Code - CameraPreview

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

 

開發過程

代碼分析

首先來看 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 !!!

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 上一篇文章,學習了併發編程中的volatile,最後取了網上流傳很廣的一張圖來結尾,從圖中可以看出除了volatile變數的讀寫,還有一個叫做CAS的東西,所以這篇文章再來學習CAS。 1、 併發編程三要素-原子性、可見性、有序性 在討論CAS前,我想先討論一下併發編程的三要素,這個應該可以幫助理解 ...
  • 什麼是RESTful? RESTful是一種開發理念,REST是Roy Thomas Fileding在他博文提出的.REST特點;url簡潔,將參數通過url傳遞到伺服器,簡單就是說URL定位資源,用HTTP動詞描述操作. RESTful架構: 每一個URL代表一種資源; 客戶端和伺服器之間,傳遞 ...
  • JRE(Java Runtime Environment Java運行環境) 包括Java虛擬機(JVM Java Virtual Machine)和Java程式所需的核心類庫等,如果想要運行一個開發好的Java程式,電腦中只需要安裝JRE即可。 JDK(Java Development Kit ...
  • 上兩篇文章我向大家介紹了一些線程間的基本通信方式,那麼這篇文章就和大家聊聊volatile關鍵字的相關知識。這個關鍵字在我們的日常開發中很少會使用到,而在JDK的Lock包和Concurrent包下的類則大量的使用了這個關鍵字,因為它有如下兩個特性: 1.確保記憶體可見性 2.禁止指令重排序 接下來就 ...
  • 2.矩陣專欄¶ 吐槽一下:矩陣本身不難,但是矩陣的寫作太蛋疼了 (⊙﹏⊙)汗 還好有Numpy,不然真的崩潰了... LaTex有沒有一個集成了很多常用公式以及推導或者含題庫的線上編輯器? 代碼褲子:https://github.com/lotapp/BaseCode 線上編程系:https://m ...
  • 寫在前面 本文地址:http://www.cnblogs.com/yilezhu/p/9315644.html 作者:yilezhu 上一篇關於Asp.Net Core Web Api圖片上傳的文章使用的是mongoDB進行圖片的存儲,文章發佈後,張隊就來了一句,說沒有使用GridFS。的確博主只是 ...
  • 推薦加【QQ49300063】專業盜取微信密碼,破解微信密碼,查詢微信聊天記錄,不成功不收費!!!! 隨著信息時代的來臨,很多人使用上了微信,微信的出現使得人們的生活變的十便利。人們不僅在工作中使用它,在社交中也讓其發揮了重要的作用。微信現在已經漸漸成為了人們生活中不能缺少的一部分。使用微信除了其方 ...
  • 1、前言 分散式已經成為了當前最熱門的話題,分散式框架也百花齊放,群雄逐鹿。從中心化服務治理框架,到去中心化分散式服務框架,再到分散式微服務引擎,這都是通過技術不斷積累改進而形成的結果。esb,網關,nginx網關 這些中心化服務治理框架現在都是各個公司比較主流的架構,而最近幾年大家炒的比較火的去中 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...