在本快速入门中,你将了解如何创建显示相机预览的基本 WinUI 相机应用。 在 WinUI 应用中,使用Microsoft.UI.Xaml.Controls命名空间中的MediaPlayerElement控件来呈现相机预览,并使用 WinRT 类MediaCapture来访问设备的相机预览流。 MediaCapture 提供用于执行各种与相机相关的任务的 API,例如捕获照片和视频以及配置相机的设备驱动程序。 有关其他 MediaCapture 功能的详细信息,请参阅本节中的其他文章。
本演练中的代码改编自 GitHub 上的 MediaCapture WinUI 示例。
小窍门
有关本文的 UWP 版本,请参阅 UWP 文档中显示相机预览。
先决条件
- 设备必须启用开发人员模式。 有关详细信息,请参阅 开发人员的设置。
- Visual Studio 2022 或更高版本,配备WinUI 应用程序开发工作负载。
创建新的 WinUI 应用
在Visual Studio中,创建新的project。 在 创建新的 project 对话框中,将语言筛选器设置为“C#”,并将平台筛选器设置为“Windows”,然后选择“空白应用,打包(桌面中的 WinUI)”project模板。
创建 UI
此示例的简单 UI 包括一个用于显示相机预览的 MediaPlayerElement 控件、一个 ComboBox ,可用于从设备的相机中进行选择,以及用于初始化 MediaCapture 类的按钮、启动和停止相机预览以及重置示例。 我们还包含用于显示状态消息的 TextBlock 。
在project的 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 设置中拒绝应用access到相机。
注释
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();
}