カメラ プレビューの表示

この記事では、ユニバーサル Windows プラットフォーム (UWP) アプリで XAML ページ内にカメラ プレビュー ストリームをすばやく表示する方法について説明します。 カメラを使って写真やビデオをキャプチャするアプリを作成するには、デバイスとカメラの向きの処理や、キャプチャされたファイルのエンコーディング オプションの設定などのタスクを実行する必要があります。 アプリ シナリオによっては、このような他の考慮事項について考えなくても、カメラからプレビュー ストリームをそのまま表示することができます。 この記事では、最小限のコードでそれを行う方法を示します。 プレビュー ストリームの操作が完了したら、必ず以下の手順に従ってプレビュー ストリームを正しくシャットダウンする必要がある点に注意してください。

写真やビデオをキャプチャするカメラ アプリの作成方法について詳しくは、「MediaCapture を使った基本的な写真、ビデオ、およびオーディオのキャプチャ」をご覧ください。

アプリ マニフェストに機能宣言を追加する

アプリからデバイスのカメラにアクセスするには、アプリでデバイス機能 (webcammicrophone) の使用を宣言する必要があります。

アプリ マニフェストに機能を追加する

  1. Microsoft Visual Studio のソリューション エクスプローラーで、package.appxmanifest 項目をダブルクリックしてアプリケーション マニフェストのデザイナーを開きます。
  2. [機能] タブを選択します。
  3. [Web カメラ] のボックスと [マイク] のボックスをオンにします。

ページに CaptureElement コントロールを追加する

CaptureElement を使って、XAML ページ内にプレビュー ストリームを表示します。

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

MediaCapture を使ってプレビュー ストリームを開始する

MediaCapture オブジェクトは、デバイスのカメラに対するアプリのインターフェイスです。 このクラスは、Windows.Media.Capture 名前空間のメンバーです。 この記事の例では、既定のプロジェクト テンプレートに含まれている API に加えて、Windows.ApplicationModel 名前空間と System.Threading.Tasks 名前空間の API も使われます。

ページの .cs ファイルに次の名前空間を含めるには using ディレクティブを追加します。

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 がスローされます。 この例外は、開発中、アプリ マニフェストに適切な機能を追加し忘れた場合も表示されます。

重要: 一部のデバイス ファミリでは、アプリがデバイスのカメラへのアクセスを付与される前に、ユーザー同意のプロンプトがユーザーに表示されます。 このため、MediaCapture.InitializeAsync のみをメイン UI スレッドから呼び出す必要があります。 別のスレッドからカメラを初期化しようとすると、初期化エラーになる可能性があります。

Source プロパティを設定して、MediaCaptureCaptureElement に接続します。 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;
           }

       }

排他的制御での変更を処理する

前のセクションで説明したように、別のアプリがキャプチャ デバイスを排他的に制御している場合、StartPreviewAsyncFileLoadException をスローします。 Windows 10 Version 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 オブジェクトを取得し、プレビュー ストリームがシャットダウンするまでシステムがアプリを中断しないことを確認します。 ストリームをシャットダウンした後、保留の 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();
    }
}