显示相机预览

本文介绍了如何在通用 Windows 平台 (UWP) 应用的 XAML 页面内快速显示相机预览流。 创建可使用相机捕获照片和视频的应用需要你执行如下任务:处理设备和相机方向或者设置捕获文件的编码选项。 有关某些应用方案,可能只需显示来自相机的预览流,而无需担心其他注意事项。 本文将向你显示如何使用最少的代码执行相关操作。 请注意,在你使用它按照下列步骤完成操作时,应当始终正确关闭预览流。

有关编写可捕获照片或视频的相机应用的信息,请参阅使用 MediaCapture 捕获基本的照片、视频和音频

向应用清单中添加功能声明

为了让你的应用可以访问设备的相机,必须声明你的应用要使用 webcammicrophone 设备功能。

将功能添加到应用清单

  1. 在 Microsoft Visual Studio 的“解决方案资源管理器”中,通过双击“package.appxmanifest”项,打开应用程序清单的设计器。
  2. 选择“功能”选项卡。
  3. 选中“摄像头”框和“麦克风”框。

将 CaptureElement 添加到你的页面

使用 CaptureElement 以在你的 XAML 页面内显示预览流。

<CaptureElement Name="PreviewControl" Stretch="Uniform"/>

使用 MediaCapture 启动预览流

MediaCapture 对象为你的设备相机的应用界面。 此类是 Windows.Media.Capture 命名空间的成员。 除了默认项目模板包含的这些 API,本文的示例还使用来自 Windows.ApplicationModelSystem.Threading.Tasks 命名空间的 API。

添加 using 指令以将以下命名空间包含在你的页面的 .cs 文件中。

using Windows.UI.Core;
using Windows.UI.Xaml.Navigation;
using Windows.Media.Capture;
using Windows.ApplicationModel;
using System.Threading.Tasks;
using Windows.System.Display;
using Windows.Graphics.Display;

MediaCapture 对象和布尔值声明类成员变量,以跟踪相机当前是否在预览。

MediaCapture mediaCapture;
bool isPreviewing;

声明用于确保屏幕在运行预览时不会关闭的 DisplayRequest 类型的变量。

DisplayRequest displayRequest = new DisplayRequest();

创建帮助程序方法以启动相机预览,在本示例中被称为 StartPreviewAsync。 根据应用方案的不同,你可能想要通过在加载页面时调用的 OnNavigatedTo 事件处理程序调用它,或者等待并启用预览以响应 UI 事件。

创建 MediaCapture 类的一个新实例并调用 InitializeAsync 以初始化捕获设备。 例如,此方法在没有相机的设备上可能会失败,所以你应当从 try 块内调用它。 如果用户已在该设备的隐私设置中禁用相机访问,则在尝试初始化相机时会引发 UnauthorizedAccessException。 如果你忘记将合适的功能添加到你的应用清单,在开发期间也会看到此异常。

重要提示 对于某些设备系列,在授予应用访问设备相机的权限前,会向用户显示用户同意提示。 出于此原因,必须从主 UI 线程仅调用 MediaCapture.InitializeAsync。 尝试从其他线程初始化相机可能会导致初始化失败。

通过设置 Source 属性来将 MediaCapture 连接到 CaptureElement。 通过调用 StartPreviewAsync 启动预览。 如果其他应用拥有捕获设备的独占控制权,则此方法会引发 FileLoadException。 有关侦听独占控制权更改的信息,请参阅下一部分。

调用 RequestActive 确保在运行预览时设备不会进入睡眠状态。 最后,将 DisplayInformation.AutoRotationPreferences 属性设为 Landscape,防止 UI 和 CaptureElement 在用户更改设备方向时旋转。 有关处理设备方向更改的详细信息,请参阅使用 MediaCapture 处理设备方向

       private async Task StartPreviewAsync()
       {
           try
           {

               mediaCapture = new MediaCapture();
               await mediaCapture.InitializeAsync();

               displayRequest.RequestActive();
               DisplayInformation.AutoRotationPreferences = DisplayOrientations.Landscape;
           }
           catch (UnauthorizedAccessException)
           {
               // This will be thrown if the user denied access to the camera in privacy settings
               ShowMessageToUser("The app was denied access to the camera");
               return;
           }

           try
           {
               PreviewControl.Source = mediaCapture;
               await mediaCapture.StartPreviewAsync();
               isPreviewing = true;
           }
           catch (System.IO.FileLoadException)
           {
               mediaCapture.CaptureDeviceExclusiveControlStatusChanged += _mediaCapture_CaptureDeviceExclusiveControlStatusChanged;
           }

       }

处理独占控制权更改

如前面的部分所述,如果其他应用拥有捕获设备的独占控制权,则 StartPreviewAsync 会引发 FileLoadException。 从 Windows 10 版本 1703 开始,可以为每当设备的独占控制权状态更改时都会引发的 MediaCapture.CaptureDeviceExclusiveControlStatusChanged 事件注册处理程序。 在此事件的处理程序中,检查 MediaCaptureDeviceExclusiveControlStatusChangedEventArgs.Status 属性以查看当前具体状态。 如果新状态是 SharedReadOnlyAvailable,则你知道当前无法启动预览,并且可能要更新 UI 以向用户发出警报。 如果新状态是 ExclusiveControlAvailable,则你可以尝试再次启动相机预览。

private async void _mediaCapture_CaptureDeviceExclusiveControlStatusChanged(MediaCapture sender, MediaCaptureDeviceExclusiveControlStatusChangedEventArgs args)
{
    if (args.Status == MediaCaptureDeviceExclusiveControlStatus.SharedReadOnlyAvailable)
    {
        ShowMessageToUser("The camera preview can't be displayed because another app has exclusive access");
    }
    else if (args.Status == MediaCaptureDeviceExclusiveControlStatus.ExclusiveControlAvailable && !isPreviewing)
    {
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
        {
            await StartPreviewAsync();
        });
    }
}

关闭预览流

当你使用预览流完成操作时,应当始终关闭该流并适当地释关联资源,以确保该相机可用于设备上的其他应用。 关闭预览流所需的步骤如下:

  • 如果相机当前正在预览,请调用 StopPreviewAsync 以停止预览流。 如果在预览没有运行时调用 StopPreviewAsync,将引发异常。
  • CaptureElementSource 属性设置为 null。 使用 CoreDispatcher.RunAsync,确保此调用在 UI 线程上执行。
  • 调用 MediaCapture 对象的 Dispose 方法以发布该对象。 同样,使用 CoreDispatcher.RunAsync,确保此调用在 UI 线程上执行。
  • MediaCapture 成员变量设置为 null。
  • 调用 RequestRelease 以支持在屏幕处于非活动状态时关闭屏幕。
private async Task CleanupCameraAsync()
{
    if (mediaCapture != null)
    {
        if (isPreviewing)
        {
            await mediaCapture.StopPreviewAsync();
        }

        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            PreviewControl.Source = null;
            if (displayRequest != null)
            {
                displayRequest.RequestRelease();
            }

            mediaCapture.Dispose();
            mediaCapture = null;
        });
    }
    
}

当用户通过替代 OnNavigatedFrom 方法导航离开你的页面时,应当关闭预览流。

protected async override void OnNavigatedFrom(NavigationEventArgs e)
{
    await CleanupCameraAsync();
}

你还应当在应用暂停时正确关闭预览流。 若要执行此操作,请在你的页面的构造函数中注册 Application.Suspending 事件的处理程序。

public MainPage()
{
    this.InitializeComponent();

    Application.Current.Suspending += Application_Suspending;
}

Suspending 事件处理程序中,首先进行查看以通过比较 CurrentSourcePageType 属性的页面类型,确保应用程序的 Frame 正显示该页面。 如果当前未显示该页面,则应当已引发 OnNavigatedFrom 事件并关闭预览流。 如果当前显示该页面,从已传入到处理程序中的事件参数中获取 SuspendingDeferral 对象,以确保直到关闭预览流系统才暂停你的应用。 关闭流后,调用延迟的 Complete 方法以使系统继续暂停你的应用。

private async void Application_Suspending(object sender, SuspendingEventArgs e)
{
    // Handle global application events only if this page is active
    if (Frame.CurrentSourcePageType == typeof(MainPage))
    {
        var deferral = e.SuspendingOperation.GetDeferral();
        await CleanupCameraAsync();
        deferral.Complete();
    }
}