共用方式為


在 WinUI 3 應用程式中顯示相機預覽

在本快速入門中,您將瞭解如何建立顯示相機預覽的基本 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 物件的 DeviceInformationCollectionDeviceInformation 物件會傳達資訊,例如相機的唯一標識碼和易記名稱。
  • 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 屬性公開。

若要尋找一個色彩視訊數據的畫面來源,例如相較於深度相機的畫面來源,我們會尋找具有 SourceKindColor 的畫面來源。 某些相機驅動程式會提供與記錄串流分開的專用預覽串流。 若要取得預覽視訊串流,我們會嘗試選取具有 MediaStreamTypeVideoPreview 的畫面來源。 如果找不到預覽串流,我們可以選取 VideoRecordMediaStreamType 來取得錄製視訊串流。 如果這兩個畫面來源都無法使用,則此擷取裝置無法用於影片預覽。

選取畫面來源之後,我們會建立新的 MediaPlayer 物件,由 UI 中的 MediaPlayerElement 轉譯。 我們將 MediaPlayerSource 屬性設定為我們從選取的 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();

}