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

在這個快速入門中,你將學習如何建立一個基本的 WinUI 相機應用程式,用來顯示相機預覽。 在 WinUI 應用程式中,你可以使用 Microsoft.UI.Xaml.Controls 命名空間中的 MediaPlayerElement 控制項來渲染相機預覽,並用 WinRT 類別 MediaCapture 來access裝置的相機預覽串流。 MediaCapture 提供 API 來執行各種不同的相機相關工作,例如擷取相片和視訊,以及設定相機的裝置驅動程式。 如需其他 MediaCapture 功能的詳細資訊,請參閱本節中的其他文章。

本攻略中的程式碼改編自 github 上的 MediaCapture WinUI 範例。

WinUI 3 圖庫圖示 WinUI 3 圖庫應用程式包含互動式的 WinUI 控制項與功能範例。 你可以從 Microsoft Store 下載應用程式,或瀏覽 GitHub 的原始碼。

先決條件

  • 您的裝置必須啟用開發人員模式。 如需詳細資訊,請參閱 開發人員的設定。
  • Visual Studio 2026 或更新版本,搭配 WinUI 應用程式開發 工作負載。

建立一個新的 WinUI 應用程式

在 Visual Studio 裡,建立一個新 project。 在 「建立新專案 」對話框中,將語言篩選器設為「C#」,平台篩選器設為「Windows」,然後選擇「WinUI Blank App (Packaged)」專案範本。

建立 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類別定義

本文其餘程式碼會加入你project 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();

}

處理攝影機選擇的變更

當使用者從 ComboBox 選擇不同的攝影機時, SelectionChanged 處理器會丟棄現有的 MediaCapture 和 MediaPlayer 物件,因為它們是為特定攝影機裝置初始化的。 處理器會重置使用者介面狀態,讓使用者可以點擊 初始化 MediaCapture 來設定新選取的攝影機。

private void cbDeviceList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (cbDeviceList.SelectedIndex < 0)
    {
        return;
    }

    // If previewing, stop the preview since the selected camera has changed.
    if (m_isPreviewing)
    {
        m_mediaPlayer.Pause();
        m_isPreviewing = false;
    }

    // Dispose of existing MediaCapture and MediaPlayer, since they are
    // initialized for a specific camera device.
    if (m_mediaPlayer != null)
    {
        m_mediaPlayer.Dispose();
        m_mediaPlayer = null;
    }

    if (m_mediaCapture != null)
    {
        m_mediaCapture.Dispose();
        m_mediaCapture = null;
    }

    m_frameSource = null;

    bStartMediaCapture.IsEnabled = true;
    bStartPreview.IsEnabled = false;
    bStopPreview.IsEnabled = false;

    tbStatus.Text = "Camera selection changed. Click 'Initialize MediaCapture' to continue.";
}

初始化 MediaCapture 物件

呼叫 InitializeAsync 來初始化 MediaCapture 物件,並傳入包含所要求初始化參數的 MediaCaptureInitializationSettings 物件。 有許多可啟用不同案例的選擇性初始化參數。 如需完整清單,請參閱 API 參考頁面。 在此簡單範例中,我們會指定一些基本設定,包括:

  • VideoDeviceId 屬性會指定 MediaCapture 要附加之相機的唯一標識符。 我們會使用 ComboBox 的選取索引,從 DeviceInformationCollection 取得裝置標識符。
  • SharingMode 屬性指定應用程式是向攝影機請求共享的唯讀存取,讓你可以觀看並擷取影片串流,或者是要求對攝影機的獨家控制,以便更改攝影機設定。 多個應用程式可以同時從相機讀取,但一次只有一個應用程式可以擁有獨佔控制權。
  • StreamingCaptureMode 屬性會指定我們想要擷取視訊、音訊或音訊和視訊。
  • MediaCaptureMemoryPreference 可讓我們要求特別針對視訊畫面使用 CPU 記憶體。 [ 自動 ] 值可讓系統在可用時使用 GPU 記憶體。

在初始化 MediaCapture物件之前,我們會呼叫 AppCapability.CheckAccess 方法來判斷使用者是否在 Windows 設定中拒絕了我們的應用程式access給相機。

備註

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();

}