概述

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 的类组成:

整体的处理逻辑很清晰:

  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 >=  // fps
            && (
                ||
                ||
                || ))?.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 !!!

Windows Community Toolkit 3.0 - CameraPreview的更多相关文章

  1. Windows Community Toolkit 3.0 新功能 在WinForms 和 WPF 使用 UWP 控件

    本文告诉大家一个令人震惊的消息,Windows Community Toolkit 有一个大更新,现在的版本是 3.0 .最大的提升就是 WinForm 和 WPF 程序可以使用部分 UWP 控件. ...

  2. Windows Community Toolkit 4.0 - DataGrid - Part03

    概述 在上面一篇 Windows Community Toolkit 4.0 - DataGrid - Part02 中,我们针对 DataGrid 控件的 Utilities 部分做了详细分享.而在 ...

  3. Windows Community Toolkit 4.0 - DataGrid - Part02

    概述 在上面一篇 Windows Community Toolkit 4.0 - DataGrid - Part01 中,我们针对 DataGrid 控件的 CollectionView 部分做了详细 ...

  4. Windows Community Toolkit 4.0 - DataGrid - Part01

    概述 在上面一篇 Windows Community Toolkit 4.0 - DataGrid - Overview 中,我们对 DataGrid 控件做了一个概览的介绍,今天开始我们会做进一步的 ...

  5. Windows Community Toolkit 4.0 - DataGrid - Overview

    概述 Windows Community Toolkit 4.0 于 2018 月 8 月初发布:Windows Community Toolkit 4.0 Release Note. 4.0 版本相 ...

  6. Windows Community Toolkit 3.0 - UniformGrid

    概述 UniformGrid 控件是一个响应式的布局控件,允许把 items 排列在一组均匀分布的行或列中,以填充整体的可用显示空间,形成均匀的多个网格.默认情况下,网格中的每个单元格大小相同. 这是 ...

  7. Windows Community Toolkit 3.0 - InfiniteCanvas

    概述 InfiniteCanvas 是一个 Canvas 控件,它支持无限画布的滚动,支持 Ink,文本,格式文本,画布缩放操作,撤销重做操作,导入和导出数据. 这是一个非常实用的控件,在“来画视频” ...

  8. Windows Community Toolkit 3.0 - Gaze Interaction

    概述 Gaze Input & Tracking - 也就是视觉输入和跟踪,是一种和鼠标/触摸屏输入非常不一样的交互方式,利用人类眼球的识别和眼球方向角度的跟踪,来判断人眼的目标和意图,从而非 ...

  9. 与众不同 windows phone (44) - 8.0 位置和地图

    [源码下载] 与众不同 windows phone (44) - 8.0 位置和地图 作者:webabcd 介绍与众不同 windows phone 8.0 之 位置和地图 位置(GPS) - Loc ...

随机推荐

  1. mysql之用户权限管理

    本文内容: 什么是用户权限 恰当的用户权限 查看权限 修改权限 删除权限 首发日期:2018-04-19 什么是用户权限: 每个用户都有自己的用户权限,这些用户权限比如有查询表权限.修改表权限.插入表 ...

  2. 作为IT,你的价值在哪里?

    也许最近是真的被无穷无尽的数据整理.导入.再整理.再导入给恶心到了. 业务部提交的数据只是一个非常初始的数据,IT还得在这个基础上七整八整,对导出的结果还要再做二次导入三次导入,不仅要帮业务部批导生成 ...

  3. VS调试IDAPython脚本

    本文最后修改时间:20180213 1.安装VS插件PTVS , 这一步与第2步中安装版本应该一致,否则最后调试时会连不上 https://github.com/Microsoft/PTVS/ 2.安 ...

  4. AIOps 平台的误解,挑战及建议(下)— AIOps 挑战及建议

    本文篇幅较长,分为上,中,下,三个部分进行连载.内容分别为:AIOps 背景/所应具备技术能力分析(上),AIOps 常见的误解(中),挑战及建议(下). 前言 我大概是 5,6 年前开始接触 ITO ...

  5. 如何设置访问内网web项目

    1.若我的项目搭建在一个linux虚拟机上 2.在内网的一台电脑做以下配置 3.测试访问 ******************************************************* ...

  6. [20170612]FOR ALL COLUMNS SIZE repeat(12c).txt

    [20170612]FOR ALL COLUMNS SIZE repeat(12c).txt --//昨天看了https://jonathanlewis.wordpress.com/2017/06/0 ...

  7. ajax参数

    $.ajax({ type: "GET", url: "Login.ashx", dataType: "text", cache: fals ...

  8. idea2018版tomcat基本配置

    前言 在配置tomcat之前,要先创建一个javaweb的工程 打开idea的主界面,在菜单中点击File,出现以下的图 点击选择 Application Server 点击选择 Tomcat Ser ...

  9. 以太坊之——golang以太坊接口调用

    Go语言具有简单易学.功能强大,可跨平台编译等众多优势,所以这里选择以Go语言切入以太坊. 开始之前需要以下环境: Ubuntu(这里以ubuntu16.04为例) geth Ubuntu16.04安 ...

  10. spark on yarn模式:yarn命令杀除当前的application

    在hadoop/bin目录下有yarn命令 yarn application -kill <applicationId>