65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Windows 10 中使用库构建相机应用

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (10投票s)

2015 年 8 月 24 日

CPOL

36分钟阅读

viewsIcon

75704

downloadIcon

3552

在本文中,我将解释 Windows.Media.Capture 命名空间中的 MediaCapture 元素,以及如何在 Windows Runtime 中使用它来创建利用相机捕获照片和视频的应用程序。此外,文章还展示了如何捕获记录的照片或视频列表。

引言和背景

这是另一篇关于 Windows 10 应用程序编程的文章,在本文中我将讨论 Windows Runtime 编程中的一些媒体元素,以及如何从运行 Windows 10 的设备上捕获媒体内容(例如,图像、视频和音频)。通常,既然这是关于 Windows Runtime 的,您也可以将相同的代码用于 Windows 8、8.1 以及 Windows Phone 8、8.1 应用程序。但是,在本文中我将只讨论 Windows 10 应用程序。

我一直在 Windows Runtime 中编程新事物,因为我曾是 Windows 的内测用户,一段时间后,我发现了一些我喜欢和不喜欢 Windows Runtime 的地方。我将讨论这两方面最突出的地方。

  1. 优点是:由于整个框架在设计时就考虑了异步编程,因此维护应用程序的响应性非常容易。您可以随时在函数前添加 async 关键字,并使用 await 来调用 API 的其他函数,虽然很多后台线程可能会限制内存,但对于小型应用程序来说,这个框架非常棒!在我看来,它的性能更好。Windows Runtime 是直接构建在原生代码之上的,以 Win32 为基础,因此在 Windows Runtime 中编写高效代码要容易得多,而且 .NET 框架也受支持(并非整个 .NET 框架,只是一部分)。
  2. 缺点是:应用沙盒对您的应用程序的抽象层非常狭窄,您甚至无法与应用程序自身的 AppData 文件夹目录外部的目录进行通信或使用它们。如果您尝试将数据写入该目录之外的任何位置,操作系统将提示您“访问被拒绝”异常。此外,应用程序清单中当前添加的功能数量非常少,您甚至无法利用机器的全部功能,您只能设计一个应用程序。要执行有用的操作,您需要获得另一个框架的支持,例如 Win32、C/C++ 原生编程,或者您必须使用 .NET 框架本身,Windows Runtime 应用程序确实支持 .NET 框架。

话虽如此,在开始撰写本文之前,我们先来谈谈其他一些事情。在接下来的部分,我将讨论

  1. Windows 10 — 不是从垃圾或广告的角度来看,我知道您对此感到震惊!:)
  2. Windows.Media.Capture Windows Runtime 命名空间及其提供的功能
  3. 如何在我们的应用程序中启用捕获图像和音频!
  4. 使用原生的 Windows Runtime API(如 StorageFile 对象)存储图像和视频文件。

在本文结束时,您可以随时下载并构建本文提供的源代码包,源代码包含我为本文制作的示例,您也可以自行使用。我建议您阅读本文,如果您希望我修复或深入解释某些内容,请投票并留下您的反馈。:)

文章和示例中使用的通用概念

我相信在深入了解 Windows Runtime 和 Windows 10 的技术概念之前,我必须分享一些关于它们的知识,我必须教您这些框架的基础知识,以及在尝试构建利用 Windows Runtime 中提供的媒体捕获和录制功能的应用程序时应如何使用它们。

Windows 10 — 应用程序功能

在 Windows Runtime 应用程序中,您必须首先定义应用程序的所有功能,然后操作系统才会允许它执行任何操作。这类似于在 Android 编程中声明 uses-featureuses-permission如果您曾经进行过 Android 编程。它会通知用户您的应用程序,此外,它还将允许用户稍后撤销对这些权限的访问。一个好的抽象(IMO)但从开发者的角度来看,您还需要处理另一个模块!例如,现在,您还必须确保应用程序有权访问资源,例如相机。用户可以随时从设置中撤销对相机的访问,您自己无法更改设置。您最多只能请求用户更改设置以允许您的应用程序使用。

对于基本的相机类应用程序(如本文将要介绍的),您需要网络摄像头和麦克风的权限。MediaCapture 对象(稍后讨论)的 StartRecordAsync 函数要求您也允许使用麦克风,好吧,您可以关闭麦克风,但仍需要权限。如果您不允许这些权限。将会抛出异常!

每个应用程序都需要一个包清单;与 Android 应用程序一样,清单文件包含商店处理应用程序类型所需的信息。它包含以下(但不限于)权限,

  1. 应用程序详细信息,例如
    显示名称。
    应用程序标题。
    入口点:要执行的类。
    应用程序的描述。
  2. 视觉资产
    应用程序的徽标和启动屏幕图像。
  3. 功能
    应用程序加载和运行所需的权限。
  4. 声明
    应用程序打算执行的服务和其他后台进程;例如警报服务、文件打开和服务和文件保存服务。
  5. 应用程序的 URI 和打包信息。

所有这些都必须用于识别您的应用程序、其目的、其意图和功能。用户将在需要时看到您的应用程序为他们工作,例如,如果您想将您的应用程序显示为 Internet 客户端,您可以在清单中设置设置,Windows 将在用户需要的应用程序列表中显示图标。这不仅仅是它的预期用途,用户还会在商店中看到您的应用程序列在他们有兴趣下载应用程序的类别下。名称、功能、视觉资产用于在商店中预览应用程序。其他由操作系统用于后台工作。

在接下来的部分,我将向您展示如何编辑清单(在设计视图中)以允许您的应用程序访问摄像头和麦克风资源。

在本文的后续部分,您将发现 Windows Runtime 使用这些功能来确定您的应用程序是否有权使用该资源。它为用户提供了一层隐私,该层抽象了应用程序使用资源而无需让用户知道。在设置应用程序中,用户可以管理哪些应用程序拥有权限以及必须拒绝哪些应用程序访问。例如,您可以拒绝一个您不想让它访问互联网的应用程序访问互联网,其他权限也一样。您可以拒绝访问摄像头、麦克风和文件系统资源的请求。在我看来,这是 Windows Runtime 中的一项很棒的功能。

Windows.Media.Capture 命名空间 — 深入概述

大多数与媒体相关的 API 和命名空间都存在于 Windows.Media.XXXX 命名空间中,音频控制器、视频控制器以及其他允许我们处理媒体资源(如摄像头、麦克风)的对象都存在于这些命名空间下。它们因分类目的而被分开。如果需要,您可以在应用程序中引用所有这些。

在本文中,我将只引用和讨论 Windows.Media.Capture 命名空间,因为这个命名空间包含创建我们的应用程序所需的类、枚举、结构和其他内容。几乎所有现代笔记本电脑或计算机系统都安装了网络摄像头和麦克风。因此,Windows 10 的 UAP 平台为应用程序原生支持这些功能,如果功能不存在,它将通知程序员。

Windows.Media.Capture 命名空间实际上提供了类、枚举、结构和其他内容,用于与媒体设备(如摄像头和麦克风)以及安装在它们上面的其他传感器进行通信或交互。例如,Windows.Media.Capture 命名空间中的 MediaCapture 对象包含 ThermalStatus 字段的定义,该字段提供设备的散热状态。捕获数据可能会导致设备发热,如果设备具有暴露散热状态的功能(例如,安装了辅助设备),它将允许您捕获散热状态并停止捕获照片或视频。此外,还包含一个事件,可用于确定散热状态何时降至较低水平,以便您可以继续从该设备捕获视频。

用于管理设备散热状态的实际枚举是 MediaCaptureThermalStatus。此枚举只有两个值,

  1. 正常 (0;零)
  2. 过热 (1)

希望这些是唯一需要的对象。您也可以深入研究此命名空间或 Windows.Media 命名空间集合中的更高级主题。Windows.Media 命名空间(集合)允许您处理其他媒体类型,例如实际的音频和视频帧或照片帧。Windows.Media.Capture 提供了捕获照片、音频或视频的方法。而要深入处理其他内容则需要一些低级编程,这些命名空间提供了相应的对象。

Visual Studio 2015 和 Windows 10 应用

Visual Studio 2015 预装了构建 Windows 10 应用程序所需的资源,已有许多文章供您阅读和学习如何在 Visual Studio 2015 中创建您的应用程序!您会发现 这篇 CodeProject 文章很有帮助,而且符合您的口味。:-)

Visual Studio 2015 是(目前)唯一支持 Windows 10 应用程序编程的 IDE,此外,您还需要运行 Windows 10 才能编写 Windows 10 的软件。

提示:如果您没有 Windows 10 或 Visual Studio 2015,您仍然可以使用该代码为 Windows 8、8.1 或 Windows Phone 8、8.1 应用程序编写应用程序!所以,请继续阅读,并在结尾处留下您的反馈,以获得 Windows 8、8.1 应用程序的帮助!

编写应用程序

让我们开始讨论本文的重点,编写应用程序的源代码。本节将分为两个主要部分:一个用于编写 UI 代码,另一个用于编写应用程序的后端代码。这两部分都将只讨论必需的部分,因为本文的目的是教授 Windows Runtime 中带 XAML 的控件(请注意,本文涵盖 C# 和 XAML,在以后的某个文章中我将展示相同的应用程序的 C++ 编程),以及如何配置应用程序以捕获内容并在屏幕上预览。此外,还将讨论捕获和存储内容到应用程序数据文件夹!

随着本节的进行,我将向您展示创建应用程序所需的步骤,因此您可以将本指南视为创建照片、音频和视频捕获应用程序的分步指南。

构建 XAML 页面

本指南的第一步是构建 XAML,示例中的 XAML 代码只是一个单独的 Page 控件,其中包含一些其他子控件,用于创建应用程序的 UI。显示相机画面的应用程序通常会在 XAML 中使用 CaptureElement 控件。CaptureElement 控件允许您将 Source 属性绑定到媒体设备,例如相机(网络摄像头)。Windows Runtime 将来自摄像头、麦克风和其他媒体设备的内容提供给此控件,然后由该控件在屏幕上渲染。这类似于在视频元素上预览视频帧的字节流。

MSDN CaptureElement 文档

渲染来自捕获设备(如摄像头或网络摄像头)的流。

在 Page 的源代码中,我将仅使用此控件,然后以编程方式为其提供 Windows Runtime 管理如何传输和渲染到屏幕上的视频帧流等内容。对我们来说的好消息是,创建此控件的语法只有一行(除非您还想为控件添加其他功能),

<CaptureElement Height="650" Name="captureElement" />

这就够了!

现在,既然我们已经创建了捕获媒体内容的控件,我们就需要创建一些按钮来处理事件。虽然这些界面不是原生提供的,所以我们需要为我们的用途构建它们。XAML 提供了许多控件供我们在应用程序中使用。话虽如此,我们需要我们的应用程序提供控件来更改视频质量、视频编码类型,我们还需要它来启动录制过程并停止录制。我们还需要我们的应用程序能够捕获照片,并在不应该捕获时进行区分;例如,在已经录制视频时。我们还需要能够在需要时静音视频中的麦克风。属性等都由 Windows.Media.Capture.MediaCapture 对象提供给我们,现在我们需要通过控件提供给用户,用户可以使用这些控件触发更改应用程序状态和内容(如视频质量等)的请求。

由于这是一个 Windows 10 应用程序,我想利用提供的原生控件,而不是重新发明轮子。在接下来的部分,我将使用内置控件来创建我们应用程序的 UI!我们需要在应用程序中实现以下功能,

  1. 捕获照片
  2. 录制视频
    录制视频时
    1. 允许用户静音/取消静音麦克风。
    2. 允许他们停止录制视频;保存视频
  3. 在开始录制之前更改视频编码和视频质量。

此外,还要进行一些检查,以确保用户在录制视频时不会捕获照片,等等!如果不需要,可以删除这些检查,也可以添加其他检查,例如在录制视频时更改质量。有许多高级主题未在本基本指南中涵盖。

为了实现这些功能,我使用了 Windows 应用程序的 AppBar 控件,它使我们能够以托盘的形式向用户提供控件,用户可以在需要时打开和关闭它。此外,它对用户来说具有出色的 UI 和 UX,易于使用。您可能还想知道的另一件事是,Windows 10 在 Symbol 枚举中提供了更好的图标。您可以在 MSDN 文档中找到有关 SymbolAppBar 的更多出色资源。此外,我大部分时间使用 AppBarButton(它继承自 Button 对象,因此提供了 Button 对象的所有事件、函数和成员,我们对 Button 对象的 Click 事件感兴趣)创建按钮和控件。这些按钮和控件将用于创建相机处理的控件栏(命令栏),我们可以使用这些按钮来更改当前连接到网络摄像头和麦克风MediaCapture 对象的状态。

<Page.BottomAppBar>
    <AppBar Background="Transparent" IsOpen="True">
        <Grid>
            <AppBarButton Icon="Camera" Name="cameraIcon" Click="button_Click" />
            <AppBarButton Icon="Video" Name="videoIcon" Click="button_Click"  Margin="70, 0, 0, 0" />
            <AppBarButton Icon="RotateCamera" Name="rotateCameraIcon" Click="button_Click" Margin="140, 0, 0, 0" />
            <ComboBox Margin="230, 10, 0, 0" Name="videoQuality" SelectionChanged="comboBox_SelectionChanged">
                <ComboBoxItem IsSelected="True">Auto</ComboBoxItem>
                <ComboBoxItem>1080p</ComboBoxItem>
                <ComboBoxItem>720p</ComboBoxItem>
                <ComboBoxItem>VGA</ComboBoxItem>
            </ComboBox>
            <ComboBox Margin="320, 10, 0, 0" Name="videoType" SelectionChanged="comboBox_SelectionChanged">
                <ComboBoxItem IsSelected="True">MP4</ComboBoxItem>
                <ComboBoxItem>WMV</ComboBoxItem>
            </ComboBox>
            <AppBarButton Icon="Microphone" Margin="400, 0, 0, 0" Name="muteIcon" Click="button_Click" />          
            <AppBarButton Icon="Library" HorizontalAlignment="Right" Name="libraryIcon" Click="button_Click" />
        </Grid>
    </AppBar>
</Page.BottomAppBar>

上面的代码充当我们应用程序的命令栏,从而使我们能够与应用程序的 UI 进行交互,以启动应用程序中的不同命令和功能。

上面的栏是显示给用户的。我想说的是,Windows 团队在构建出色的 Windows UI 和 UX 字体方面付出了巨大的努力。所有这些图标(相机、视频等)实际上是字符(如果您之前不知道的话)。它们被映射到每个字符的 Unicode 代码,然后这些字形在屏幕上渲染。这样做的好处是(一个简单的好处)您不必为多种颜色和主题创建多个图像,您可以像更改文本颜色一样更改这些图标的颜色!

到目前为止,我们已经创建了应用程序的 UI,现在我们需要编写后端代码,以便实际向用户显示一些他们可以与之交互并在他们机器上捕获和存储的内容。

为什么不在 XAML 中设置 Source 属性?

无论如何,这都会困扰初学者,为什么我没有在 XAML 中设置 Source 属性?答案很简单。XAML 用于初始设置,XAML 中允许的设置已经存在,例如系统资源、系统主题和其他类似内容。如果 XAML 中传递的值为 null — 直到我们在接下来的代码中显式调用 capture.InitializeAsync() 函数,我们的 capture 对象也为 null,这将在应用程序启动时导致更多麻烦 — 它将导致引发异常,通常是 NullReferenceException 类型。看下面,已经声明这是个麻烦!

出于此目的,我们将所有此类设置留到以后处理,我们可以在构造函数中异步设置环境和其他内容。然后,一旦一切就绪,我们就可以初始化我们应用程序的 XAML 视图,因为设置资源所需的时间不到 1 秒(在我的系统上,您的系统可能需要 2 秒,但在奔腾处理器上是 3 秒;如果它们支持的话)来初始化所有内容并使您的设备和应用程序准备好进行处理。然后,您可以根据需要显示内容并绑定所有内容。

处理事件和任务

我们的 UI 现在已设置完毕,由于相机尚未绑定到显示屏等,因此显示整个应用程序视图没有用。在后端代码运行之前,向用户显示的唯一内容是我们刚刚使用 AppBar 创建的命令栏!

现在,在本节中,我将向您展示如何将相机绑定到您的应用程序,更改应用程序状态,运行 MediaCapture 对象的功能以开始捕获照片和视频或停止捕获。所有这些都应被视为使我们的应用程序完全正常运行所必需的!我在这里不会使用其中的一些功能,但您可以为自己的应用程序考虑使用它们。

1. 未使用 ThermalStatus 属性

我将不使用命名空间中的 ThermalStatus 属性,因为为了本指南的目的,没有必要使用它,而且我不确定我的相机是否支持它,但在您的 Windows 应用商店应用程序的情况下,您必须始终考虑使用该对象来确定您的设备是否应该继续捕获数据,或者它是否需要休息一下,以防止它完全损坏!

此外,您还可以使用应用程序中的 ThermalStatusChanged 事件来确定摄像头是否过热或是否已准备好再次使用。使用这些将为您的应用程序提供更好的用户体验,并获得良好的评价。虽然它们不是必需的,但用户通过加热它而不关闭它来损坏摄像头,没问题。但是,我个人建议您使用它们以应对任何意外情况。:-)

2. 提供面部识别!

对于那些想要用网络摄像头识别门前的人来说,这里有个好消息。Windows Runtime 原生支持面部识别,您可以在您的应用程序中使用这些库和对象来使其更好!Windows.Media.FacialAnalysis 命名空间提供了您可能感兴趣的对象,用于在查看网络摄像头数据时识别或存储面部模式。

发生的情况是,您必须将视频帧传递给这些库,它们会检测视频帧中是否有人脸。如果检测到,它们会通知您,您还可以跟踪帧中的人脸,这与 Windows 10 的原生相机应用程序中的做法类似。目前我上传的源代码不支持此功能;将来可能会支持

设置应用程序

话虽如此,现在让我们考虑为我们应用程序的进一步处理和功能处理设置应用程序。我们知道我们有一个空的控件(CaptureElement)在等待一些内容被传递以进行渲染,为此,我们需要找到首先执行的函数。第一个触发的函数是我们应用程序 Page 的 OnNavigatedTo(e) 事件处理程序。在事件处理程序中,我们可以进行设置并将内容传递给 Page 中的 CaptureElement 控件,这样当应用程序启动时,用户就可以像启动相机应用程序一样使用他们的相机。

层级是,我们必须在对 MediaCapture 对象执行任何操作之前调用 InitializeAsync() 函数。必须调用此函数,因为 Windows Runtime 会为我们设置大部分内容,如果我们不这样做,我们将开始捕获异常而不是媒体内容。所以,首先我们调用此函数,然后在此调用之后,我们必须将捕获对象与渲染内容的控件关联起来,然后才能开始预览捕获的内容。为了捕获内容并在屏幕上预览,您必须遵循此层级。看看下面的代码,我将在下面解释其目的,

private async void init()
{
    await capture.InitializeAsync();                                // (1)

    captureElement.Source = capture;                                // (2)
    await capture.StartPreviewAsync();                              // (3)

    #region Error handling
    MediaCaptureFailedEventHandler handler = (sender, e) =>
    {
         System.Threading.Tasks.Task task = System.Threading.Tasks.Task.Run(async () =>
         {
              await new MessageDialog("There was an error capturing the video from camera.", "Error").ShowAsync();
         });
    };
            
    capture.Failed += handler;
    #endregion
}

现在,请理解以下列表以了解正在调用的层级。

  1. 首先,InitializeAsync() 函数初始化组件并创建实例(请注意,这与在应用程序中调用 new MediaCapture() 不同),以便您现在可以在应用程序中将其用于渲染目的。此调用也用于确定用户设置,以确定用户是否允许您的应用程序使用资源。在 MSDN 上阅读更多信息
  2. 您将 capture 对象绑定到 XAML captureElement 控件。这将在屏幕上为用户渲染传入的内容流!
  3. 这会激活流并将其传递给视图供用户观看。

所有这些函数都是异步调用的,允许您的应用程序保持响应性。

提示:我在代码中添加了一个处理程序,以防在捕获内容时出现任何问题,它会向您显示错误消息。

处理事件和任务 — Jr。

抱歉标题相同,在本节中,我将展示实际完成所有工作的代码。如果您注意到上面的 XAML 代码,您会发现每个按钮都附加到同一个 Click 事件处理程序。我这样做是为了消除冗余,因为基于用户使用的图标将执行相同的事情。我编写了处理程序,以便根据图标(在代码中我使用的是Name;在 UI 中不可见)来处理按钮,以便可以重用相同的代码库。:)

private void button_Click(object sender, RoutedEventArgs e)
{
    if((sender as AppBarButton).Name == "cameraIcon")
    {
        // Capture the image
        capturePhoto();
    } else if((sender as AppBarButton).Name == "videoIcon")
    {
        // Start recording
        alterRecording();
    } else if((sender as AppBarButton).Name == "rotateCameraIcon")
    {
        invertCamera();
    } 
    else if ((sender as AppBarButton).Name == "muteIcon")
    {
        muteUnmute();
    } else if ((sender as AppBarButton).Name == "libraryIcon")
    {
        App.RootFrame.Navigate(typeof(Library));

        cleanResources();
    }
}

需要清理!

在此文章的上一版本中,没有包含库。在此版本中,我还包含了渲染屏幕上的数据且不需要您的摄像头的库。在这种情况下,最好将相机与您的应用程序断开连接,否则它将不断地将视频帧流发送到您的 capture 元素。这会浪费资源。为此,您应该考虑清理资源并在您的应用程序重新激活以再次使用摄像头时重新初始化。

private async void cleanResources()
{
    captureElement.Source = null;
    await capture.StopPreviewAsync();

    if(isRecording)
    {
        await capture.StopRecordAsync();
    }
    capture.Dispose(); // Dispose the resource
}

此外,应用程序还有一个更改。我没有在 OnNavigatedTo(e) 事件中处理设置资源,而是将加载资源的过程放在了那里,这提高了应用程序的性能。

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    // Page has become active, load the resources.
    capture = new MediaCapture();
    loading.Visibility = Visibility.Visible;

    init();
}

这些函数是我们应用程序打算执行的高级任务。在接下来的部分,我将分解这些函数块,向您展示如何在您的应用程序中执行这些操作。

进一步程序所需的唯一要求是理解 C# 语言中的异步编程模型。C# 语言中的异步编程(正如我们今天所知的)是在其第五版(现在是第六版;写作时)中引入的,并且将继续发展。MSDN 是 C# 编程教程、示例和注释的绝佳资源。因此,在继续之前(虽然不是必需的,但)您应该考虑理解 C# 中的异步编程

捕获照片

继续按钮点击处理程序,第一个函数是为用户捕获图像的函数。好吧,既然 Windows Runtime 已经覆盖了我们,我们实际上不必做任何事情,我们只需要指定两件事

  1. 存储文件的位置
    创建了 StorageFile,您可以为文件指定其他设置,例如文件名冲突情况
  2. 图像编码格式,JPEG、PNG 等。
    这些是必需的,用于将捕获元素的字节流转换为您的文件系统。然后它们将作为有效的图像在您的计算机上渲染。
  3. 录制视频时是否捕获照片?
    您可以在这两种情况下都尝试一下,在我的示例中,我在用户录制视频时忽略了捕获!

在此基础上,您可以编写处理捕获照片并将其存储在文件系统某处请求的函数。这两者都可以使用 Windows Runtime API 用一行代码完成。但由于我们需要额外的条件,我们将把它稍微扩展一下。

private async void capturePhoto()
{
     if(!isRecording)
     {
          var file = await (await ApplicationData.Current.LocalFolder.CreateFolderAsync("Photos",     
                                      CreationCollisionOption.OpenIfExists)
                                      ).CreateFileAsync("Photo.jpg", 
                                      CreationCollisionOption.GenerateUniqueName);

          await capture.CapturePhotoToStorageFileAsync(ImageEncodingProperties.CreateJpeg(), file);
     } else
     {
          await new MessageDialog("Application is currently recording your camera, please stop recording and try again.", "Recording").ShowAsync();
     }
}

上面的代码有两个路径,一个在用户录制视频时执行,另一个在用户未录制视频时执行。一条路径显示错误消息,另一条路径是我们感兴趣的路径,它在应用程序数据文件夹中创建一个名为“Photo.jpg”的新文件,并且还请注意应用了 CreationCollisionOption.GenerateUniqueName,这使我们能够处理文件已存在的情况。在这种情况下,文件将获得一个唯一的名称,例如在后面附加一个整数。

您可以根据需要添加闪烁、音频效果和其他内容!这有点花哨,所以我忽略了。

录制视频

讨论了这些,现在要涵盖的下一个主题是录制视频。过程类似,所有这些都由 Windows Runtime 本身完成,我们实际上不必编写任何内容,只需调用函数即可完成。但是,在实际开始录制之前,仍有一些事情需要考虑。我们仍然需要传递一些参数,

  1. 我们将存储录制视频的存储文件。
    与上面相同,没什么不同……哦,是的,只有格式扩展名。
  2. 视频必须录制的编码。

您在上一节中看到了,我们也做了同样的事情,我们创建了基于格式的图像!在这个模块中也做了同样的事情,我们创建了一个视频编码来使用,MP4 或 WMV。我们还传入视频的质量,1080p,720p 等。然后这些用于配置将内容存储在文件系统上的设置。Windows Runtime 已经覆盖了我们,不用担心!:)

private async void alterRecording()
{
     if(isRecording)
     {
          // Stop recording
          await capture.StopRecordAsync();
          videoIcon.Foreground = new SolidColorBrush(new Windows.UI.Color() { A = 255, R = 0, G = 0, B = 0 }); // Black
          isRecording = false; // Not recording any more
     } else
     {
          encoding = getVideoEncoding(); // Get the current encoding selection.

          // Start recording
          var file = await (await ApplicationData.Current.LocalFolder.CreateFolderAsync("Videos", 
                                  CreationCollisionOption.OpenIfExists)).CreateFileAsync(
                                  string.Format("Recording_{0}-{1}.{2}", 
                                  myEncoding, 
                                  myQuality, 
                                  (myEncoding == "MP4") ? "mp4" : "wav"), 
                                  CreationCollisionOption.GenerateUniqueName);

          await capture.StartRecordToStorageFileAsync(encoding, file);

          videoIcon.Foreground = new SolidColorBrush(new Windows.UI.Color() { A = 255, R = 255, G = 0, B = 0 }); // Red
          isRecording = true; // Capturing the video stream.
     }
}

上面的代码捕获和停止捕获视频流,并将缓冲区保存到文件系统中以供将来使用。创建的文件必须具有指定的名称和格式扩展名。上面的代码足够灵活,可以根据用户在开始录制事件时选择的内容更改扩展名和其他设置。我想分享 getVideoEncoding 函数的内容,因为上面代码中提到了它,不在此处分享不诚实。

private MediaEncodingProfile getVideoEncoding()
{
     VideoEncodingQuality quality = VideoEncodingQuality.Auto;
     myQuality = "Auto";

     switch (videoQuality.SelectedIndex)
     {
          case 2:
              quality = VideoEncodingQuality.HD1080p;
              myQuality = "1080p";
              break;
          case 3:
              quality = VideoEncodingQuality.HD720p;
              myQuality = "720p";
              break;
          case 4:
              quality = VideoEncodingQuality.Vga;
              myQuality = "VGA";
              break;
          default:
              break;
     }

     myEncoding = (videoType == null || videoType.SelectedIndex == 0) ? "MP4" : "WMV";

     return (videoType == null || videoType.SelectedIndex == 0) ?
          MediaEncodingProfile.CreateMp4(quality) :
          MediaEncodingProfile.CreateWmv(quality);
}

每次在 UI 中更改设置时都会执行此函数,请查看命令栏并查找提供的 ComboBox 控件。它们用于更改保存在计算机上的视频编码和视频质量。每次更改设置时,都会执行此函数来选择视频编码和视频文件质量的当前设置。

另请注意,这些并非所有设置。我只提供了一些质量类型和编码类型。其他是音频编码,我没有在此代码中讨论,但它们存在,并且可以用于创建用于将内容存储在文件系统上的配置文件。质量设置也低至 VGA 质量,但在大多数情况下,您只需要提供 Auto 设置。这适合您的视频控制器和其他设备,以免您遇到任何错误。

预览镜像

通常,当您构建用于在网络上共享视图的应用程序时,您将不得不以镜像视图共享内容,通常用户不喜欢以一种似乎与他们相反的方式看待自己,换句话说,是颠倒的。添加镜像效果就像将图像水平旋转 180°,然后像在镜子中看到自己一样预览内容。

Windows Runtime 虽然提供了一个使用 SetPreviewMirroring(true) 设置预览镜像的函数,但不建议使用。相反,您应该始终使用 FlowDirection 属性并将其设置为 RightToLeft 值。这将按要求颠倒图像。

您也可以以编程方式执行此操作,

captureElement.FlowDirection = FlowDirection.RightToLeft;
静音/取消静音麦克风

最后,在本指南中,我想展示在录制视频时静音/取消静音麦克风的代码。MediaCapture 对象为您提供了音频、视频控制器的只读成员。您可以使用它们来更改这些设备的状态,您可以静音/取消静音音频控制器,同样您也可以处理视频控制器。

  1. AudioDeviceController
    此成员用于控制音频控制器,在本节中,我将使用此成员来控制静音和/或取消静音设备的功能。
  2. CameraStreamState
    获取相机流的当前流状态。
  3. MediaCaptureSettings
    您的捕获对象的设置。
  4. ThermalStatus
    上面已讨论过,它提供了有关设备散热状态的信息。
  5. VideoDeviceController
    控制视频控制器设置的对象 — 在此处不讨论。

有关它们的文档和其他信息,请参阅 MSDN 上MediaCapture 属性部分。

在本节中,我将使用 AudioDeviceController 来更改麦克风的静音或取消静音状态。

private void muteUnmute()
{
     if(muted)
     {
          // Unmute
          capture.AudioDeviceController.Muted = false;
          muted = false;
          muteIcon.Foreground = new SolidColorBrush(new Windows.UI.Color() { A = 255, R = 0, G = 0, B = 0 });
     } else
     {
          // Mute
          capture.AudioDeviceController.Muted = true;
          muted = true;
          muteIcon.Foreground = new SolidColorBrush(new Windows.UI.Color() { A = 255, R = 255, G = 0, B = 0 });
     }
}

完成所有操作的主要代码是控制器 Muted 属性的更改,它会更改状态并静音或取消静音。上面的代码根据控制器的当前状态检查要执行的操作。

构建图库 —

如果相机应用程序没有办法查看您保存的内容并预览您录制的内容,那还有什么用呢?除了能够创建一个捕获和存储图像和视频的应用程序之外,我想分享代码以能够为这些图像创建图库并预览视频。无论如何,图库(因为我不是一个好的设计师)不包含非常优雅风格的 UI,它只是一个以列表形式预览内容的 ListView。

提示:如果您愿意,也可以以 GridView 的形式显示内容,您可以删除图像名称和其他内容,只需在 Windows Runtime API 的 Image 控件中预览图像即可。

我使用了一个新的页面控件创建了图库,它有一个单独的底部应用程序栏和其他细节,因此我们可以将相机控件和库控件彼此分开。主要思想是,在库中,用户应该能够获得他们捕获的图像列表、他们录制的视频列表,并且能够预览它们。我们只需要一个 ListView 控件,或者一个 GridView,具体取决于我们想如何设计一切。然后,我们可以将媒体文件列表绑定到这些列表并显示给用户。

在本节中,我将教您如何做到这一点,以便在您自己的应用程序中,您可以修改源代码并为您的用户创建单独的主题和样式,或者为他们提供两种样式(列表样式图库或网格样式图库)。Windows 10 应用程序开发真的非常简单!:-)

构建页面

如果您曾经开发过动态应用程序,您应该已经熟悉数据绑定的过程。这是一种将控件、视觉元素绑定到后端数据的方法。数据按原样填充控件,并尽可能多地填充。数据绑定允许开发人员为应用程序的布局创建单个模板,然后数据填充模板并提供给用户。我建议您仔细阅读 MSDN 上的什么是数据绑定,然后再继续。

在下面的应用程序代码中,我使用了相同的技术,我创建了一个控件模板,然后我将传递一个对象列表(请记住,我们也可以传递单个元素列表或空列表,XAML 会处理其余的事情),它们将用于根据我们的数据创建应用程序的 UI。这样,无论捕获了多少图像或视频,我们都可以将它们显示在应用程序的 UI 中,而且好消息是我们只需要提供模板。XAML 会自行填写详细信息并负责渲染。

您必须首先了解,渲染图像和渲染视频是不同的,并且使用不同的控件来渲染图像。在下面的部分,我为每种类型创建了两个单独的 ListView,其中一个用于渲染图像,另一个用于渲染视频。因此,应用程序中使用了两个模板来构建图库。在渲染图像的列表中,我使用了 Image 控件。它允许我们将 Source 属性添加到文件系统中的某些图像资源。另一方面,对于渲染视频的列表,我使用了 MediaElement 在屏幕上渲染视频。同样,它也为我们提供了修改 Source 属性的功能,因此我们可以将其附加到文件系统中的视频资源。

<TextBlock FontSize="20" TextWrapping="WrapWholeWords" Margin="30, 0, 0, 0">This is the area for your application's gallery, images or videos recorded can be viewed in this area.</TextBlock>
<ListView Name="photos" Visibility="Visible" Margin="0, 100, 0, 0">
    <ListView.ItemTemplate>
          <DataTemplate>
               <Grid Height="100" Margin="10, 2, 10, 2">
                    <Grid.ColumnDefinitions>
                         <ColumnDefinition />
                         <ColumnDefinition />
                    </Grid.ColumnDefinitions>
                    <Image Grid.Column="0" Source="{Binding Path=Path}" />
                    <TextBlock Grid.Column="1" Margin="10, 0, 0, 0" Text="{Binding Path=Name}" />
               </Grid>
          </DataTemplate>
     </ListView.ItemTemplate>
</ListView>
<ListView Name="videos" Visibility="Collapsed" Margin="0, 100, 0, 0">
    <ListView.ItemTemplate>
          <DataTemplate>
               <Grid Height="100" Margin="10, 2, 10, 2">
                    <Grid.ColumnDefinitions>
                         <ColumnDefinition />
                         <ColumnDefinition />
                     </Grid.ColumnDefinitions>
                     <MediaElement Grid.Column="0" Width="100" Source="{Binding Path}" Volume="0" />
                     <TextBlock Grid.Column="1" Text="{Binding Name}" Margin="10, 0, 0, 0"  />
               </Grid>
           </DataTemplate>
     </ListView.ItemTemplate>
</ListView>

上面的 XAML 代码只是我之前提到的模板。它将被我们提供的数据填充。ItemTemplate 允许我们为添加到其中的项目创建模板。它响应 ItemsSource(或 Items;通常不推荐使用)属性,并使用其属性或特性来填充数据。

注意属性,Source="{Binding Path=Path}"Text="{Binding Path=Name}"。它们是我正在使用的绑定属性,Path 或 Name 属性将用于这些占位符,UI 将随之构建。它们只是持有对集合中该项数据的引用的变量。

换个说法:为什么现在要在 XAML 中设置 Source

在本文的上面,我说过我们不应该考虑在 XAML 中将 Source 属性添加到 MediaElement,而现在我却直接在 XAML 中添加 Source 属性,真是羞耻之举,不是吗?

嗯,其实不是。看看上面。Source 属性绑定到 Binding 属性,而不是资源。这意味着,虽然数据很可能为 null,但如果成功绑定,Source 将不会为 null,应用程序将成功构建。看看,XAML 仍然会抱怨,但在模板模式下没关系!请继续。

但是一旦应用程序加载,问题就不再存在了。看看下面的图库列表视图快照!

以编程方式绑定数据

正如我所说,我们需要将对象绑定到此模板,当我们处理不同类型的数据时,我们将如何真正捕获对象?关键是我们提取通用部分并将其用作自定义对象的成员,然后传递该数据。简单地说,我们创建一个类来保存我们感兴趣的属性,

public class Data
{
     public string Name { get; set; }
     public Uri Path { get; set; }
}

完成,现在我们可以获得所需的值(此外,您可以删除 Name 字段,只使用 Path,然后稍后获取 Name 和其他内容的价值!但这属于高级主题),现在我们可以创建我们需要的对象列表。

但是为了确保 XAML 不会因为“对象引用未设置为对象实例”而抛出异常,我们需要确保应用了 Source 属性,我们将处理 OnNavigatedTo(e) 事件并进行所有设置。我建议您也使用此事件,因为它在应用程序加载页面并开始在屏幕上渲染控件时触发。要处理它,我们重写其默认行为并执行我们期望它做的事情,看看,

protected override void OnNavigatedTo(NavigationEventArgs e)
{
     getResources();

     // Set the resources
     photos.ItemsSource = photosList;
     videos.ItemsSource = videosList;
}

完成后,请查看我应用程序源代码中的 getResources 函数。

private void getResources()
{
     System.Threading.Tasks.Task.Run(async () =>
     {
         var photos = await (await ApplicationData.Current.LocalFolder.GetFolderAsync("Photos")).GetFilesAsync();
         var videos = await (await ApplicationData.Current.LocalFolder.GetFolderAsync("Videos")).GetFilesAsync();
                
         // Filter files
         foreach (var photo in photos)
         {
              photosList.Add(
                  new Data { Name = photo.Name, Path = new Uri(photo.Path) }
              );
         }

         foreach (var video in videos)
         {
              videosList.Add(
                  new Data { Name = video.Name, Path = new Uri(video.Path) }
              );
         }
    }).Wait();
}

我使用 Task 来执行此函数,因为如果我不使用,将出现两种情况

  1. await 关键字将从我们来的地方继续前一个函数,并将一个 null 列表应用于 photosvideos 控件。这并非我们想要的,但这正是 await 所做的。
  2. 如果我们忽略 await 关键字,那么我们将剩下 IAsyncResult 对象,使用它们又是一件头疼的事。

考虑到以上几点,最好使用 await 关键字并让应用程序函数异步运行,而不是等待任务完成。这使我们能够保持同步调用,以便应用的列表实际表示文件系统中的图像和视频,此外,我们不必处理其他异步函数结果的低级接口等等。

一旦上面的代码编译(按 CTRL + SHIFT + B 进行测试),它将提供您从文件系统中所需的数据。此外,由于我们在用户导航到此页面时加载资源,因此我们还获得了一个附加功能,即如果您捕获新的照片或视频,它们也会显示在图库中;我称此图库为 Library

提示:使用 GridView 而不是 ListView

如果您有兴趣使用 GridView 而不是 ListView,您可以更改 XAML 页面的内容,并用 GridView 控件替换 ListView 控件。示例如下,

<GridView Name="photosGrid" Visibility="Visible" Margin="0, 100, 0, 0">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid Height="100" Margin="10, 2, 10, 2">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <Image Grid.Column="0" Source="{Binding Path=Path}" />
                <TextBlock Grid.Column="1" Margin="10, 0, 0, 0" Text="{Binding Path=Name}" />
            </Grid>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

这将以网格而不是垂直堆叠每个图像的方式渲染图像。

通常,在创建图像库时,您会使用网格而不是列表。GridView 的设计方式是水平堆叠对象,而不是像 ListView 那样垂直堆叠。

应用程序示例预览

应用程序的相机页面如下所示,请注意应用程序的底部栏,它包含相机功能控件以及一个用于图库的新控件。

图库包含捕获的媒体。

图像库

图像库包含一个按钮,可以触发图库 UI 的更改,以显示视频或照片。

<AppBarToggleButton Icon="Video" Click="showVideos_Click" HorizontalAlignment="Right" Name="showVideos" Content="Show Videos" />

注意红色的矩形,只是想指出后退按钮仍然存在。 :sigh

如果用户单击该按钮,该按钮将被选中,并可用于更改应用程序的 UI。

视频库

它显示应用程序数据文件夹中的视频。

很好,应用程序的按钮和后退按钮现在清晰可见。:-) 您可以添加功能以在带有其他媒体传输控件的单独视频中显示视频,这些控件允许用户更改媒体状态,例如暂停或播放媒体。

兴趣点

Windows Runtime 提供您构建使用设备资源(如网络摄像头、麦克风和其他类似媒体内容)的应用程序所需的库和工具。Windows 10 应用程序使用 Windows Runtime 作为编程框架。Windows.Media.Capture 命名空间为您提供了可以在设备上捕获照片和录制视频所需的库和对象。为了用户隐私和安全,Windows Runtime 为您提供了在访问这些资源之前必须拥有的权限。

本文及相关的示例提供了此类程序的が基本示例。该应用程序能够捕获图像,存储视频,还提供其他选项,例如在录制时静音或取消静音麦克风。该应用程序的源代码还提供了一个示例,用于构建用于表示存储的媒体(如图像和视频)的库。

下载本文提供的应用程序源代码,用于其他目的进行测试,并评论您在其中发现的内容,投票并分享!:-) 我很想听听您的反馈,以便做得更好!

接下来呢?

我将继续为相机应用程序添加更多功能,通过实际将其用作应用程序的消费者,我相信我还会发现一些错误,我将更新本文和源代码以提供解决方案。

此外,如果我有任何其他想法,我会告诉大家。:-)如果您有任何其他想法,请留下您的反馈

历史

跟踪更改

  1. 文章和源代码的第一个版本。
  2. 添加了相机的图库部分
© . All rights reserved.