次の方法で共有


MediaFrameReader を使用してメディア フレームを処理する

この記事では、MediaCaptureMediaFrameReader を使用して、色、深度、赤外線カメラ、オーディオ デバイス、骨格追跡フレームを生成するカスタム フレーム ソースなど、1 つ以上の利用可能なソースからメディア フレームを取得する方法について説明します。 この機能は、拡張現実や深度対応カメラ アプリなど、メディア フレームのリアルタイム処理を実行するアプリで使用するように設計されています。

一般的な写真アプリなど、単にビデオや写真をキャプチャすることに関心がある場合は、 MediaCapture でサポートされている他のキャプチャ手法のいずれかを使用する必要があります。 使用可能なメディア キャプチャ手法と、その使用方法を示す記事の一覧については、「 カメラ」を参照してください。

この記事で説明する機能は、Windows 10 バージョン 1607 以降でのみ使用できます。

MediaFrameReader を使用して、色、深度、赤外線カメラなど、さまざまなフレーム ソースのフレームを表示する方法を示すユニバーサル Windows アプリサンプルがあります。 詳細については、「カメラ フレームのサンプル」を参照してください。

オーディオ データで MediaFrameReader を使用するための新しい API のセットが Windows 10 バージョン 1803 で導入されました。 詳細については、「 MediaFrameReader を使用したオーディオ フレームの処理」を参照してください。

フレーム ソースとフレーム ソース グループの選択

メディア フレームを処理する多くのアプリでは、デバイスのカラー カメラや深度カメラなど、複数のソースから一度にフレームを取得する必要があります。 MediaFrameSourceGroup オブジェクトは、同時に使用できるメディア フレーム ソースのセットを表します。 静的メソッド MediaFrameSourceGroup.FindAllAsync を呼び出して、現在のデバイスでサポートされているフレーム ソースのすべてのグループの一覧を取得します。

var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();

DeviceInformation.CreateWatcher を使用して DeviceWatcher を作成し、MediaFrameSourceGroup.GetDeviceSelector から返される値を して、デバイスで使用可能なフレーム ソース グループが変更されたとき (外部カメラが接続されている場合など) に通知を受け取ることもできます。 詳細については、「 デバイスの列挙」を参照してください。

MediaFrameSourceGroup には、グループに含まれるフレーム ソースを記述する MediaFrameSourceInfo オブジェクトのコレクションがあります。 デバイスで使用可能なフレーム ソース グループを取得したら、関心のあるフレーム ソースを公開するグループを選択できます。

次の例は、フレーム ソース グループを選択する最も簡単な方法を示しています。 このコードは、使用可能なすべてのグループをループし、 SourceInfos コレクション内の各項目をループするだけです。 各 MediaFrameSourceInfo がチェックされ、探している機能がサポートされているかどうかを確認します。 この場合、 MediaStreamType プロパティの値 VideoPreview がチェックされます。つまり、デバイスがビデオ プレビュー ストリームを提供し、 SourceKind プロパティの値 Color がチェックされ、ソースがカラー フレームを提供することを示します。

var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();

MediaFrameSourceGroup selectedGroup = null;
MediaFrameSourceInfo colorSourceInfo = null;

foreach (var sourceGroup in frameSourceGroups)
{
    foreach (var sourceInfo in sourceGroup.SourceInfos)
    {
        if (sourceInfo.MediaStreamType == MediaStreamType.VideoPreview
            && sourceInfo.SourceKind == MediaFrameSourceKind.Color)
        {
            colorSourceInfo = sourceInfo;
            break;
        }
    }
    if (colorSourceInfo != null)
    {
        selectedGroup = sourceGroup;
        break;
    }
}

目的のフレーム ソース グループとフレーム ソースを識別するこの方法は単純なケースで機能しますが、より複雑な条件に基づいてフレーム ソースを選択する場合は、すぐに面倒になる可能性があります。 もう 1 つの方法は、Linq 構文と匿名オブジェクトを使用して選択を行う方法です。 次の例では、Select 拡張メソッドを使用して、frameSourceGroups リスト内の MediaFrameSourceGroup オブジェクトを、グループ自体を表す sourceGroup と、グループ内のカラー フレーム ソースを表す colorSourceInfo という 2 つのフィールドを持つ匿名オブジェクトに変換します。 colorSourceInfo フィールドは、指定された述語が true に解決される最初のオブジェクト 選択する FirstOrDefaultの結果に設定されます。 この場合、ストリームの種類が VideoPreviewで、ソースの種類が Colorかつデバイスのフロントパネルにカメラがあるとき、述語は true になります。

上記のクエリから返された匿名オブジェクトの一覧から、 Where 拡張メソッドを使用して、 colorSourceInfo フィールドが null でないオブジェクトのみを選択します。 最後に、 FirstOrDefault が呼び出され、リスト内の最初の項目が選択されます。

選択したオブジェクトのフィールドを使用して、選択した MediaFrameSourceGroup とカラー カメラを表す MediaFrameSourceInfo オブジェクトへの参照を取得できるようになりました。 これらは後で MediaCapture オブジェクトを初期化し、選択したソースの MediaFrameReader を作成するために使用されます。 最後に、ソース グループが null かどうかをテストする必要があります。つまり、現在のデバイスに要求されたキャプチャ ソースがありません。

var selectedGroupObjects = frameSourceGroups.Select(group =>
   new
   {
       sourceGroup = group,
       colorSourceInfo = group.SourceInfos.FirstOrDefault((sourceInfo) =>
       {
           // On Xbox/Kinect, omit the MediaStreamType and EnclosureLocation tests
           return sourceInfo.MediaStreamType == MediaStreamType.VideoPreview
           && sourceInfo.SourceKind == MediaFrameSourceKind.Color
           && sourceInfo.DeviceInformation?.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front;
       })

   }).Where(t => t.colorSourceInfo != null)
   .FirstOrDefault();

MediaFrameSourceGroup selectedGroup = selectedGroupObjects?.sourceGroup;
MediaFrameSourceInfo colorSourceInfo = selectedGroupObjects?.colorSourceInfo;

if (selectedGroup == null)
{
    return;
}

次の例では、上記と同様の手法を使用して、色、深度、赤外線カメラを含むソース グループを選択します。

var allGroups = await MediaFrameSourceGroup.FindAllAsync();
var eligibleGroups = allGroups.Select(g => new
{
    Group = g,

    // For each source kind, find the source which offers that kind of media frame,
    // or null if there is no such source.
    SourceInfos = new MediaFrameSourceInfo[]
    {
        g.SourceInfos.FirstOrDefault(info => info.SourceKind == MediaFrameSourceKind.Color),
        g.SourceInfos.FirstOrDefault(info => info.SourceKind == MediaFrameSourceKind.Depth),
        g.SourceInfos.FirstOrDefault(info => info.SourceKind == MediaFrameSourceKind.Infrared),
    }
}).Where(g => g.SourceInfos.Any(info => info != null)).ToList();

if (eligibleGroups.Count == 0)
{
    System.Diagnostics.Debug.WriteLine("No source group with color, depth or infrared found.");
    return;
}

var selectedGroupIndex = 0; // Select the first eligible group
MediaFrameSourceGroup selectedGroup = eligibleGroups[selectedGroupIndex].Group;
MediaFrameSourceInfo colorSourceInfo = eligibleGroups[selectedGroupIndex].SourceInfos[0];
MediaFrameSourceInfo infraredSourceInfo = eligibleGroups[selectedGroupIndex].SourceInfos[1];
MediaFrameSourceInfo depthSourceInfo = eligibleGroups[selectedGroupIndex].SourceInfos[2];

Windows 10 バージョン 1803 以降では、 MediaCaptureVideoProfile クラスを使用して、必要な一連の機能を備えたメディア フレーム ソースを選択できます。 詳細については、この記事で後述する 「ビデオ プロファイルを使用してフレーム ソースを選択する 」セクションを参照してください。

選択したフレーム ソース グループを使用するように MediaCapture オブジェクトを初期化する

次の手順では、前の手順で選択したフレーム ソース グループを使用するように MediaCapture オブジェクトを初期化します。 コンストラクターを呼び出して MediaCapture オブジェクトのインスタンスを作成します。 次に、MediaCapture オブジェクトの初期化に使用する MediaCaptureInitializationSettings オブジェクトを作成します。 この例では、次の設定が使用されます。

  • SourceGroup - フレームの取得に使用するソース グループをシステムに通知します。 ソース グループでは、同時に使用できるメディア フレーム ソースのセットが定義されていることに注意してください。
  • SharingMode - これにより、キャプチャ ソース デバイスを排他的に制御する必要があるかどうかをシステムに通知します。 ExclusiveControl に設定した場合、生成するフレームの形式など、キャプチャ デバイスの設定を変更できることを意味しますが、これは、別のアプリが既に排他的制御を持っている場合、メディア キャプチャ デバイスを初期化しようとしたときにアプリが失敗することを意味します。 これを SharedReadOnly に設定すると、フレーム ソースが別のアプリで使用されている場合でもフレーム ソースからフレームを受信できますが、デバイスの設定を変更することはできません。
  • MemoryPreference - CPU を指定した場合、システムは CPU メモリを使用します。これにより、フレームが到着したときに SoftwareBitmap オブジェクトとして使用できるようになります。 自動を指定すると、システムはフレームを格納するための最適なメモリ位置を動的に選択します。 システムが GPU メモリの使用を選択した場合、メディア フレームは SoftwareBitmap ではなく IDirect3DSurface オブジェクトとして到着します。
  • StreamingCaptureMode - オーディオをストリーミングする必要がないことを示すには、これを Video に設定します。

の InitializeAsync を呼び出して、目的の設定で MediaCapture を初期化してください。 初期化が失敗した場合は、 try ブロック内で必ずこれを呼び出してください。

m_mediaCapture = new MediaCapture();

var settings = new MediaCaptureInitializationSettings()
{
    SourceGroup = selectedGroup,
    SharingMode = MediaCaptureSharingMode.ExclusiveControl,
    MemoryPreference = MediaCaptureMemoryPreference.Cpu,
    StreamingCaptureMode = StreamingCaptureMode.Video
};
try
{
    await m_mediaCapture.InitializeAsync(settings);
}
catch (Exception ex)
{
    System.Diagnostics.Debug.WriteLine("MediaCapture initialization failed: " + ex.Message);
    return;
}

フレーム ソースの優先形式を設定する

フレーム ソースの優先形式を設定するには、ソースを表す MediaFrameSource オブジェクトを取得する必要があります。 このオブジェクトを取得するには、初期化された MediaCapture オブジェクトの Frames ディクショナリにアクセスし、使用するフレーム ソースの識別子を指定します。 このため、フレーム ソース グループを選択するときに MediaFrameSourceInfo オブジェクトを保存しました。

MediaFrameSource.SupportedFormats プロパティには、フレーム ソースでサポートされている形式を記述する MediaFrameFormat オブジェクトの一覧が含まれています。 この例では、幅が 1080 ピクセルの形式を選択し、32 ビット RGB 形式でフレームを提供できます。 FirstOrDefault 拡張メソッドは、リスト内の最初のエントリを選択します。 選択した形式が null の場合、要求された形式はフレーム ソースではサポートされません。 形式がサポートされている場合は、 SetFormatAsync を呼び出して、ソースにこの形式の使用を要求できます。

var colorFrameSource = m_mediaCapture.FrameSources[colorSourceInfo.Id];
var preferredFormat = colorFrameSource.SupportedFormats.Where(format =>
{
    return format.VideoFormat.Width >= 1080
    && format.Subtype == MediaEncodingSubtypes.Argb32;

}).FirstOrDefault();

if (preferredFormat == null)
{
    // Our desired format is not supported
    return;
}

await colorFrameSource.SetFormatAsync(preferredFormat);

フレーム ソースのフレーム リーダーを作成する

メディア フレーム ソースのフレームを受信するには、 MediaFrameReader を使用します。

MediaFrameReader m_mediaFrameReader;

初期化された MediaCapture オブジェクトで CreateFrameReaderAsync を呼び出して、フレーム リーダーをインスタンス化します。 このメソッドの最初の引数は、フレームを受信するフレーム ソースです。 使用するフレーム ソースごとに個別のフレーム リーダーを作成できます。 2 番目の引数は、フレームを到着させる出力形式をシステムに指示します。 これにより、フレームの到着時に独自の変換を行う必要がなくなります。 フレーム ソースでサポートされていない形式を指定した場合、例外がスローされるので、この値が SupportedFormats コレクション内にあることを確認してください。

フレーム リーダーを作成した後、ソースから新しいフレームが使用可能になると発生する FrameArrived イベントのハンドラーを登録します。

StartAsync を呼び出して、ソースからフレームの読み取りを開始するようにシステムに指示します。

m_mediaFrameReader = await m_mediaCapture.CreateFrameReaderAsync(colorFrameSource, MediaEncodingSubtypes.Argb32);
m_mediaFrameReader.FrameArrived += ColorFrameReader_FrameArrived;
await m_mediaFrameReader.StartAsync();

フレーム到達イベントを処理する

MediaFrameReader.FrameArrived イベントは、新しいフレームが使用可能になると常に発生します。 到着するすべてのフレームを処理するか、必要な場合にのみフレームを使用するかを選択できます。 フレーム リーダーは独自のスレッドでイベントを発生させるので、複数のスレッドから同じデータにアクセスしないように、同期ロジックの実装が必要になる場合があります。 このセクションでは、XAML ページのイメージ コントロールに描画カラー フレームを同期する方法について説明します。 このシナリオでは、XAML コントロールに対するすべての更新を UI スレッドで実行する必要がある追加の同期制約に対処します。

XAML でフレームを表示する最初の手順は、Image コントロールを作成することです。

<Image x:Name="iFrameReaderImageControl" MaxWidth="300" MaxHeight="200"/>

コード ビハインド ページで、すべての受信イメージがコピーされるバックバッファとして使用される SoftwareBitmap 型のクラスメンバー変数を宣言します。 イメージ データ自体はコピーされず、オブジェクト参照のみであることに注意してください。 また、UI 操作が現在実行されているかどうかを追跡するブール値を宣言します。

private SoftwareBitmap backBuffer;
private bool taskRunning = false;

フレームが SoftwareBitmap オブジェクトとして到着するため、SoftwareBitmapSource オブジェクトを作成する必要があります。これにより、SoftwareBitmap を XAML コントロールのソースとして使用できます。 フレーム リーダーを開始する前に、コード内のどこかにイメージ ソースを設定する必要があります。

iFrameReaderImageControl.Source = new SoftwareBitmapSource();

次に、 FrameArrived イベント ハンドラーを実装します。 ハンドラーが呼び出されると、 sender パラメーターには、イベントを発生させた MediaFrameReader オブジェクトへの参照が含まれます。 このオブジェクトで TryAcquireLatestFrame を 呼び出して、最新のフレームの取得を試みます。 名前が示すように、TryAcquireLatestFrame はフレームを返せないかもしれません。 そのため、VideoMediaFrame プロパティと SoftwareBitmap プロパティにアクセスするときは、必ず null をテストしてください。 この例では、null 条件演算子は ? は SoftwareBitmap にアクセスするために使用され、取得されたオブジェクトに null がチェックされます。

Image コントロールでは、事前乗算またはアルファなしの BRGA8 形式でのみ画像を表示できます。 到着フレームがその形式でない場合は、静的メソッド Convert を使用してソフトウェア ビットマップを正しい形式に変換します。

次に、 Interlocked.Exchange メソッドを使用して、到着ビットマップへの参照をバックバッファー ビットマップにスワップします。 このメソッドは、スレッド セーフなアトミック操作でこれらの参照をスワップします。 スワップ後、 softwareBitmap 変数内の古いバックバッファー イメージは、リソースをクリーンアップするために破棄されます。

次に、Image 要素に関連付けられている CoreDispatcher を使用して、RunAsync を呼び出して UI スレッドで実行するタスクを作成します。 非同期タスクはタスク内で実行されるため、 RunAsync に渡されるラムダ式は async キーワードを使用して宣言されます。

タスク内で、 _taskRunning 変数がチェックされ、タスクのインスタンスが一度に 1 つだけ実行されていることを確認します。 タスクがまだ実行されていない場合、 _taskRunning は true に設定され、タスクが再度実行されないようにします。 ループ中の では、Interlocked.Exchange が呼び出され、バックバッファーイメージが null になるまでバックバッファーから一時的な SoftwareBitmap にコピーされます。 一時ビットマップが設定されるたびに、イメージSource プロパティが SoftwareBitmapSource にキャストされ、SetBitmapAsync が呼び出されてイメージのソースが設定されます。

最後に、 _taskRunning 変数は false に戻されるため、次回ハンドラーが呼び出されるときにタスクを再度実行できます。

MediaFrameReferenceVideoMediaFrame プロパティによって提供される SoftwareBitmap オブジェクトまたは Direct3DSurface オブジェクトにアクセスすると、これらのオブジェクトへの厳密な参照が作成されます。つまり、含まれている MediaFrameReferenceDispose を呼び出しても破棄されません。 オブジェクトを直ちに破棄するには、SoftwareBitmap または Direct3DSurfaceDispose メソッドを明示的に直接呼び出す必要があります。 そうしないと、ガベージ コレクターは最終的にこれらのオブジェクトのメモリを解放しますが、これがいつ発生するかはわかりません。また、割り当てられたビットマップまたはサーフェスの数がシステムで許可されている最大量を超えた場合、新しいフレームのフローは停止します。 たとえば 、SoftwareBitmap.Copy メソッドを使用して取得したフレームをコピーし、元のフレームを解放してこの制限を克服できます。 また、MediaFrameReaderCreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype, Windows.Graphics.Imaging.BitmapSize outputSize) または CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype)のオーバーロードを使用して作成する場合、返されるフレームは元のフレームデータのコピーであるため、保持されていてもフレームの取得が停止することはありません。

private void ColorFrameReader_FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
{
    var mediaFrameReference = sender.TryAcquireLatestFrame();
    var videoMediaFrame = mediaFrameReference?.VideoMediaFrame;
    var softwareBitmap = videoMediaFrame?.SoftwareBitmap;

    if (softwareBitmap != null)
    {
        if (softwareBitmap.BitmapPixelFormat != Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8 ||
            softwareBitmap.BitmapAlphaMode != Windows.Graphics.Imaging.BitmapAlphaMode.Premultiplied)
        {
            softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
        }

        // Swap the processed frame to _backBuffer and dispose of the unused image.
        softwareBitmap = Interlocked.Exchange(ref backBuffer, softwareBitmap);
        softwareBitmap?.Dispose();

        // Changes to XAML ImageElement must happen on UI thread through Dispatcher
        var task = iFrameReaderImageControl.DispatcherQueue.TryEnqueue(
            async () =>
            {
                // Don't let two copies of this task run at the same time.
                if (taskRunning)
                {
                    return;
                }
                taskRunning = true;

                // Keep draining frames from the backbuffer until the backbuffer is empty.
                SoftwareBitmap latestBitmap;
                while ((latestBitmap = Interlocked.Exchange(ref backBuffer, null)) != null)
                {
                    var imageSource = (SoftwareBitmapSource)iFrameReaderImageControl.Source;
                    await imageSource.SetBitmapAsync(latestBitmap);
                    latestBitmap.Dispose();
                }

                taskRunning = false;
            });
    }

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

リソースをクリーンアップする

フレームの読み取りが完了したら、 StopAsync を呼び出し、 FrameArrived ハンドラーの登録を解除し、 MediaCapture オブジェクトを破棄して、メディア フレーム リーダーを停止してください。

await m_mediaFrameReader.StopAsync();
m_mediaFrameReader.FrameArrived -= ColorFrameReader_FrameArrived;
m_mediaCapture.Dispose();
m_mediaCapture = null;

アプリケーションが中断されたときにメディア キャプチャ オブジェクトをクリーンアップする方法の詳細については、「 WinUI 3 アプリでカメラプレビューを表示する」を参照してください。

フレームレンダラー ヘルパークラス

このセクションでは、アプリ内の色、赤外線、深度ソースからのフレームを簡単に表示できるようにするヘルパー クラスを提供する、a の完全なコード 一覧を示します。 通常は、画面に表示するだけでなく、深度と赤外線のデータを使用して何かを行う必要がありますが、このヘルパー クラスは、フレーム リーダー機能を示したり、独自のフレーム リーダーの実装をデバッグしたりするのに便利なツールです。 このセクションのコードは、 カメラ フレームのサンプルから調整されています。

FrameRenderer ヘルパー クラスは、次のメソッドを実装します。

  • FrameRenderer コンストラクター - コンストラクターは、メディア フレームを表示するために渡す XAML Image 要素を使用するようにヘルパー クラスを初期化します。
  • ProcessFrame - このメソッドは、コンストラクターに渡した Image 要素に、MediaFrameReference で表されるメディア フレームを表示します。 通常は、このメソッドを FrameArrived イベント ハンドラーから呼び出し、 TryAcquireLatestFrame によって返されるフレームを渡す必要があります。
  • ConvertToDisplayableImage - このメソッドはメディア フレームの形式をチェックし、必要に応じて表示可能な形式に変換します。 カラー イメージの場合、これは、カラー形式が BGRA8 であり、ビットマップ アルファ モードが事前乗算されていることを確認することを意味します。 深度フレームまたは赤外線フレームの場合、各スキャンラインは、サンプルに含まれていて以下に示す PsuedoColorHelper クラスを使用して、深度または赤外線の値を psuedocolor グラデーションに変換するために処理されます。

SoftwareBitmap イメージのピクセル操作を行うには、ネイティブ メモリ バッファーにアクセスする必要があります。 これを行うには、以下のコード リストに含まれている IMemoryBufferByteAccess COM インターフェイスを使用する必要があります。また、安全でないコードのコンパイルを許可するようにプロジェクトのプロパティを更新する必要があります。 詳細については、「 ビットマップ イメージの作成、編集、保存」を参照してください。

[GeneratedComInterface]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe partial interface IMemoryBufferByteAccess
{
    void GetBuffer(out byte* buffer, out uint capacity);
}
class FrameRenderer
{
    private Image m_imageElement;
    private SoftwareBitmap m_backBuffer;
    private bool _taskRunning = false;

    public FrameRenderer(Image imageElement)
    {
        m_imageElement = imageElement;
        m_imageElement.Source = new SoftwareBitmapSource();
    }

    // Processes a MediaFrameReference and displays it in a XAML image control
    public void ProcessFrame(MediaFrameReference frame)
    {
        var softwareBitmap = FrameRenderer.ConvertToDisplayableImage(frame?.VideoMediaFrame);
        if (softwareBitmap != null)
        {
            // Swap the processed frame to m_backBuffer and trigger UI thread to render it
            softwareBitmap = Interlocked.Exchange(ref m_backBuffer, softwareBitmap);

            // UI thread always reset m_backBuffer before using it.  Unused bitmap should be disposed.
            softwareBitmap?.Dispose();

            // Changes to xaml ImageElement must happen in UI thread through Dispatcher
            var task = m_imageElement.DispatcherQueue.TryEnqueue(
                async () =>
                {
                    // Don't let two copies of this task run at the same time.
                    if (_taskRunning)
                    {
                        return;
                    }
                    _taskRunning = true;

                    // Keep draining frames from the backbuffer until the backbuffer is empty.
                    SoftwareBitmap latestBitmap;
                    while ((latestBitmap = Interlocked.Exchange(ref m_backBuffer, null)) != null)
                    {
                        var imageSource = (SoftwareBitmapSource)m_imageElement.Source;
                        await imageSource.SetBitmapAsync(latestBitmap);
                        latestBitmap.Dispose();
                    }

                    _taskRunning = false;
                });
        }
    }



    // Function delegate that transforms a scanline from an input image to an output image.
    private unsafe delegate void TransformScanline(int pixelWidth, byte* inputRowBytes, byte* outputRowBytes);
    /// <summary>
    /// Determines the subtype to request from the MediaFrameReader that will result in
    /// a frame that can be rendered by ConvertToDisplayableImage.
    /// </summary>
    /// <returns>Subtype string to request, or null if subtype is not renderable.</returns>

    public static string GetSubtypeForFrameReader(MediaFrameSourceKind kind, MediaFrameFormat format)
    {
        // Note that media encoding subtypes may differ in case.
        // https://docs.microsoft.com/en-us/uwp/api/Windows.Media.MediaProperties.MediaEncodingSubtypes

        string subtype = format.Subtype;
        switch (kind)
        {
            // For color sources, we accept anything and request that it be converted to Bgra8.
            case MediaFrameSourceKind.Color:
                return Windows.Media.MediaProperties.MediaEncodingSubtypes.Bgra8;

            // The only depth format we can render is D16.
            case MediaFrameSourceKind.Depth:
                return String.Equals(subtype, Windows.Media.MediaProperties.MediaEncodingSubtypes.D16, StringComparison.OrdinalIgnoreCase) ? subtype : null;

            // The only infrared formats we can render are L8 and L16.
            case MediaFrameSourceKind.Infrared:
                return (String.Equals(subtype, Windows.Media.MediaProperties.MediaEncodingSubtypes.L8, StringComparison.OrdinalIgnoreCase) ||
                    String.Equals(subtype, Windows.Media.MediaProperties.MediaEncodingSubtypes.L16, StringComparison.OrdinalIgnoreCase)) ? subtype : null;

            // No other source kinds are supported by this class.
            default:
                return null;
        }
    }

    /// <summary>
    /// Converts a frame to a SoftwareBitmap of a valid format to display in an Image control.
    /// </summary>
    /// <param name="inputFrame">Frame to convert.</param>

    public static unsafe SoftwareBitmap ConvertToDisplayableImage(VideoMediaFrame inputFrame)
    {
        SoftwareBitmap result = null;
        using (var inputBitmap = inputFrame?.SoftwareBitmap)
        {
            if (inputBitmap != null)
            {
                switch (inputFrame.FrameReference.SourceKind)
                {
                    case MediaFrameSourceKind.Color:
                        // XAML requires Bgra8 with premultiplied alpha.
                        // We requested Bgra8 from the MediaFrameReader, so all that's
                        // left is fixing the alpha channel if necessary.
                        if (inputBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8)
                        {
                            System.Diagnostics.Debug.WriteLine("Color frame in unexpected format.");
                        }
                        else if (inputBitmap.BitmapAlphaMode == BitmapAlphaMode.Premultiplied)
                        {
                            // Already in the correct format.
                            result = SoftwareBitmap.Copy(inputBitmap);
                        }
                        else
                        {
                            // Convert to premultiplied alpha.
                            result = SoftwareBitmap.Convert(inputBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
                        }
                        break;

                    case MediaFrameSourceKind.Depth:
                        // We requested D16 from the MediaFrameReader, so the frame should
                        // be in Gray16 format.
                        if (inputBitmap.BitmapPixelFormat == BitmapPixelFormat.Gray16)
                        {
                            // Use a special pseudo color to render 16 bits depth frame.
                            var depthScale = (float)inputFrame.DepthMediaFrame.DepthFormat.DepthScaleInMeters;
                            var minReliableDepth = inputFrame.DepthMediaFrame.MinReliableDepth;
                            var maxReliableDepth = inputFrame.DepthMediaFrame.MaxReliableDepth;
                            result = TransformBitmap(inputBitmap, (w, i, o) => PseudoColorHelper.PseudoColorForDepth(w, i, o, depthScale, minReliableDepth, maxReliableDepth));
                        }
                        else
                        {
                            System.Diagnostics.Debug.WriteLine("Depth frame in unexpected format.");
                        }
                        break;

                    case MediaFrameSourceKind.Infrared:
                        // We requested L8 or L16 from the MediaFrameReader, so the frame should
                        // be in Gray8 or Gray16 format. 
                        switch (inputBitmap.BitmapPixelFormat)
                        {
                            case BitmapPixelFormat.Gray16:
                                // Use pseudo color to render 16 bits frames.
                                result = TransformBitmap(inputBitmap, PseudoColorHelper.PseudoColorFor16BitInfrared);
                                break;

                            case BitmapPixelFormat.Gray8:
                                // Use pseudo color to render 8 bits frames.
                                result = TransformBitmap(inputBitmap, PseudoColorHelper.PseudoColorFor8BitInfrared);
                                break;
                            default:
                                System.Diagnostics.Debug.WriteLine("Infrared frame in unexpected format.");
                                break;
                        }
                        break;
                }
            }
        }

        return result;
    }



    /// <summary>
    /// Transform image into Bgra8 image using given transform method.
    /// </summary>
    /// <param name="softwareBitmap">Input image to transform.</param>
    /// <param name="transformScanline">Method to map pixels in a scanline.</param>

    private static unsafe SoftwareBitmap TransformBitmap(SoftwareBitmap softwareBitmap, TransformScanline transformScanline)
    {
        // XAML Image control only supports premultiplied Bgra8 format.
        var outputBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8,
            softwareBitmap.PixelWidth, softwareBitmap.PixelHeight, BitmapAlphaMode.Premultiplied);

        using (var input = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
        using (var output = outputBitmap.LockBuffer(BitmapBufferAccessMode.Write))
        {
            // Get stride values to calculate buffer position for a given pixel x and y position.
            int inputStride = input.GetPlaneDescription(0).Stride;
            int outputStride = output.GetPlaneDescription(0).Stride;
            int pixelWidth = softwareBitmap.PixelWidth;
            int pixelHeight = softwareBitmap.PixelHeight;

            using (var outputReference = output.CreateReference())
            using (var inputReference = input.CreateReference())
            {
                // Get input and output byte access buffers.
                byte* inputBytes;
                uint inputCapacity;
                ((IMemoryBufferByteAccess)inputReference).GetBuffer(out inputBytes, out inputCapacity);
                byte* outputBytes;
                uint outputCapacity;
                ((IMemoryBufferByteAccess)outputReference).GetBuffer(out outputBytes, out outputCapacity);

                // Iterate over all pixels and store converted value.
                for (int y = 0; y < pixelHeight; y++)
                {
                    byte* inputRowBytes = inputBytes + y * inputStride;
                    byte* outputRowBytes = outputBytes + y * outputStride;

                    transformScanline(pixelWidth, inputRowBytes, outputRowBytes);
                }
            }
        }

        return outputBitmap;
    }



    /// <summary>
    /// A helper class to manage look-up-table for pseudo-colors.
    /// </summary>

    private static class PseudoColorHelper
    {
        #region Constructor, private members and methods

        private const int TableSize = 1024;   // Look up table size
        private static readonly uint[] PseudoColorTable;
        private static readonly uint[] InfraredRampTable;

        // Color palette mapping value from 0 to 1 to blue to red colors.
        private static readonly Color[] ColorRamp =
        {
            Color.FromArgb(a:0xFF, r:0x7F, g:0x00, b:0x00),
            Color.FromArgb(a:0xFF, r:0xFF, g:0x00, b:0x00),
            Color.FromArgb(a:0xFF, r:0xFF, g:0x7F, b:0x00),
            Color.FromArgb(a:0xFF, r:0xFF, g:0xFF, b:0x00),
            Color.FromArgb(a:0xFF, r:0x7F, g:0xFF, b:0x7F),
            Color.FromArgb(a:0xFF, r:0x00, g:0xFF, b:0xFF),
            Color.FromArgb(a:0xFF, r:0x00, g:0x7F, b:0xFF),
            Color.FromArgb(a:0xFF, r:0x00, g:0x00, b:0xFF),
            Color.FromArgb(a:0xFF, r:0x00, g:0x00, b:0x7F),
        };

        static PseudoColorHelper()
        {
            PseudoColorTable = InitializePseudoColorLut();
            InfraredRampTable = InitializeInfraredRampLut();
        }

        /// <summary>
        /// Maps an input infrared value between [0, 1] to corrected value between [0, 1].
        /// </summary>
        /// <param name="value">Input value between [0, 1].</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]  // Tell the compiler to inline this method to improve performance

        private static uint InfraredColor(float value)
        {
            int index = (int)(value * TableSize);
            index = index < 0 ? 0 : index > TableSize - 1 ? TableSize - 1 : index;
            return InfraredRampTable[index];
        }

        /// <summary>
        /// Initializes the pseudo-color look up table for infrared pixels
        /// </summary>

        private static uint[] InitializeInfraredRampLut()
        {
            uint[] lut = new uint[TableSize];
            for (int i = 0; i < TableSize; i++)
            {
                var value = (float)i / TableSize;
                // Adjust to increase color change between lower values in infrared images

                var alpha = (float)Math.Pow(1 - value, 12);
                lut[i] = ColorRampInterpolation(alpha);
            }

            return lut;
        }



        /// <summary>
        /// Initializes pseudo-color look up table for depth pixels
        /// </summary>
        private static uint[] InitializePseudoColorLut()
        {
            uint[] lut = new uint[TableSize];
            for (int i = 0; i < TableSize; i++)
            {
                lut[i] = ColorRampInterpolation((float)i / TableSize);
            }

            return lut;
        }



        /// <summary>
        /// Maps a float value to a pseudo-color pixel
        /// </summary>
        private static uint ColorRampInterpolation(float value)
        {
            // Map value to surrounding indexes on the color ramp
            int rampSteps = ColorRamp.Length - 1;
            float scaled = value * rampSteps;
            int integer = (int)scaled;
            int index =
                integer < 0 ? 0 :
                integer >= rampSteps - 1 ? rampSteps - 1 :
                integer;

            Color prev = ColorRamp[index];
            Color next = ColorRamp[index + 1];

            // Set color based on ratio of closeness between the surrounding colors
            uint alpha = (uint)((scaled - integer) * 255);
            uint beta = 255 - alpha;
            return
                ((prev.A * beta + next.A * alpha) / 255) << 24 | // Alpha
                ((prev.R * beta + next.R * alpha) / 255) << 16 | // Red
                ((prev.G * beta + next.G * alpha) / 255) << 8 |  // Green
                ((prev.B * beta + next.B * alpha) / 255);        // Blue
        }


        /// <summary>
        /// Maps a value in [0, 1] to a pseudo RGBA color.
        /// </summary>
        /// <param name="value">Input value between [0, 1].</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]

        private static uint PseudoColor(float value)
        {
            int index = (int)(value * TableSize);
            index = index < 0 ? 0 : index > TableSize - 1 ? TableSize - 1 : index;
            return PseudoColorTable[index];
        }

        #endregion

        /// <summary>
        /// Maps each pixel in a scanline from a 16 bit depth value to a pseudo-color pixel.
        /// </summary>
        /// <param name="pixelWidth">Width of the input scanline, in pixels.</param>
        /// <param name="inputRowBytes">Pointer to the start of the input scanline.</param>
        /// <param name="outputRowBytes">Pointer to the start of the output scanline.</param>
        /// <param name="depthScale">Physical distance that corresponds to one unit in the input scanline.</param>
        /// <param name="minReliableDepth">Shortest distance at which the sensor can provide reliable measurements.</param>
        /// <param name="maxReliableDepth">Furthest distance at which the sensor can provide reliable measurements.</param>

        public static unsafe void PseudoColorForDepth(int pixelWidth, byte* inputRowBytes, byte* outputRowBytes, float depthScale, float minReliableDepth, float maxReliableDepth)
        {
            // Visualize space in front of your desktop.
            float minInMeters = minReliableDepth * depthScale;
            float maxInMeters = maxReliableDepth * depthScale;
            float one_min = 1.0f / minInMeters;
            float range = 1.0f / maxInMeters - one_min;

            ushort* inputRow = (ushort*)inputRowBytes;
            uint* outputRow = (uint*)outputRowBytes;

            for (int x = 0; x < pixelWidth; x++)
            {
                var depth = inputRow[x] * depthScale;

                if (depth == 0)
                {
                    // Map invalid depth values to transparent pixels.
                    // This happens when depth information cannot be calculated, e.g. when objects are too close.
                    outputRow[x] = 0;
                }
                else
                {
                    var alpha = (1.0f / depth - one_min) / range;
                    outputRow[x] = PseudoColor(alpha * alpha);
                }
            }
        }



        /// <summary>
        /// Maps each pixel in a scanline from a 8 bit infrared value to a pseudo-color pixel.
        /// </summary>
        /// /// <param name="pixelWidth">Width of the input scanline, in pixels.</param>
        /// <param name="inputRowBytes">Pointer to the start of the input scanline.</param>
        /// <param name="outputRowBytes">Pointer to the start of the output scanline.</param>

        public static unsafe void PseudoColorFor8BitInfrared(
            int pixelWidth, byte* inputRowBytes, byte* outputRowBytes)
        {
            byte* inputRow = inputRowBytes;
            uint* outputRow = (uint*)outputRowBytes;

            for (int x = 0; x < pixelWidth; x++)
            {
                outputRow[x] = InfraredColor(inputRow[x] / (float)Byte.MaxValue);
            }
        }

        /// <summary>
        /// Maps each pixel in a scanline from a 16 bit infrared value to a pseudo-color pixel.
        /// </summary>
        /// <param name="pixelWidth">Width of the input scanline.</param>
        /// <param name="inputRowBytes">Pointer to the start of the input scanline.</param>
        /// <param name="outputRowBytes">Pointer to the start of the output scanline.</param>

        public static unsafe void PseudoColorFor16BitInfrared(int pixelWidth, byte* inputRowBytes, byte* outputRowBytes)
        {
            ushort* inputRow = (ushort*)inputRowBytes;
            uint* outputRow = (uint*)outputRowBytes;

            for (int x = 0; x < pixelWidth; x++)
            {
                outputRow[x] = InfraredColor(inputRow[x] / (float)UInt16.MaxValue);
            }
        }
    }


    // Displays the provided softwareBitmap in a XAML image control.
    public void PresentSoftwareBitmap(SoftwareBitmap softwareBitmap)
    {
        if (softwareBitmap != null)
        {
            // Swap the processed frame to m_backBuffer and trigger UI thread to render it
            softwareBitmap = Interlocked.Exchange(ref m_backBuffer, softwareBitmap);

            // UI thread always reset m_backBuffer before using it.  Unused bitmap should be disposed.
            softwareBitmap?.Dispose();

            // Changes to xaml ImageElement must happen in UI thread through Dispatcher
            var task = m_imageElement.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                async () =>
                {
                    // Don't let two copies of this task run at the same time.
                    if (_taskRunning)
                    {
                        return;
                    }
                    _taskRunning = true;

                    // Keep draining frames from the backbuffer until the backbuffer is empty.
                    SoftwareBitmap latestBitmap;
                    while ((latestBitmap = Interlocked.Exchange(ref m_backBuffer, null)) != null)
                    {
                        var imageSource = (SoftwareBitmapSource)m_imageElement.Source;
                        await imageSource.SetBitmapAsync(latestBitmap);
                        latestBitmap.Dispose();
                    }

                    _taskRunning = false;
                });
        }
    }
}

MultiSourceMediaFrameReader を使用して複数のソースから時間相関フレームを取得する

Windows 10 バージョン 1607 以降では、 MultiSourceMediaFrameReader を使用して、複数のソースから時間相関フレームを受信できます。 この API を使用すると、 DepthCorrelatedCoordinateMapper クラスの使用など、一時的に近接して取得された複数のソースからのフレームを必要とする処理を簡単に実行できます。 この新しい方法を使用する場合の制限の 1 つは、フレーム到着イベントが最も低速なキャプチャ ソースの速度でのみ発生することです。 より高速なソースからの余分なフレームが削除されます。 また、システムは異なるレートで異なるソースからフレームが到着することを想定しているため、ソースがフレームの生成を完全に停止したかどうかを自動的に認識することはありません。 このセクションのコード例では、イベントを使用して、関連付けられたフレームがアプリ定義の制限時間内に到着しない場合に呼び出される独自のタイムアウト ロジックを作成する方法を示します。

MultiSourceMediaFrameReader を使用する手順は、この記事で前述した MediaFrameReader を使用する手順と似ています。 この例では、カラー ソースと深度ソースを使用します。 各ソースからフレームを選択するために使用されるメディア フレーム ソース ID を格納する文字列変数をいくつか宣言します。 次に、 ManualResetEventSlimCancellationTokenSource、およびこの例のタイムアウト ロジックの実装に使用される EventHandler を宣言します。

private MultiSourceMediaFrameReader m_multiFrameReader = null;
private string m_colorSourceId = null;
private string m_depthSourceId = null;


private readonly ManualResetEventSlim m_frameReceived = new ManualResetEventSlim(false);
private readonly CancellationTokenSource m_tokenSource = new CancellationTokenSource();
public event EventHandler CorrelationFailed;

この記事で前述した手法を使用して、このサンプル シナリオに必要な色と深度のソースを含む MediaFrameSourceGroup を照会します。 目的のフレーム ソース グループを選択した後、各フレーム ソースの MediaFrameSourceInfo を取得します。

var allGroups = await MediaFrameSourceGroup.FindAllAsync();
var eligibleGroups = allGroups.Select(g => new
{
    Group = g,

    // For each source kind, find the source which offers that kind of media frame,
    // or null if there is no such source.
    SourceInfos = new MediaFrameSourceInfo[]
    {
        g.SourceInfos.FirstOrDefault(info => info.SourceKind == MediaFrameSourceKind.Color),
        g.SourceInfos.FirstOrDefault(info => info.SourceKind == MediaFrameSourceKind.Depth)
    }
}).Where(g => g.SourceInfos.Any(info => info != null)).ToList();

if (eligibleGroups.Count == 0)
{
    System.Diagnostics.Debug.WriteLine("No source group with color, depth or infrared found.");
    return;
}

var selectedGroupIndex = 0; // Select the first eligible group
MediaFrameSourceGroup selectedGroup = eligibleGroups[selectedGroupIndex].Group;
MediaFrameSourceInfo colorSourceInfo = eligibleGroups[selectedGroupIndex].SourceInfos[0];
MediaFrameSourceInfo depthSourceInfo = eligibleGroups[selectedGroupIndex].SourceInfos[1];

MediaCapture オブジェクトを作成して初期化し、初期化設定で選択したフレーム ソース グループを渡します。

m_mediaCapture = new MediaCapture();

var settings = new MediaCaptureInitializationSettings()
{
    SourceGroup = selectedGroup,
    SharingMode = MediaCaptureSharingMode.ExclusiveControl,
    MemoryPreference = MediaCaptureMemoryPreference.Cpu,
    StreamingCaptureMode = StreamingCaptureMode.Video
};

await m_mediaCapture.InitializeAsync(settings);

MediaCapture オブジェクトを初期化した後、色と深度の各カメラの MediaFrameSource オブジェクトを取得します。 対応するソースの到着フレームを選択できるように、各ソースの ID を格納します。

MediaFrameSource colorSource =
    m_mediaCapture.FrameSources.Values.FirstOrDefault(
        s => s.Info.SourceKind == MediaFrameSourceKind.Color);

MediaFrameSource depthSource =
    m_mediaCapture.FrameSources.Values.FirstOrDefault(
        s => s.Info.SourceKind == MediaFrameSourceKind.Depth);

if (colorSource == null || depthSource == null)
{
    System.Diagnostics.Debug.WriteLine("MediaCapture doesn't have the Color and Depth streams");
    return;
}

m_colorSourceId = colorSource.Info.Id;
m_depthSourceId = depthSource.Info.Id;

CreateMultiSourceFrameReaderAsync を呼び出し、リーダーが使用するフレーム ソースの配列を渡すことによって、MultiSourceMediaFrameReader を作成して初期化します。 FrameArrived イベントのイベント ハンドラーを登録します。 この例では、フレームを Image コントロールにレンダリングするために、この記事で前述した FrameRenderer ヘルパー クラスのインスタンスを作成します。 StartAsync を呼び出してフレーム リーダーを起動します。

この例で前に宣言 した CorellationFailed イベントのイベント ハンドラーを登録します。 このイベントは、使用されているメディア フレーム ソースの 1 つがフレームの生成を停止した場合に通知します。 最後に、 Task.Run を 呼び出して、タイムアウト ヘルパー メソッド NotifyAboutCorrelationFailure を別のスレッドで呼び出します。 このメソッドの実装については、この記事の後半で説明します。

m_multiFrameReader = await m_mediaCapture.CreateMultiSourceFrameReaderAsync(
    new[] { colorSource, depthSource });

m_multiFrameReader.FrameArrived += MultiFrameReader_FrameArrived;

m_frameRenderer = new FrameRenderer(iFrameReaderImageControl);

MultiSourceMediaFrameReaderStartStatus startStatus =
    await m_multiFrameReader.StartAsync();

if (startStatus != MultiSourceMediaFrameReaderStartStatus.Success)
{
    throw new InvalidOperationException(
        "Unable to start reader: " + startStatus);
}

this.CorrelationFailed += MainWindow_CorrelationFailed;
Task.Run(() => NotifyAboutCorrelationFailure(m_tokenSource.Token));

FrameArrived イベントは、MultiSourceMediaFrameReader によって管理されているすべてのメディア フレーム ソースから新しいフレームが使用可能になると発生します。 つまり、最も遅いメディア ソースのタイミングでイベントがトリガーされます。 低速のソースが 1 つのフレームを生成する時間内に 1 つのソースが複数のフレームを生成する場合、高速ソースからの余分なフレームは削除されます。

TryAcquireLatestFrameを呼び出して、イベントに関連付けられている MultiSourceMediaFrameReference 取得します。 TryGetFrameReferenceBySourceId を呼び出して、各メディア フレーム ソースに関連付けられている MediaFrameReference を取得し、フレーム リーダーの初期化時に格納された ID 文字列を渡します。

フレームが到着したことを通知するには、ManualResetEventSlim オブジェクトの Set メソッドを呼び出します。 このイベントは、別のスレッドで実行されている NotifyCorrelationFailure メソッドで確認します。

最後に、時間相関メディア フレームに対して任意の処理を実行します。 この例は、単に深度ソースからのフレームを表示します。

private void MultiFrameReader_FrameArrived(MultiSourceMediaFrameReader sender, MultiSourceMediaFrameArrivedEventArgs args)
{
    using (MultiSourceMediaFrameReference muxedFrame =
        sender.TryAcquireLatestFrame())
    using (MediaFrameReference colorFrame =
        muxedFrame.TryGetFrameReferenceBySourceId(m_colorSourceId))
    using (MediaFrameReference depthFrame =
        muxedFrame.TryGetFrameReferenceBySourceId(m_depthSourceId))
    {
        // Notify the listener thread that the frame has been received.
        m_frameReceived.Set();
        m_frameRenderer.ProcessFrame(depthFrame);
    }
}

NotifyCorrelationFailure ヘルパー メソッドは、フレーム リーダーの起動後に別のスレッドで実行されました。 このメソッドでは、フレーム受信イベントが通知されているかどうかを確認します。 FrameArrived ハンドラーでは、関連付けられた一連のフレームが到着するたびにこのイベントを設定します。 イベントが一部のアプリ定義期間 (5 秒が妥当な値) で通知されておらず、 CancellationToken を使用してタスクが取り消されなかった場合、メディア フレーム ソースの 1 つがフレームの読み取りを停止している可能性があります。 この場合、通常はフレーム リーダーをシャットダウンするため、アプリ定義の CorrelationFailed イベントを発生させます。 このイベントのハンドラーでは、フレーム リーダーを停止し、この記事で前述したように、関連付けられているリソースをクリーンアップできます。

private void NotifyAboutCorrelationFailure(CancellationToken token)
{
    // If in 5 seconds the token is not cancelled and frame event is not signaled,
    // correlation is most likely failed.
    if (WaitHandle.WaitAny(new[] { token.WaitHandle, m_frameReceived.WaitHandle }, 5000)
            == WaitHandle.WaitTimeout)
    {
        CorrelationFailed?.Invoke(this, EventArgs.Empty);
    }
}
private async void MainWindow_CorrelationFailed(object sender, EventArgs e)
{
    await m_multiFrameReader.StopAsync();
    m_multiFrameReader.FrameArrived -= MultiFrameReader_FrameArrived;
    m_mediaCapture.Dispose();
    m_mediaCapture = null;
}

バッファー フレーム取得モードを使用して、取得されたフレームのシーケンスを保持する

Windows 10 バージョン 1709 以降では、MediaFrameReader または MultiSourceMediaFrameReaderAcquisitionMode プロパティを Buffered に設定して、フレーム ソースからアプリに渡されたフレームのシーケンスを保持できます。

m_mediaFrameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Buffered;

既定の取得モード である Realtime では、アプリが前のフレームの FrameArrived イベントを処理している間に複数のフレームがソースから取得された場合、システムは、最後に取得したフレームをアプリに送信し、バッファーで待機している追加のフレームをドロップします。 これにより、常に利用可能な最新のフレームがアプリに提供されます。 これは通常、リアルタイムコンピューター ビジョン アプリケーションに最も便利なモードです。

バッファー化 取得モードでは、システムはバッファー内のすべてのフレームを保持し、受信した順序でそれらを FrameArrived イベントを介してアプリに提供し続けます。 このモードでは、システムのフレームバッファーがいっぱいになると、アプリが以前のフレームの FrameArrived イベントを完了するまで、システムは新しいフレームの取得を停止し、バッファー内の領域を増やします。

MediaSource を使用して MediaPlayerElement 内のフレームを表示する

Windows バージョン 1709 以降では、 MediaFrameReader から取得したフレームを XAML ページの MediaPlayerElement コントロールに直接表示できます。 これは、MediaSource.CreateFromMediaFrameSource を使用して MediaPlayerElement に関連付けられている MediaPlayer によって直接使用できる MediaSource オブジェクトを作成することによって実現されます。 MediaPlayerMediaPlayerElementの操作に関する詳細情報は、「MediaPlayerでオーディオとビデオを再生する」を参照してください。

次のコード例は、前面カメラと背面カメラのフレームを XAML ページに同時に表示する簡単な実装を示しています。

まず、2 つの MediaPlayerElement コントロールを XAML ページに追加します。

<MediaPlayerElement x:Name="mediaPlayerElement1" Width="320" Height="240"/>
<MediaPlayerElement x:Name="mediaPlayerElement2" Width="320" Height="240"/>

次に、この記事の前のセクションで示した手法を使用して、フロント パネルとバック パネルのカラー カメラ用の MediaFrameSourceInfo オブジェクトを含む MediaFrameSourceGroup を選択します。 MediaPlayer は、深度データや赤外線データなどの非カラー形式のフレームをカラー データに自動的に変換しないことに注意してください。 他の種類のセンサーを使用すると、予期しない結果が発生する可能性があります。

var allGroups = await MediaFrameSourceGroup.FindAllAsync();
var eligibleGroups = allGroups.Select(g => new
{
    Group = g,

    // For each source kind, find the source which offers that kind of media frame,
    // or null if there is no such source.
    SourceInfos = new MediaFrameSourceInfo[]
    {
        g.SourceInfos.FirstOrDefault(info => info.DeviceInformation?.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front
            && info.SourceKind == MediaFrameSourceKind.Color),
        g.SourceInfos.FirstOrDefault(info => info.DeviceInformation?.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Back
            && info.SourceKind == MediaFrameSourceKind.Color)
    }
}).Where(g => g.SourceInfos.Any(info => info != null)).ToList();

if (eligibleGroups.Count == 0)
{
    System.Diagnostics.Debug.WriteLine("No source group with front and back-facing camera found.");
    return;
}

var selectedGroupIndex = 0; // Select the first eligible group
MediaFrameSourceGroup selectedGroup = eligibleGroups[selectedGroupIndex].Group;
MediaFrameSourceInfo frontSourceInfo = selectedGroup.SourceInfos[0];
MediaFrameSourceInfo backSourceInfo = selectedGroup.SourceInfos[1];

選択した MediaFrameSourceGroup を使用するように MediaCapture オブジェクトを初期化します。

m_mediaCapture = new MediaCapture();

var settings = new MediaCaptureInitializationSettings()
{
    SourceGroup = selectedGroup,
    SharingMode = MediaCaptureSharingMode.ExclusiveControl,
    MemoryPreference = MediaCaptureMemoryPreference.Cpu,
    StreamingCaptureMode = StreamingCaptureMode.Video
};
try
{
    await m_mediaCapture.InitializeAsync(settings);
}
catch (Exception ex)
{
    System.Diagnostics.Debug.WriteLine("MediaCapture initialization failed: " + ex.Message);
    return;
}

最後に、MediaSource.CreateFromMediaFrameSource を呼び出し、関連する MediaFrameSourceInfo オブジェクトの Id プロパティを使用して、MediaCapture オブジェクトの FrameSources コレクション内のフレーム ソースのいずれかを選択することで、各フレーム ソースの MediaSource を作成します。 新しい MediaPlayer オブジェクトを初期化し、MediaPlayerElement に割り当てるには SetMediaPlayerを呼び出します。 次に、Source プロパティを、新しく作成した MediaSource オブジェクトに設定します。

var frameMediaSource1 = MediaSource.CreateFromMediaFrameSource(m_mediaCapture.FrameSources[frontSourceInfo.Id]);
mediaPlayerElement1.SetMediaPlayer(new Windows.Media.Playback.MediaPlayer());
mediaPlayerElement1.MediaPlayer.Source = frameMediaSource1;
mediaPlayerElement1.AutoPlay = true;

var frameMediaSource2 = MediaSource.CreateFromMediaFrameSource(m_mediaCapture.FrameSources[backSourceInfo.Id]);
mediaPlayerElement2.SetMediaPlayer(new Windows.Media.Playback.MediaPlayer());
mediaPlayerElement2.MediaPlayer.Source = frameMediaSource2;
mediaPlayerElement2.AutoPlay = true;

ビデオ プロファイルを使用してフレーム ソースを選択する

MediaCaptureVideoProfile オブジェクトによって表されるカメラ プロファイルは、フレーム レート、解像度、HDR キャプチャなどの高度な機能など、特定のキャプチャ デバイスが提供する機能のセットを表します。 キャプチャ デバイスは複数のプロファイルをサポートする場合があり、キャプチャ シナリオに合わせて最適化されたプロファイルを選択できます。 Windows 10 バージョン 1803 以降では、 MediaCaptureVideoProfile を使用して、MediaCapture オブジェクトを初期化する前に、特定の機能を持つ メディア フレーム ソースを選択できます。 次のコード例では、ワイド カラー ガマット (WCG) で HDR をサポートするビデオ プロファイルを検索し、選択したデバイスとプロファイルを使用するように MediaCapture を初期化するために使用できる MediaCaptureInitializationSettings オブジェクトを返します。

まず、MediaFrameSourceGroup.FindAllAsync を呼び出して、現在のデバイスで使用可能なすべてのメディア フレーム ソース グループの一覧を取得します。 各ソース グループをループし、MediaCapture.FindKnownVideoProfiles 呼び出して、指定されたプロファイルをサポートする現在のソース グループのすべてのビデオ プロファイルの一覧を取得します (この場合は HDR と WCG 写真)。 条件を満たすプロファイルが見つかった場合は、新しい MediaCaptureInitializationSettings オブジェクトを作成し、VideoProfile を選択プロファイルに設定し、VideoDeviceId を現在のメディア フレーム ソース グループの Id プロパティに設定します。

IReadOnlyList<MediaFrameSourceGroup> sourceGroups = await MediaFrameSourceGroup.FindAllAsync();
MediaCaptureInitializationSettings settings = null;

foreach (MediaFrameSourceGroup sourceGroup in sourceGroups)
{
    // Find a device that support AdvancedColorPhoto
    IReadOnlyList<MediaCaptureVideoProfile> profileList = MediaCapture.FindKnownVideoProfiles(
                                  sourceGroup.Id,
                                  KnownVideoProfile.HdrWithWcgPhoto);

    if (profileList.Count > 0)
    {
        settings = new MediaCaptureInitializationSettings();
        settings.VideoProfile = profileList[0];
        settings.VideoDeviceId = sourceGroup.Id;
        break;
    }
}

カメラ プロファイルの使用方法の詳細については、「カメラ プロファイル」を参照してください。