在本快速入门中,你将了解如何创建显示相机预览的基本 WinUI 3 相机应用。 在 WinUI 3 应用中,使用 Microsoft.UI.Xaml.Controls 命名空间中的 MediaPlayerElement 控件来呈现相机预览和 WinRT 类 MediaCapture 以访问设备的相机预览流。 MediaCapture 提供用于执行各种与相机相关的任务的 API,例如捕获照片和视频以及配置相机的设备驱动程序。 有关其他 MediaCapture 功能的详细信息,请参阅本节中的其他文章。
本演练中的代码改编自 github 上的 MediaCapture WinUI 3 示例。
小窍门
有关本文的 UWP 版本,请参阅 UWP 文档中 的“显示相机预览 ”。
先决条件
- 设备必须启用开发人员模式。 有关详细信息,请参阅启用用于开发的设备。
- 使用 WinUI 应用程序开发工作负载的 Visual Studio 2022 或更高版本。
创建新的 WinUI 3 应用
在 Visual Studio 中,创建新项目。 在“ 创建新项目 ”对话框中,将语言筛选器设置为“C#”,并将平台筛选器设置为“Windows”,然后选择“空白应用,打包(桌面中的 WinUI 3)”项目模板。
创建 UI
此示例的简单 UI 包括一个用于显示相机预览的 MediaPlayerElement 控件、一个 ComboBox ,可用于从设备的相机中进行选择,以及用于初始化 MediaCapture 类的按钮、启动和停止相机预览以及重置示例。 我们还包含用于显示状态消息的 TextBlock 。
在项目的 MainWindow.xml 文件中,将默认 StackPanel 控件替换为以下 XAML。
<Grid ColumnDefinitions="4*,*" ColumnSpacing="4">
<MediaPlayerElement x:Name="mpePreview" Grid.Row="0" Grid.Column="0" AreTransportControlsEnabled="False" ManipulationMode="None"/>
<StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Top">
<TextBlock Text="Status:" Margin="0,0,10,0"/>
<TextBlock x:Name="tbStatus" Text=""/>
<TextBlock Text="Preview Source:" Margin="0,0,10,0"/>
<ComboBox x:Name="cbDeviceList" HorizontalAlignment="Stretch" SelectionChanged="cbDeviceList_SelectionChanged"></ComboBox>
<Button x:Name="bStartMediaCapture" Content="Initialize MediaCapture" IsEnabled="False" Click="bStartMediaCapture_Click"/>
<Button x:Name="bStartPreview" Content="Start preview" IsEnabled="False" Click="bStartPreview_Click"/>
<Button x:Name="bStopPreview" Content="Stop preview" IsEnabled="False" Click="bStopPreview_Click"/>
<Button x:Name="bReset" Content="Reset" Click="bReset_Click" />
</StackPanel>
</Grid>
更新 MainWindow 类定义
本文中的其余代码将添加到项目的 MainWindow.xaml.cs 文件中的 MainWindow 类定义中。 首先,添加几个类变量,这些变量将在窗口的整个生存期内保留。 这些变量包括:
- 一个 DeviceInformationCollection ,用于存储每个可用相机的 DeviceInformation 对象。 DeviceInformation 对象传达信息,例如唯一标识符和相机的友好名称。
- 一个 MediaCapture 对象,该对象处理与所选相机驱动程序的交互,并允许检索相机的视频流。
- 一个 MediaFrameSource 对象,该对象表示媒体帧的源,例如视频流。
- 一个布尔值,用于跟踪相机预览运行时。 预览运行时无法更改某些相机设置,因此最好跟踪相机预览的状态。
private DeviceInformationCollection m_deviceList;
private MediaCapture m_mediaCapture;
private MediaFrameSource m_frameSource;
private MediaPlayer m_mediaPlayer;
private bool m_isPreviewing;
填充可用相机的列表
接下来,我们将创建一个帮助程序方法来检测当前设备上存在的相机,并使用相机名称填充 UI 中的 ComboBox ,允许用户选择相机进行预览。 DeviceInformation.FindAllAsync 允许查询多种不同类型的设备。 我们使用 MediaDevice.GetVideoCaptureSelector 检索标识符,该标识符指定我们只想检索视频捕获设备。
private async void PopulateCameraList()
{
cbDeviceList.Items.Clear();
m_deviceList = await DeviceInformation.FindAllAsync(MediaDevice.GetVideoCaptureSelector());
if(m_deviceList.Count == 0)
{
tbStatus.Text = "No video capture devices found.";
return;
}
foreach (var device in m_deviceList)
{
cbDeviceList.Items.Add(device.Name);
bStartMediaCapture.IsEnabled = true;
}
}
向 MainWindow 类构造函数添加对此帮助程序方法的调用,以便在窗口加载时填充 ComboBox 。
public MainWindow()
{
this.InitializeComponent();
PopulateCameraList();
}
初始化 MediaCapture 对象
通过调用 InitializeAsync 初始化 MediaCapture 对象,传入包含所请求初始化参数的 MediaCaptureInitializationSettings 对象。 有许多可选的初始化参数可以启用不同的方案。 有关完整列表,请参阅 API 参考页。 在此简单示例中,我们指定了一些基本设置,包括:
- VideoDeviceId 属性指定 MediaCapture 将附加到的相机的唯一标识符。 我们使用 ComboBox 的选定索引从 DeviceInformationCollection 获取设备 ID。
- SharingMode 属性指定应用是请求对相机的共享、只读访问权限,这允许你从视频流查看和捕获,还是允许你更改相机配置的专用控制。 多个应用可以同时从相机读取,但一次只能有一个应用具有独占控制权。
- StreamingCaptureMode 属性指定是要捕获视频、音频还是音频和视频。
- MediaCaptureMemoryPreference 允许我们请求专门将 CPU 内存用于视频帧。 如果系统可用,“ 自动” 值允许系统使用 GPU 内存。
在初始化 MediaCapture 对象之前,我们调用 AppCapability.CheckAccess 方法来确定用户在 Windows 设置中是否拒绝了应用访问相机的权限。
注释
Windows 允许用户在“ 隐私和安全 -> 相机”下授予或拒绝访问 Windows 设置应用中设备的相机。 初始化捕获设备时,应用应检查他们是否有权访问相机并处理用户拒绝访问的情况。 有关详细信息,请参阅 “处理 Windows 相机隐私设置”。
InitializeAsync 调用是从 try 块内部进行的,因此,如果初始化失败,我们可以恢复。 应用应正常处理初始化失败。 在此简单示例中,我们只会在失败时显示一条错误消息。
private async void bStartMediaCapture_Click(object sender, RoutedEventArgs e)
{
if (m_mediaCapture != null)
{
tbStatus.Text = "MediaCapture already initialized.";
return;
}
// Supported in Windows Build 18362 and later
if(AppCapability.Create("Webcam").CheckAccess() != AppCapabilityAccessStatus.Allowed)
{
tbStatus.Text = "Camera access denied. Launching settings.";
bool result = await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings:privacy-webcam"));
if (AppCapability.Create("Webcam").CheckAccess() != AppCapabilityAccessStatus.Allowed)
{
tbStatus.Text = "Camera access denied in privacy settings.";
return;
}
}
try
{
m_mediaCapture = new MediaCapture();
var mediaCaptureInitializationSettings = new MediaCaptureInitializationSettings()
{
VideoDeviceId = m_deviceList[cbDeviceList.SelectedIndex].Id,
SharingMode = MediaCaptureSharingMode.ExclusiveControl,
StreamingCaptureMode = StreamingCaptureMode.Video,
MemoryPreference = MediaCaptureMemoryPreference.Auto
};
await m_mediaCapture.InitializeAsync(mediaCaptureInitializationSettings);
tbStatus.Text = "MediaCapture initialized successfully.";
bStartPreview.IsEnabled = true;
}
catch (Exception ex)
{
tbStatus.Text = "Initialize media capture failed: " + ex.Message;
}
}
初始化相机预览版
当用户单击“开始预览”按钮时,我们将尝试从初始化 MediaCapture 对象的相机设备为视频流创建 MediaFrameSource。 MediaCapture.FrameSources 属性公开可用的帧源。
若要查找颜色视频数据的帧源,而非深度相机,我们需查找具有 Color 的 SourceKind 帧源。 某些相机驱动程序提供独立于记录流的专用预览流。 若要获取预览视频流,我们尝试选择具有 MediaStreamType of VideoPreview 的帧源。 如果未找到预览流,可以通过选择 VideoRecord 的 MediaStreamType 来获取录制视频流。 如果这两个帧源都不可用,则无法将此捕获设备用于视频预览。
选择帧源后,我们将创建一个新的 MediaPlayer 对象,该对象将由 MediaPlayerElement 在 UI 中呈现。 我们将 MediaPlayer 的 Source 属性设置为从所选 MediaFrameSource 创建的新 MediaSource 对象。
在 MediaPlayer 对象上调用“播放”以开始呈现视频流。
private void bStartPreview_Click(object sender, RoutedEventArgs e)
{
m_frameSource = null;
// Find preview source.
// The preferred preview stream from a camera is defined by MediaStreamType.VideoPreview on the RGB camera (SourceKind == color).
var previewSource = m_mediaCapture.FrameSources.FirstOrDefault(source => source.Value.Info.MediaStreamType == MediaStreamType.VideoPreview
&& source.Value.Info.SourceKind == MediaFrameSourceKind.Color).Value;
if (previewSource != null)
{
m_frameSource = previewSource;
}
else
{
var recordSource = m_mediaCapture.FrameSources.FirstOrDefault(source => source.Value.Info.MediaStreamType == MediaStreamType.VideoRecord
&& source.Value.Info.SourceKind == MediaFrameSourceKind.Color).Value;
if (recordSource != null)
{
m_frameSource = recordSource;
}
}
if (m_frameSource == null)
{
tbStatus.Text = "No video preview or record stream found.";
return;
}
// Create MediaPlayer with the preview source
m_mediaPlayer = new MediaPlayer();
m_mediaPlayer.RealTimePlayback = true;
m_mediaPlayer.AutoPlay = false;
m_mediaPlayer.Source = MediaSource.CreateFromMediaFrameSource(m_frameSource);
m_mediaPlayer.MediaFailed += MediaPlayer_MediaFailed; ;
// Set the mediaPlayer on the MediaPlayerElement
mpePreview.SetMediaPlayer(m_mediaPlayer);
// Start preview
m_mediaPlayer.Play();
tbStatus.Text = "Start preview succeeded!";
m_isPreviewing = true;
bStartPreview.IsEnabled = false;
bStopPreview.IsEnabled = true;
}
为 MediaFailed 事件实现处理程序,以便处理呈现预览的错误。
private void MediaPlayer_MediaFailed(MediaPlayer sender, MediaPlayerFailedEventArgs args)
{
tbStatus.Text = "MediaPlayer error: " + args.ErrorMessage;
}
停止相机预览
若要停止相机预览,请对 MediaPlayer 对象调用 Pause。
private void bStopPreview_Click(object sender, RoutedEventArgs e)
{
// Stop preview
m_mediaPlayer.Pause();
m_isPreviewing = false;
bStartPreview.IsEnabled = true;
bStopPreview.IsEnabled = false;
}
重置应用
若要更轻松地测试示例应用,请添加一个方法来重置应用的状态。 不再需要相机时,相机应用应始终释放相机和相关资源。
private void bReset_Click(object sender, RoutedEventArgs e)
{
if (m_mediaCapture != null)
{
m_mediaCapture.Dispose();
m_mediaCapture = null;
}
if(m_mediaPlayer != null)
{
m_mediaPlayer.Dispose();
m_mediaPlayer = null;
}
m_frameSource = null;
bStartMediaCapture.IsEnabled = false;
bStartPreview.IsEnabled = false;
bStopPreview.IsEnabled = false;
PopulateCameraList();
}