顯示相機預覽

本文說明如何在通用 Windows 平台 (UWP) 應用程式中的 XAML 頁面內,快速顯示相機預覽串流。 建立使用相機攝錄相片和影片的應用程式,需要您為攝錄檔案執行裝置操作與相機轉向,或是設定編碼選項等工作。 在某些應用程式案例中,您可能只想從相機顯示預覽串流,而不打算煩惱這些其他考量。 本文說明如何使用最少的程式碼來執行此動作。 請注意,當您完成預覽串流時,應該一律遵循下列步驟來正確關閉預覽串流。

如需撰寫攝錄相片或影片相機應用程式的資訊,請參閱使用 MediaCapture 攝錄相片、影片與音訊的基本知識

將功能宣告加到應用程式資訊清單

為了讓您的應用程式存取裝置的相機,您必須宣告應用程式使用網路攝影機麥克風裝置功能。

將功能加到應用程式資訊清單

  1. 在 Microsoft Visual Studio 的 [方案總管] 中,按兩下 package.appxmanifest 項目,開啟應用程式資訊清單的設計工具。
  2. 選取 [功能] 索引標籤。
  3. 核取 [網路攝影機] 方塊和 [麥克風] 方塊。

將 CaptureElement 新增至您的頁面

使用 CaptureElement 在 XAML 頁面中顯示預覽串流。

<CaptureElement Name="PreviewControl" Stretch="Uniform"/>

使用 MediaCapture 啟動預覽串流

MediaCapture 物件是裝置相機的應用程式介面。 這個類別是 Windows.Media.Capture 命名空間的成員。 本文中的範例也會使用來自 Windows.ApplicationModelSystem.Threading.Tasks 命名空間的 API,以及預設專案範本所包含的 API。

新增 using 指示詞,以在頁面的 .cs 檔案中包含下列命名空間。

using Windows.UI.Core;
using Windows.UI.Xaml.Navigation;
using Windows.Media.Capture;
using Windows.ApplicationModel;
using System.Threading.Tasks;
using Windows.System.Display;
using Windows.Graphics.Display;

宣告 MediaCapture 物件的類別成員變數,以及用來追蹤相機目前是否正在預覽的布林值。

MediaCapture mediaCapture;
bool isPreviewing;

宣告 DisplayRequest 類型的變數,該變數將用來確保預覽執行時不會關閉顯示器。

DisplayRequest displayRequest = new DisplayRequest();

建立協助程式方法來啟動相機預覽,在此範例中稱為 StartPreviewAsync。 視您應用程式的案例而定,您可能會想要從載入頁面或等候頁面時呼叫的 OnNavigatedTo 事件處理常式來呼叫此方法,並啟動預覽以回應 UI 事件。

建立 MediaCapture 類別的新執行個體,並呼叫 InitializeAsync 來初始化攝錄裝置。 例如,在沒有相機的裝置上,這個方法可能會失敗,因此您應該從 try 區塊內呼叫它。 如果使用者在裝置隱私權設定中停用相機存取,而您嘗試初始化相機,則系統會擲回 UnauthorizedAccessException。 如果您已忽略將適當的功能加入應用程式資訊清單,則也會在開發期間看到此例外狀況。

重要事項 在一些裝置系列上,會先向使用者顯示使用者同意提示,然後才會將裝置相機的存取權授予您的應用程式。 基於這個理由,您只能從主要 UI 執行緒呼叫 MediaCapture.InitializeAsync。 嘗試從另一個執行緒初始化相機,可能會導致初始化失敗。

MediaCapture 連線到 CaptureElement,方法是設定 Source 屬性。 呼叫 StartPreviewAsync 來啟動預覽。 如果另一 個應用程式具有攝錄裝置的獨佔控制權,這個方法將會擲回 FileLoadException。 如需接聽獨佔控制權變更的資訊,請參閱下一節。

呼叫 RequestActive 以確定裝置在執行預覽時不會進入睡眠狀態。 最後,將 DisplayInformation.AutoRotationPreferences 屬性設定為 Landscape,以防止使用者變更裝置方向時,UI 和 CaptureElement 跟著旋轉。 如需處理裝置方向變更的詳細資訊,請參閱使用 MediaCapture 處理裝置方向

       private async Task StartPreviewAsync()
       {
           try
           {

               mediaCapture = new MediaCapture();
               await mediaCapture.InitializeAsync();

               displayRequest.RequestActive();
               DisplayInformation.AutoRotationPreferences = DisplayOrientations.Landscape;
           }
           catch (UnauthorizedAccessException)
           {
               // This will be thrown if the user denied access to the camera in privacy settings
               ShowMessageToUser("The app was denied access to the camera");
               return;
           }

           try
           {
               PreviewControl.Source = mediaCapture;
               await mediaCapture.StartPreviewAsync();
               isPreviewing = true;
           }
           catch (System.IO.FileLoadException)
           {
               mediaCapture.CaptureDeviceExclusiveControlStatusChanged += _mediaCapture_CaptureDeviceExclusiveControlStatusChanged;
           }

       }

處理獨佔控制權中的變更

如上一節所述,如果另一個應用程式擁有攝錄裝置的獨佔控制權,StartPreviewAsync 將會擲回 FileLoadException。 從 Windows 10 版本 1703 開始,您可以註冊 MediaCapture.CaptureDeviceExclusiveControlStatusChanged 事件的處理常式,每當裝置的獨佔控制權狀態變更時,就會引發此事件。 在此事件的處理常式中,檢查 MediaCaptureDeviceExclusiveControlStatusChangedEventArgs.Status 屬性以查看目前的狀態。 如果新的狀態為 SharedReadOnlyAvailable,則您知道您目前無法啟動預覽,而您可能想要更新 UI 以對使用者發出警示。 如果新狀態為 ExclusiveControlAvailable,您可以再次嘗試啟動相機預覽。

private async void _mediaCapture_CaptureDeviceExclusiveControlStatusChanged(MediaCapture sender, MediaCaptureDeviceExclusiveControlStatusChangedEventArgs args)
{
    if (args.Status == MediaCaptureDeviceExclusiveControlStatus.SharedReadOnlyAvailable)
    {
        ShowMessageToUser("The camera preview can't be displayed because another app has exclusive access");
    }
    else if (args.Status == MediaCaptureDeviceExclusiveControlStatus.ExclusiveControlAvailable && !isPreviewing)
    {
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
        {
            await StartPreviewAsync();
        });
    }
}

關閉預覽串流

當您使用預覽串流完畢時,應該一律關閉串流並適當處置相關聯的資源,以確保相機可供裝置上的其他應用程式使用。 關閉預覽串流所需的步驟如下:

  • 如果相機目前正在預覽,請呼叫 StopPreviewAsync 以停止預覽串流。 如果您在預覽未執行時呼叫 StopPreviewAsync,將會擲回例外狀況。
  • CaptureElementSource 屬性設定為 null。 使用 CoreDispatcher.RunAsync 確定此呼叫是在 UI 執行緒上執行。
  • 呼叫 MediaCapture 物件的 Dispose 方法以釋放物件。 再一次,使用 CoreDispatcher.RunAsync 確定此呼叫是在 UI 執行緒上執行。
  • MediaCapture 成員變數設定為 null。
  • 呼叫 RequestRelease 以允許在非使用中時關閉畫面。
private async Task CleanupCameraAsync()
{
    if (mediaCapture != null)
    {
        if (isPreviewing)
        {
            await mediaCapture.StopPreviewAsync();
        }

        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            PreviewControl.Source = null;
            if (displayRequest != null)
            {
                displayRequest.RequestRelease();
            }

            mediaCapture.Dispose();
            mediaCapture = null;
        });
    }
    
}

當使用者覆寫 OnNavigatedFrom 方法離開頁面時,您應該關閉預覽串流。

protected async override void OnNavigatedFrom(NavigationEventArgs e)
{
    await CleanupCameraAsync();
}

當您的應用程式暫停時,您也應該妥善地關閉預覽串流。 若要這樣做,請在頁面建構函式中註冊 Application.Suspending 事件的處理常式。

public MainPage()
{
    this.InitializeComponent();

    Application.Current.Suspending += Application_Suspending;
}

Suspending 事件處理常式中,先檢查以確定頁面正藉由比較頁面類型與 CurrentSourcePageType 屬性,來顯示應用程式的 Frame。 如果頁面目前未顯示,則應該已引發 OnNavigatedFrom 事件,且已關閉預覽串流。 如果目前正在顯示頁面,請從傳遞至處理常式的事件引數取得 SuspendingDeferral 物件,以確保系統在預覽串流關閉之前不會暫停您的應用程式。 串流關閉之後,請呼叫延遲 deferral 的 Complete 方法,讓系統繼續暫停您的應用程式。

private async void Application_Suspending(object sender, SuspendingEventArgs e)
{
    // Handle global application events only if this page is active
    if (Frame.CurrentSourcePageType == typeof(MainPage))
    {
        var deferral = e.SuspendingOperation.GetDeferral();
        await CleanupCameraAsync();
        deferral.Complete();
    }
}