概述 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="" Foreground="{ThemeResource SystemAltHighColor}" /> </Button> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
2. CameraPreview.cs
我們先來看一下 CameraPreview 的類組成:
整體的處理邏輯很清晰:
- 通過 OnApplyTemplate(), InitializeAsync(), SetUIControls(), SetMediaPlayerSource() 等方法初始化控制項,初始化攝像頭視頻源組,選擇視頻源賦值 MediaPleyerElement 做展示;
- 通過 StartAsync() 方法開始使用攝像頭視頻源,開發者用於展示和獲取每一幀圖像 Bitmap;
- 使用完成後,調用 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 !!!