在本快速入門中,您將瞭解如何建立顯示相機預覽的基本 WinUI 3 相機應用程式。 在 WinUI 3 應用程式中,您可以使用 Microsoft.UI.Xaml.Controls 命名空間中的 MediaPlayerElement 控件來轉譯相機預覽,以及 WinRT 類別 MediaCapture 來存取裝置的相機預覽串流。 MediaCapture 提供 API 來執行各種不同的相機相關工作,例如擷取相片和視訊,以及設定相機的裝置驅動程式。 如需其他 MediaCapture 功能的詳細資訊,請參閱本節中的其他文章。
本逐步解說中的程序代碼會從 github 上的 MediaCapture WinUI 3 範例進行調整。
小提示
如需本文的 UWP 版本,請參閱 UWP 檔中 的顯示相機預覽 。
先決條件
- 您的裝置必須啟用開發人員模式。 如需詳細資訊,請參閱 開發人員的設定。
- Visual Studio 2022 或更新版本搭配 WinUI 應用程式開發 工作負載。
建立新的 WinUI 3 應用程式
在 Visual Studio 中,建立新的專案。 在 [ 建立新專案 ] 對話框中,將語言篩選設定為 “C#”,並將平臺篩選設定為 “Windows”,然後選取 [空白應用程式,封裝](傳統型 WinUI 3)專案範本。
建立 UI
此範例的簡單 UI 包含用來顯示相機預覽的 MediaPlayerElement 控件、可讓您從裝置相機選取的 ComboBox ,以及初始化 MediaCapture 類別、啟動和停止相機預覽,以及重設範例的按鈕。 我們也包含用來顯示狀態消息的 TextBlock 。
在專案的 MainWindow.xml 檔案中,以下列 XAML 取代預設 StackPanel 控件。
<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 類別定義。 首先,新增一些類別變數,以在視窗的存留期內保存。 這些變數包括:
- 將為每個可用相機儲存 DeviceInformation 物件的 DeviceInformationCollection。 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 取得裝置標識符。
- 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 屬性公開。
若要尋找一個色彩視訊數據的畫面來源,例如相較於深度相機的畫面來源,我們會尋找具有 SourceKind 為 Color 的畫面來源。 某些相機驅動程式會提供與記錄串流分開的專用預覽串流。 若要取得預覽視訊串流,我們會嘗試選取具有 MediaStreamType 之 VideoPreview 的畫面來源。 如果找不到預覽串流,我們可以選取 VideoRecord 的 MediaStreamType 來取得錄製視訊串流。 如果這兩個畫面來源都無法使用,則此擷取裝置無法用於影片預覽。
選取畫面來源之後,我們會建立新的 MediaPlayer 物件,由 UI 中的 MediaPlayerElement 轉譯。 我們將 MediaPlayer 的 Source 屬性設定為我們從選取的 MediaFrameSource 建立的新 MediaSource 物件。
在 MediaPlayer 物件上呼叫 Play,開始轉譯視訊串流。
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();
}