Memproses bingkai media dengan MediaFrameReader

Artikel ini menunjukkan cara menggunakan MediaFrameReader dengan MediaCapture untuk mendapatkan bingkai media dari satu atau beberapa sumber yang tersedia, termasuk kamera warna, kedalaman, dan inframerah, perangkat audio, atau bahkan sumber bingkai kustom seperti yang menghasilkan bingkai pelacakan kerangka. Fitur ini dirancang untuk digunakan oleh aplikasi yang melakukan pemrosesan bingkai media secara real time, seperti realitas tertambah dan aplikasi kamera yang sadar kedalaman.

Jika Anda tertarik untuk hanya mengambil video atau foto, seperti aplikasi fotografi biasa, maka Anda mungkin ingin menggunakan salah satu teknik penangkapan lainnya yang didukung oleh MediaCapture. Untuk daftar teknik dan artikel penangkapan media yang tersedia yang menunjukkan cara menggunakannya, lihat Kamera.

Catatan

Fitur yang dibahas dalam artikel ini hanya tersedia dimulai dengan Windows 10, versi 1607.

Catatan

Ada sampel aplikasi Universal Windows yang menunjukkan penggunaan MediaFrameReader untuk menampilkan bingkai dari sumber bingkai yang berbeda, termasuk warna, kedalaman, dan camreas inframerah. Untuk informasi selengkapnya, lihat Sampel bingkai kamera.

Catatan

Sekumpulan API baru untuk menggunakan MediaFrameReader dengan data audio diperkenalkan di Windows 10, versi 1803. Untuk informasi selengkapnya, lihat Memproses bingkai audio dengan MediaFrameReader.

Menyiapkan proyek Anda

Seperti halnya aplikasi apa pun yang menggunakan MediaCapture, Anda harus menyatakan bahwa aplikasi Anda menggunakan kemampuan webcam sebelum mencoba mengakses perangkat kamera apa pun. Jika aplikasi Anda akan mengambil dari perangkat audio, Anda juga harus mendeklarasikan kemampuan perangkat mikrofon .

Menambahkan kemampuan ke manifes aplikasi

  1. Di Microsoft Visual Studio, di Penjelajah Solusi, buka perancang untuk manifes aplikasi dengan mengklik dua kali item package.appxmanifest .
  2. Pilih tab Kemampuan.
  3. Centang kotak untuk Webcam dan kotak untuk Mikrofon.
  4. Untuk akses ke pustaka Gambar dan Video, centang kotak untuk Pustaka Gambar dan kotak untuk Pustaka Video.

Contoh kode dalam artikel ini menggunakan API dari namespace berikut, selain yang disertakan oleh templat proyek default.

using Windows.Media.Capture.Frames;
using Windows.Devices.Enumeration;
using Windows.Media.Capture;
using Windows.UI.Xaml.Media.Imaging;
using Windows.Media.MediaProperties;
using Windows.Graphics.Imaging;
using System.Threading;
using Windows.UI.Core;
using System.Threading.Tasks;
using Windows.Media.Core;
using System.Diagnostics;
using Windows.Media;
using Windows.Media.Devices;
using Windows.Media.Audio;

Pilih sumber bingkai dan grup sumber bingkai

Banyak aplikasi yang memproses bingkai media perlu mendapatkan bingkai dari beberapa sumber sekaligus, seperti kamera warna dan kedalaman perangkat. Objek MediaFrameSourceGroup mewakili sekumpulan sumber bingkai media yang dapat digunakan secara bersamaan. Panggil metode statis MediaFrameSourceGroup.FindAllAsync untuk mendapatkan daftar semua grup sumber bingkai yang didukung oleh perangkat saat ini.

var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();

Anda juga dapat membuat DeviceWatcher menggunakan DeviceInformation.CreateWatcher dan nilai yang dikembalikan dari MediaFrameSourceGroup.GetDeviceSelector untuk menerima pemberitahuan saat grup sumber bingkai yang tersedia pada perangkat berubah, seperti saat kamera eksternal dicolokkan. Untuk informasi selengkapnya, lihat Menghitung perangkat.

MediaFrameSourceGroup memiliki kumpulan objek MediaFrameSourceInfo yang menjelaskan sumber bingkai yang disertakan dalam grup. Setelah mengambil grup sumber bingkai yang tersedia di perangkat, Anda dapat memilih grup yang mengekspos sumber bingkai yang Anda minati.

Contoh berikut menunjukkan cara paling sederhana untuk memilih grup sumber bingkai. Kode ini hanya mengulangi semua grup yang tersedia dan kemudian mengulang setiap item dalam koleksi SourceInfos . Setiap MediaFrameSourceInfo diperiksa untuk melihat apakah mediaFrameSourceInfo mendukung fitur yang kami cari. Dalam hal ini, properti MediaStreamType diperiksa untuk nilai VideoPreview, yang berarti perangkat menyediakan aliran pratinjau video, dan properti SourceKind diperiksa untuk nilai Warna, menunjukkan bahwa sumber menyediakan bingkai warna.

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;
    }
}

Metode mengidentifikasi grup sumber bingkai dan sumber bingkai yang diinginkan ini berfungsi untuk kasus sederhana, tetapi jika Anda ingin memilih sumber bingkai berdasarkan kriteria yang lebih kompleks, itu dapat dengan cepat menjadi rumit. Metode lain adalah menggunakan sintaks Linq dan objek anonim untuk membuat pilihan. Contoh berikut menggunakan metode Pilih ekstensi untuk mengubah objek MediaFrameSourceGroup dalam daftar frameSourceGroups menjadi objek anonim dengan dua bidang: sourceGroup, yang mewakili grup itu sendiri, dan colorSourceInfo, yang mewakili sumber bingkai warna dalam grup. Bidang colorSourceInfo diatur ke hasil FirstOrDefault, yang memilih objek pertama yang predikat yang disediakan diselesaikan ke true. Dalam hal ini, predikatnya benar jika jenis stream adalah VideoPreview, jenis sumbernya adalah Warna, dan jika kamera berada di panel depan perangkat.

Dari daftar objek anonim yang dikembalikan dari kueri yang dijelaskan di atas, metode Di mana ekstensi digunakan untuk memilih hanya objek di mana bidang colorSourceInfo tidak null. Terakhir, FirstOrDefault dipanggil untuk memilih item pertama dalam daftar.

Sekarang Anda dapat menggunakan bidang objek yang dipilih untuk mendapatkan referensi ke MediaFrameSourceGroup yang dipilih dan objek MediaFrameSourceInfo yang mewakili kamera warna. Ini akan digunakan nanti untuk menginisialisasi objek MediaCapture dan membuat MediaFrameReader untuk sumber yang dipilih. Terakhir, Anda harus menguji untuk melihat apakah grup sumber null, yang berarti perangkat saat ini tidak memiliki sumber pengambilan yang Anda minta.

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;
}

Contoh berikut menggunakan teknik serupa seperti yang dijelaskan di atas untuk memilih grup sumber yang berisi kamera warna, kedalaman, dan inframerah.

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];

Catatan

Dimulai dengan Windows 10, versi 1803, Anda dapat menggunakan kelas MediaCaptureVideoProfile untuk memilih sumber bingkai media dengan serangkaian kemampuan yang diinginkan. Untuk informasi selengkapnya, lihat bagian Menggunakan profil video untuk memilih sumber bingkai nanti di artikel ini.

Menginisialisasi objek MediaCapture untuk menggunakan grup sumber bingkai yang dipilih

Langkah selanjutnya adalah menginisialisasi objek MediaCapture untuk menggunakan grup sumber bingkai yang Anda pilih di langkah sebelumnya.

Objek MediaCapture biasanya digunakan dari beberapa lokasi dalam aplikasi Anda, jadi Anda harus mendeklarasikan variabel anggota kelas untuk menahannya.

MediaCapture mediaCapture;

Buat instans objek MediaCapture dengan memanggil konstruktor. Selanjutnya, buat objek MediaCaptureInitializationSettings yang akan digunakan untuk menginisialisasi objek MediaCapture . Dalam contoh ini, pengaturan berikut digunakan:

  • SourceGroup - Ini memberi tahu sistem grup sumber mana yang akan Anda gunakan untuk mendapatkan bingkai. Ingatlah bahwa grup sumber mendefinisikan sekumpulan sumber bingkai media yang dapat digunakan secara bersamaan.
  • SharingMode - Ini memberi tahu sistem apakah Anda memerlukan kontrol eksklusif atas perangkat sumber tangkapan. Jika Anda mengatur ini ke ExclusiveControl, itu berarti Anda dapat mengubah pengaturan perangkat tangkapan, seperti format bingkai yang dihasilkannya, tetapi ini berarti bahwa jika aplikasi lain sudah memiliki kontrol eksklusif, aplikasi Anda akan gagal saat mencoba menginisialisasi perangkat pengambilan media. Jika Anda mengatur ini ke SharedReadOnly, Anda dapat menerima bingkai dari sumber bingkai meskipun sedang digunakan oleh aplikasi lain, tetapi Anda tidak dapat mengubah pengaturan untuk perangkat.
  • MemoryPreference - Jika Anda menentukan CPU, sistem akan menggunakan memori CPU yang menjamin bahwa ketika bingkai tiba, mereka akan tersedia sebagai objek SoftwareBitmap . Jika Anda menentukan Otomatis, sistem akan secara dinamis memilih lokasi memori optimal untuk menyimpan bingkai. Jika sistem memilih untuk menggunakan memori GPU, bingkai media akan tiba sebagai objek IDirect3DSurface dan bukan sebagai SoftwareBitmap.
  • StreamingCaptureMode - Atur ini ke Video untuk menunjukkan bahwa audio tidak perlu di-streaming.

Panggil InitializeAsync untuk menginisialisasi MediaCapture dengan pengaturan yang Anda inginkan. Pastikan untuk memanggil ini dalam blok coba jika inisialisasi gagal.

mediaCapture = new MediaCapture();

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

Mengatur format yang disukai untuk sumber bingkai

Untuk mengatur format pilihan untuk sumber bingkai, Anda perlu mendapatkan objek MediaFrameSource yang mewakili sumbernya. Anda mendapatkan objek ini dengan mengakses kamus Bingkai dari objek MediaCapture yang diinisialisasi, menentukan pengidentifikasi sumber bingkai yang ingin Anda gunakan. Inilah sebabnya mengapa kami menyimpan objek MediaFrameSourceInfo ketika kami memilih grup sumber bingkai.

Properti MediaFrameSource.SupportedFormats berisi daftar objek MediaFrameFormat yang menjelaskan format yang didukung untuk sumber bingkai. Gunakan metode ekstensi Where Linq untuk memilih format berdasarkan properti yang diinginkan. Dalam contoh ini, format dipilih yang memiliki lebar 1080 piksel dan dapat menyediakan bingkai dalam format RGB 32-bit. Metode ekstensi FirstOrDefault memilih entri pertama dalam daftar. Jika format yang dipilih null, format yang diminta tidak didukung oleh sumber bingkai. Jika format didukung, Anda dapat meminta agar sumber menggunakan format ini dengan memanggil SetFormatAsync.

var colorFrameSource = 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);

Membuat pembaca bingkai untuk sumber bingkai

Untuk menerima bingkai untuk sumber bingkai media, gunakan MediaFrameReader.

MediaFrameReader mediaFrameReader;

Buat instans pembaca bingkai dengan memanggil CreateFrameReaderAsync pada objek MediaCapture yang diinisialisasi. Argumen pertama untuk metode ini adalah sumber bingkai tempat Anda ingin menerima bingkai. Anda dapat membuat pembaca bingkai terpisah untuk setiap sumber bingkai yang ingin Anda gunakan. Argumen kedua memberi tahu sistem format output tempat Anda ingin bingkai tiba. Ini dapat menyelamatkan Anda dari keramaian untuk melakukan konversi Anda sendiri ke bingkai saat mereka tiba. Perhatikan bahwa jika Anda menentukan format yang tidak didukung oleh sumber bingkai, pengecualian akan dilemparkan, jadi pastikan nilai ini ada di koleksi SupportedFormats .

Setelah membuat pembaca bingkai, daftarkan handler untuk peristiwa FrameArrived yang dinaikkan setiap kali bingkai baru tersedia dari sumbernya.

Beri tahu sistem untuk mulai membaca bingkai dari sumber dengan memanggil StartAsync.

mediaFrameReader = await mediaCapture.CreateFrameReaderAsync(colorFrameSource, MediaEncodingSubtypes.Argb32);
mediaFrameReader.FrameArrived += ColorFrameReader_FrameArrived;
await mediaFrameReader.StartAsync();

Menangani peristiwa bingkai yang tiba

Peristiwa MediaFrameReader.FrameArrived dinaikkan setiap kali bingkai baru tersedia. Anda dapat memilih untuk memproses setiap bingkai yang tiba atau hanya menggunakan bingkai saat Anda membutuhkannya. Karena pembaca bingkai menaikkan peristiwa pada utasnya sendiri, Anda mungkin perlu menerapkan beberapa logika sinkronisasi untuk memastikan bahwa Anda tidak mencoba mengakses data yang sama dari beberapa utas. Bagian ini memperlihatkan kepada Anda cara menyinkronkan bingkai warna gambar ke kontrol gambar di halaman XAML. Skenario ini membahas batasan sinkronisasi tambahan yang mengharuskan semua pembaruan kontrol XAML dilakukan pada utas UI.

Langkah pertama dalam menampilkan bingkai di XAML adalah membuat kontrol Gambar.

<Image x:Name="imageElement" Width="320" Height="240" />

Dalam kode Anda di belakang halaman, deklarasikan variabel anggota kelas jenis SoftwareBitmap yang akan digunakan sebagai buffer belakang tempat semua gambar masuk akan disalin. Perhatikan bahwa data gambar itu sendiri tidak disalin, hanya referensi objek. Selain itu, nyatakan boolean untuk melacak apakah operasi UI kami saat ini berjalan.

private SoftwareBitmap backBuffer;
private bool taskRunning = false;

Karena bingkai akan tiba sebagai objek SoftwareBitmap , Anda perlu membuat objek SoftwareBitmapSource yang memungkinkan Anda menggunakan SoftwareBitmap sebagai sumber untuk Kontrol XAML. Anda harus mengatur sumber gambar di suatu tempat dalam kode Anda sebelum memulai pembaca bingkai.

imageElement.Source = new SoftwareBitmapSource();

Sekarang saatnya untuk mengimplementasikan penanganan aktivitas FrameArrived . Ketika handler dipanggil, parameter pengirim berisi referensi ke objek MediaFrameReader yang menaikkan peristiwa. Panggil TryAcquireLatestFrame pada objek ini untuk mencoba mendapatkan bingkai terbaru. Seperti namanya, TryAcquireLatestFrame mungkin tidak berhasil mengembalikan bingkai. Jadi, ketika Anda mengakses properti VideoMediaFrame dan kemudian SoftwareBitmap, pastikan untuk menguji null. Dalam contoh ini operator kondional null ? digunakan untuk mengakses SoftwareBitmap dan kemudian objek yang diambil diperiksa null.

Kontrol Gambar hanya dapat menampilkan gambar dalam format BRGA8 dengan alfa yang telah dikalikan sebelumnya atau tanpa alfa. Jika bingkai yang tiba tidak dalam format tersebut, metode statis Konversi digunakan untuk mengonversi bitmap perangkat lunak ke format yang benar.

Selanjutnya, metode Interlocked.Exchange digunakan untuk menukar referensi ke bitmap yang tiba dengan bitmap backbuffer. Metode ini menukar referensi ini dalam operasi atomik yang aman untuk utas. Setelah bertukar, gambar backbuffer lama, sekarang dalam variabel softwareBitmap dibuang untuk membersihkan sumber dayanya.

Selanjutnya, CoreDispatcher yang terkait dengan elemen Gambar digunakan untuk membuat tugas yang akan berjalan pada utas UI dengan memanggil RunAsync. Karena tugas asinkron akan dilakukan dalam tugas, ekspresi lambda yang diteruskan ke RunAsync dinyatakan dengan kata kunci asinkron .

Dalam tugas, variabel _taskRunning diperiksa untuk memastikan bahwa hanya satu instans tugas yang berjalan pada satu waktu. Jika tugas belum berjalan, _taskRunning diatur ke true untuk mencegah tugas berjalan lagi. Dalam perulangan sementara , Interlocked.Exchange dipanggil untuk menyalin dari backbuffer ke SoftwareBitmap sementara sampai gambar backbuffer null. Untuk setiap kali bitmap sementara diisi, properti SumberGambar ditransmisikan ke SoftwareBitmapSource, lalu SetBitmapAsync dipanggil untuk mengatur sumber gambar.

Terakhir, variabel _taskRunning diatur kembali ke false sehingga tugas dapat dijalankan lagi lain kali handler dipanggil.

Catatan

Jika Anda mengakses objek SoftwareBitmap atau Direct3DSurface yang disediakan oleh properti VideoMediaFrame dari MediaFrameReference, sistem membuat referensi yang kuat ke objek ini, yang berarti bahwa objek tersebut tidak akan dibuang saat Anda memanggil Buang pada MediaFrameReference yang berisi. Anda harus secara eksplisit memanggil metode BuangSoftwareBitmap atau Direct3DSurface secara langsung agar objek segera dibuang. Jika tidak, pengumpul sampah pada akhirnya akan membebaskan memori untuk objek-objek ini, tetapi Anda tidak dapat tahu kapan ini akan terjadi, dan jika jumlah bitmap atau permukaan yang dialokasikan melebihi jumlah maksimum yang diizinkan oleh sistem, aliran bingkai baru akan berhenti. Anda dapat menyalin bingkai yang diambil, menggunakan metode SoftwareBitmap.Copy misalnya, lalu merilis bingkai asli untuk mengatasi batasan ini. Selain itu, jika Anda membuat MediaFrameReader menggunakan overload CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype, Windows.Graphics.Imaging.BitmapSize outputSize) atau CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype), bingkai yang dikembalikan adalah salinan data bingkai asli sehingga tidak menyebabkan akuisisi bingkai berhenti saat dipertahankan.

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 = 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 backBuffer, null)) != null)
                {
                    var imageSource = (SoftwareBitmapSource)imageElement.Source;
                    await imageSource.SetBitmapAsync(latestBitmap);
                    latestBitmap.Dispose();
                }

                taskRunning = false;
            });
    }

    mediaFrameReference.Dispose();
}

Membersihkan sumber daya

Ketika Anda selesai membaca bingkai, pastikan untuk menghentikan pembaca bingkai media dengan memanggil StopAsync, membatalkan pendaftaran handler FrameArrived , dan membuang objek MediaCapture .

await mediaFrameReader.StopAsync();
mediaFrameReader.FrameArrived -= ColorFrameReader_FrameArrived;
mediaCapture.Dispose();
mediaCapture = null;

Untuk informasi selengkapnya tentang membersihkan objek pengambilan media saat aplikasi Anda ditangguhkan, lihat Menampilkan pratinjau kamera.

Kelas pembantu FrameRenderer

Sampel bingkai Universal Windows Camera menyediakan kelas pembantu yang memudahkan untuk menampilkan bingkai dari sumber warna, inframerah, dan kedalaman di aplikasi Anda. Biasanya, Anda ingin melakukan sesuatu lebih dengan data kedalaman dan inframerah daripada hanya menampilkannya ke layar, tetapi kelas pembantu ini adalah alat yang bermanfaat untuk menunjukkan fitur pembaca bingkai dan untuk men-debug implementasi pembaca bingkai Anda sendiri.

Kelas pembantu FrameRenderer mengimplementasikan metode berikut.

  • Konstruktor FrameRenderer - Konstruktor menginisialisasi kelas pembantu untuk menggunakan elemen Gambar XAML yang Anda berikan untuk menampilkan bingkai media.
  • ProcessFrame - Metode ini menampilkan bingkai media, yang diwakili oleh MediaFrameReference, dalam elemen Gambar yang Anda berikan ke konstruktor. Anda biasanya harus memanggil metode ini dari penanganan aktivitas FrameArrived Anda, melewati bingkai yang dikembalikan oleh TryAcquireLatestFrame.
  • ConvertToDisplayableImage - Metode ini memeriksa format bingkai media dan, jika perlu, mengonversinya ke format yang dapat ditampilkan. Untuk gambar warna, ini berarti memastikan bahwa format warna adalah BGRA8 dan bahwa mode alfa bitmap telah ditentukan sebelumnya. Untuk bingkai kedalaman atau inframerah, setiap pemindaian diproses untuk mengonversi nilai kedalaman atau inframerah ke gradien psuedocolor, menggunakan kelas PsuedoColorHelper yang juga disertakan dalam sampel dan tercantum di bawah ini.

Catatan

Untuk melakukan manipulasi piksel pada gambar SoftwareBitmap , Anda harus mengakses buffer memori asli. Untuk melakukan ini, Anda harus menggunakan antarmuka COM IMemoryBufferByteAccess yang disertakan dalam daftar kode di bawah ini dan Anda harus memperbarui properti proyek Anda untuk memungkinkan kompilasi kode yang tidak aman. Untuk informasi selengkapnya, lihat Membuat, mengedit, dan menyimpan gambar bitmap.

[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
    void GetBuffer(out byte* buffer, out uint capacity);
}

class FrameRenderer
{
    private Image _imageElement;
    private SoftwareBitmap _backBuffer;
    private bool _taskRunning = false;

    public FrameRenderer(Image imageElement)
    {
        _imageElement = imageElement;
        _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 _backBuffer and trigger UI thread to render it
            softwareBitmap = Interlocked.Exchange(ref _backBuffer, softwareBitmap);

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

            // Changes to xaml ImageElement must happen in UI thread through Dispatcher
            var task = _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 _backBuffer, null)) != null)
                    {
                        var imageSource = (SoftwareBitmapSource)_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 _backBuffer and trigger UI thread to render it
            softwareBitmap = Interlocked.Exchange(ref _backBuffer, softwareBitmap);

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

            // Changes to xaml ImageElement must happen in UI thread through Dispatcher
            var task = _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 _backBuffer, null)) != null)
                    {
                        var imageSource = (SoftwareBitmapSource)_imageElement.Source;
                        await imageSource.SetBitmapAsync(latestBitmap);
                        latestBitmap.Dispose();
                    }

                    _taskRunning = false;
                });
        }
    }
}

Gunakan MultiSourceMediaFrameReader untuk mendapatkan bingkai corellasi waktu dari berbagai sumber

Dimulai dengan Windows 10, versi 1607, Anda dapat menggunakan MultiSourceMediaFrameReader untuk menerima bingkai inti waktu dari berbagai sumber. API ini memudahkan untuk melakukan pemrosesan yang memerlukan bingkai dari beberapa sumber yang diambil dalam jarak temporal yang dekat, seperti menggunakan kelas DepthCorrelatedCoordinateMapper . Salah satu batasan penggunaan metode baru ini adalah bahwa peristiwa yang tiba di bingkai hanya dinaikkan pada tingkat sumber tangkapan paling lambat. Bingkai tambahan dari sumber yang lebih cepat akan dihilangkan. Selain itu, karena sistem mengharapkan bingkai tiba dari sumber yang berbeda pada tingkat yang berbeda, sistem tidak secara otomatis mengenali apakah sumber telah berhenti menghasilkan bingkai sama sekali. Contoh kode di bagian ini menunjukkan cara menggunakan peristiwa untuk membuat logika batas waktu Anda sendiri yang dipanggil jika bingkai berkorelasi tidak tiba dalam batas waktu yang ditentukan aplikasi.

Langkah-langkah untuk menggunakan MultiSourceMediaFrameReader mirip dengan langkah-langkah untuk menggunakan MediaFrameReader yang dijelaskan sebelumnya dalam artikel ini. Contoh ini akan menggunakan sumber warna dan sumber kedalaman. Deklarasikan beberapa variabel string untuk menyimpan ID sumber bingkai media yang akan digunakan untuk memilih bingkai dari setiap sumber. Selanjutnya, deklarasikan ManualResetEventSlim, CancellationTokenSource, dan EventHandler yang akan digunakan untuk menerapkan logika batas waktu untuk contoh.

private MultiSourceMediaFrameReader _multiFrameReader = null;
private string _colorSourceId = null;
private string _depthSourceId = null;


private readonly ManualResetEventSlim _frameReceived = new ManualResetEventSlim(false);
private readonly CancellationTokenSource _tokenSource = new CancellationTokenSource();
public event EventHandler CorrelationFailed;

Menggunakan teknik yang dijelaskan sebelumnya dalam artikel ini, kueri untuk MediaFrameSourceGroup yang menyertakan sumber warna dan kedalaman yang diperlukan untuk skenario contoh ini. Setelah memilih grup sumber bingkai yang diinginkan, dapatkan MediaFrameSourceInfo untuk setiap sumber bingkai.

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];

Buat dan inisialisasi objek MediaCapture , melewati grup sumber bingkai yang dipilih dalam pengaturan inisialisasi.

mediaCapture = new MediaCapture();

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

await mediaCapture.InitializeAsync(settings);

Setelah menginisialisasi objek MediaCapture , ambil objek MediaFrameSource untuk kamera warna dan kedalaman. Simpan ID untuk setiap sumber sehingga Anda dapat memilih bingkai yang tiba untuk sumber yang sesuai.

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

MediaFrameSource depthSource =
    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;
}

_colorSourceId = colorSource.Info.Id;
_depthSourceId = depthSource.Info.Id;

Buat dan inisialisasi MultiSourceMediaFrameReader dengan memanggil CreateMultiSourceFrameReaderAsync dan meneruskan array sumber bingkai yang akan digunakan pembaca. Daftarkan penanganan aktivitas untuk peristiwa FrameArrived . Contoh ini membuat instans kelas pembantu FrameRenderer , yang dijelaskan sebelumnya dalam artikel ini, untuk merender bingkai ke kontrol Gambar . Mulai pembaca bingkai dengan memanggil StartAsync.

Daftarkan penanganan aktivitas untuk peristiwa CorellationFailed yang dideklarasikan sebelumnya dalam contoh. Kami akan memberi sinyal peristiwa ini jika salah satu sumber bingkai media yang digunakan berhenti menghasilkan bingkai. Terakhir, panggil Task.Run untuk memanggil metode pembantu waktu habis, NotifyAboutCorrelationFailure, pada utas terpisah. Implementasi metode ini ditampilkan nanti dalam artikel ini.

_multiFrameReader = await mediaCapture.CreateMultiSourceFrameReaderAsync(
    new[] { colorSource, depthSource });

_multiFrameReader.FrameArrived += MultiFrameReader_FrameArrived;

_frameRenderer = new FrameRenderer(imageElement);

MultiSourceMediaFrameReaderStartStatus startStatus =
    await _multiFrameReader.StartAsync();

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

this.CorrelationFailed += MainPage_CorrelationFailed;
Task.Run(() => NotifyAboutCorrelationFailure(_tokenSource.Token));

Peristiwa FrameArrived dimunculkan setiap kali bingkai baru tersedia dari semua sumber bingkai media yang dikelola oleh MultiSourceMediaFrameReader. Ini berarti bahwa peristiwa akan diangkat pada irama sumber media yang paling lambat. Jika satu sumber menghasilkan beberapa bingkai pada saat sumber yang lebih lambat menghasilkan satu bingkai, bingkai tambahan dari sumber cepat akan dihilangkan.

Dapatkan MultiSourceMediaFrameReference yang terkait dengan peristiwa dengan memanggil TryAcquireLatestFrame. Dapatkan MediaFrameReference yang terkait dengan setiap sumber bingkai media dengan memanggil TryGetFrameReferenceBySourceId, meneruskan string ID yang disimpan saat pembaca bingkai diinisialisasi.

Panggil metode Set objek ManualResetEventSlim untuk memberi sinyal bahwa bingkai telah tiba. Kami akan memeriksa peristiwa ini dalam metode NotifyCorrelationFailure yang berjalan di utas terpisah.

Terakhir, lakukan pemrosesan apa pun pada bingkai media yang berkorelasi waktu. Contoh ini hanya menampilkan bingkai dari sumber kedalaman.

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

Metode pembantu NotifyCorrelationFailure dijalankan pada utas terpisah setelah pembaca bingkai dimulai. Dalam metode ini, periksa untuk melihat apakah peristiwa yang diterima bingkai telah disinyalkan. Ingat, di handler FrameArrived , kami mengatur peristiwa ini setiap kali satu set bingkai berkorelasi tiba. Jika peristiwa belum diberi sinyal untuk beberapa periode waktu yang ditentukan aplikasi - 5 detik adalah nilai yang wajar - dan tugas tidak dibatalkan menggunakan CancellationToken, kemungkinan salah satu sumber bingkai media telah berhenti membaca bingkai. Dalam hal ini Anda biasanya ingin mematikan pembaca bingkai, jadi naikkan peristiwa CorrelationFailed yang ditentukan aplikasi. Di handler untuk peristiwa ini, Anda dapat menghentikan pembaca bingkai dan membersihkan sumber daya terkait seperti yang ditunjukkan sebelumnya dalam artikel ini.

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, _frameReceived.WaitHandle }, 5000)
            == WaitHandle.WaitTimeout)
    {
        CorrelationFailed?.Invoke(this, EventArgs.Empty);
    }
}
private async void MainPage_CorrelationFailed(object sender, EventArgs e)
{
    await _multiFrameReader.StopAsync();
    _multiFrameReader.FrameArrived -= MultiFrameReader_FrameArrived;
    mediaCapture.Dispose();
    mediaCapture = null;
}

Gunakan mode akuisisi bingkai buffer untuk mempertahankan urutan bingkai yang diperoleh

Dimulai dengan Windows 10, versi 1709, Anda dapat mengatur properti AcquisitionMode dari MediaFrameReader atau MultiSourceMediaFrameReader ke Buffered untuk mempertahankan urutan bingkai yang diteruskan ke aplikasi Anda dari sumber bingkai.

mediaFrameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Buffered;

Dalam mode akuisisi default, Realtime, jika beberapa bingkai diperoleh dari sumber saat aplikasi Anda masih menangani peristiwa FrameArrived untuk bingkai sebelumnya, sistem akan mengirim bingkai yang paling baru diperoleh aplikasi Anda dan menghilangkan bingkai tambahan yang menunggu di buffer. Ini memberi aplikasi Anda bingkai terbaru yang tersedia setiap saat. Ini biasanya merupakan mode yang paling berguna untuk aplikasi visi komputer realtime.

Dalam mode akuisisi Buffered , sistem akan menyimpan semua bingkai di buffer dan menyediakannya ke aplikasi Anda melalui peristiwa FrameArrived dalam urutan yang diterima. Perhatikan bahwa dalam mode ini, ketika buffer sistem untuk bingkai terisi, sistem akan berhenti memperoleh bingkai baru sampai aplikasi Anda menyelesaikan peristiwa FrameArrived untuk bingkai sebelumnya, membebaskan lebih banyak ruang di buffer.

Menggunakan MediaSource untuk menampilkan bingkai di MediaPlayerElement

Dimulai dengan Windows, versi 1709, Anda dapat menampilkan bingkai yang diperoleh dari MediaFrameReader langsung di kontrol MediaPlayerElement di halaman XAML Anda. Hal ini dicapai dengan menggunakan MediaSource.CreateFromMediaFrameSource untuk membuat objek MediaSource yang dapat digunakan langsung oleh MediaPlayer yang terkait dengan MediaPlayerElement. Untuk informasi terperinci tentang bekerja dengan MediaPlayer dan MediaPlayerElement, lihat Memutar audio dan video dengan MediaPlayer.

Contoh kode berikut menunjukkan implementasi sederhana yang menampilkan bingkai dari kamera depan dan belakang secara bersamaan di halaman XAML.

Pertama, tambahkan dua kontrol MediaPlayerElement ke halaman XAML Anda.

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

Selanjutnya, menggunakan teknik yang ditampilkan di bagian sebelumnya dalam artikel ini, pilih MediaFrameSourceGroup yang berisi objek MediaFrameSourceInfo untuk kamera warna pada panel depan dan panel belakang. Perhatikan bahwa MediaPlayer tidak secara otomatis mengonversi bingkai dari format non-warna, seperti data kedalaman atau inframerah, menjadi data warna. Menggunakan jenis sensor lain dapat menghasilkan hasil yang tidak terduga.

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];

Inisialisasi objek MediaCapture untuk menggunakan MediaFrameSourceGroup yang dipilih.

mediaCapture = new MediaCapture();

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

Terakhir, panggil MediaSource.CreateFromMediaFrameSource untuk membuat MediaSource untuk setiap sumber bingkai dengan menggunakan properti Id dari objek MediaFrameSourceInfo terkait untuk memilih salah satu sumber bingkai dalam koleksi FrameSources objek MediaCapture. Inisialisasi objek MediaPlayer baru dan tetapkan ke MediaPlayerElement dengan memanggil SetMediaPlayer. Kemudian atur properti Sumber ke objek MediaSource yang baru dibuat.

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

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

Menggunakan profil video untuk memilih sumber bingkai

Profil kamera, yang diwakili oleh objek MediaCaptureVideoProfile , mewakili serangkaian kemampuan yang disediakan perangkat tangkapan tertentu, seperti kecepatan bingkai, resolusi, atau fitur canggih seperti pengambilan HDR. Perangkat tangkapan dapat mendukung beberapa profil, memungkinkan Anda memilih profil yang dioptimalkan untuk skenario pengambilan Anda. Dimulai dengan Windows 10, versi 1803, Anda dapat menggunakan MediaCaptureVideoProfile untuk memilih sumber bingkai media dengan kemampuan tertentu sebelum menginisialisasi objek MediaCapture. Metode contoh berikut mencari profil video yang mendukung HDR dengan Wide Color Gamut (WCG) dan mengembalikan objek MediaCaptureInitializationSettings yang dapat digunakan untuk menginisialisasi MediaCapture untuk menggunakan perangkat dan profil yang dipilih.

Pertama, panggil MediaFrameSourceGroup.FindAllAsync untuk mendapatkan daftar semua grup sumber bingkai media yang tersedia di perangkat saat ini. Loop melalui setiap grup sumber dan panggil MediaCapture.FindKnownVideoProfiles untuk mendapatkan daftar semua profil video untuk grup sumber saat ini yang mendukung profil yang ditentukan, dalam hal ini HDR dengan foto WCG. Jika profil yang memenuhi kriteria ditemukan, buat objek MediaCaptureInitializationSettings baru dan atur VideoProfile ke profil pilih dan VideoDeviceId ke properti Id dari grup sumber bingkai media saat ini.

public async Task<MediaCaptureInitializationSettings> FindHdrWithWcgPhotoProfile()
{
    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;
        }
    }
    return settings;
}

private void StartDeviceWatcherButton_Click(object sender, RoutedEventArgs e)
{
    var remoteCameraHelper = new RemoteCameraPairingHelper(this.Dispatcher);
}

Untuk informasi selengkapnya tentang menggunakan profil kamera, lihat Profil kamera.