Поделиться через


Обработка кадров мультимедиа с помощью MediaFrameReader

В этой статье показано, как использовать MediaFrameReader с MediaCapture, чтобы получить медиакадры из одного или нескольких доступных источников, включая цветные, глубинные и инфракрасные камеры, аудиоустройства или даже пользовательские источники кадров, такие как те, которые создают кадры отслеживания скелетов. Эта функция предназначена для использования приложениями, выполняющими обработку кадров мультимедиа в режиме реального времени, таких как дополненная реальность и приложения камеры с поддержкой глубины.

Если вы хотите просто записать видео или фотографии, например типичное приложение для фотографии, то вы, вероятно, хотите использовать один из других методов захвата, поддерживаемых MediaCapture. Для списка доступных методов захвата мультимедиа и статей, показывающих, как их использовать, см. раздел Камера.

Замечание

Функции, рассмотренные в этой статье, доступны только начиная с Windows 10 версии 1607.

Замечание

Существует пример универсального приложения Для Windows, демонстрирующий использование MediaFrameReader для отображения кадров из разных источников кадров, включая цвет, глубину и инфракрасные камеры. Дополнительные сведения см. в примере кадров камеры .

Замечание

Новый набор API для использования MediaFrameReader с звуковыми данными были представлены в Windows 10 версии 1803. Дополнительные сведения см. в разделе Обработка звуковых кадров с помощьюMediaFrameReader.

Выберите источники кадров и группы источников кадров

Многие приложения, обрабатывающие кадры мультимедиа, должны одновременно получать кадры из нескольких источников, таких как цвет и камеры глубины устройства. Объект MediaFrameSourceGroup представляет набор источников кадров мультимедиа, которые можно использовать одновременно. Вызовите статический метод MediaFrameSourceGroup.FindAllAsync , чтобы получить список всех групп источников кадров, поддерживаемых текущим устройством.

var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();

Вы также можете создать DeviceWatcher с помощью DeviceInformation.CreateWatcher и значения, возвращаемого из 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;
    }
}

Этот метод определения требуемой исходной группы кадров и источников кадров работает для простых случаев, но если вы хотите выбрать источники кадров на основе более сложных критериев, он может быстро стать громоздким. Другим способом является использование синтаксиса Linq и анонимных объектов для выбора. В следующем примере метод select extension используется для преобразования объектов MediaFrameSourceGroup в списке frameSourceGroups в анонимный объект с двумя полями: sourceGroup, представляющий саму группу и colorSourceInfo, которая представляет источник цветового кадра в группе. Поле colorSourceInfo имеет значение результата FirstOrDefault, который выбирает первый объект, для которого предоставленный предикат разрешает значение true. В этом случае предикат имеет значение true, если тип потока VideoPreview, тип источника цвет, а камера находится на передней панели устройства.

В списке анонимных объектов, возвращаемых из запроса, описанного выше, метод расширения 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 , вызвав конструктор. Затем создайте объект MediaCaptureInitializationSettings , который будет использоваться для инициализации объекта MediaCapture . В этом примере используются следующие параметры:

  • SourceGroup — это указывает системе, какую исходную группу вы будете использовать для получения кадров. Помните, что исходная группа определяет набор источников кадров мультимедиа, которые можно использовать одновременно.
  • SharingMode . Это сообщает системе, требуется ли монопольный контроль над исходными устройствами записи. Если установить для этого значение ExclusiveControl, это позволит изменить параметры устройства захвата, например формат создаваемых кадров. Однако, если другое приложение уже обладает монопольным управлением, ваша программа завершится ошибкой при попытке инициализировать устройство захвата. Если для этого задано значение SharedReadOnly, вы можете получать кадры из источников кадров, даже если они используются другим приложением, но не удается изменить параметры для устройств.
  • MemoryPreference . Если указать ЦП, система будет использовать память ЦП, которая гарантирует, что при поступлении кадров они будут доступны как объекты SoftwareBitmap. При указании Autoсистема динамически выбирает оптимальное местоположение памяти для хранения кадров. Если система решит использовать память GPU, кадры мультимедиа будут поступать как объект IDirect3DSurface , а не как SoftwareBitmap.
  • StreamingCaptureMode - Установите это значение на Видео, чтобы указать, что звук не требует потоковой передачи.

Вызовите 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 , указав идентификатор источника кадра, который вы хотите использовать. Именно поэтому мы сохранили объект 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;

Создайте экземпляр средства чтения кадров путем вызова CreateFrameReaderAsync на инициализированном объекте MediaCapture . Первым аргументом этого метода является источник кадра, из которого требуется получать кадры. Вы можете создать отдельное средство чтения кадров для каждого источника кадра, который вы хотите использовать. Аргумент второй определяет системе выходной формат, в котором должны поступать кадры. Это может избавить вас от необходимости выполнять собственные преобразования в кадры по мере их поступления. Обратите внимание, что при указании формата, который не поддерживается источником кадра, создается исключение, поэтому убедитесь, что это значение находится в коллекции 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 в потоке пользовательского интерфейса.

Первым шагом в отображении кадров в XAML является создание элемента управления Image.

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

На странице программной части объявите переменную-член класса типа SoftwareBitmap, которая будет использоваться в качестве заднего буфера, в который будут скопированы все входящие изображения. Обратите внимание, что сами данные изображения не копируются, а только ссылки на объекты. Кроме того, объявите логическую переменную, чтобы отслеживать, выполняется ли в данный момент операция пользовательского интерфейса.

private SoftwareBitmap backBuffer;
private bool taskRunning = false;

Так как кадры будут поступать как объекты SoftwareBitmap, необходимо создать объект SoftwareBitmapSource, который позволяет использовать SoftwareBitmap в качестве источника элемента управления XAML. Перед запуском средства чтения кадров необходимо задать источник изображения где-то в коде.

iFrameReaderImageControl.Source = new SoftwareBitmapSource();

Теперь пришло время реализовать обработчик событий FrameArrived . При вызове обработчика параметр отправителя содержит ссылку на объект MediaFrameReader , который вызвал событие. Вызов TryAcquireLatestFrame в этом объекте, чтобы попытаться получить последний кадр. Как подразумевает имя, TryAcquireLatestFrame может не вернуть кадр. Таким образом, когда вы обращаетесь к свойствам VideoMediaFrame, а затем SoftwareBitmap, обязательно проверьте, что их значение не является null. В этом примере условный оператор NULL ? используется для доступа к SoftwareBitmap , а затем извлекаемый объект проверяется на значение NULL.

Элемент управления изображения может отображать только изображения в формате BRGA8 с предварительно умноженным альфа-каналом или без альфа-канала. Если поступающий кадр не имеет этого формата, статический метод Convert используется для преобразования растрового изображения программного обеспечения в правильный формат.

Затем метод Interlocked.Exchange используется для замены ссылки на поступающий растровый рисунок с растровым изображением backbuffer. Этот метод выполняет атомарную операцию, при которой ссылки заменяются и соблюдается потокобезопасность. После переключения старое изображение backbuffer, теперь находящееся в переменной softwareBitmap, удаляется для освобождения ресурсов.

Затем CoreDispatcher, связанный с элементом Image, используется для создания задачи, которая будет выполняться в потоке интерфейса пользователя путем вызова RunAsync. Поскольку асинхронные задачи будут выполняться внутри задачи, лямбда-выражение, переданное в RunAsync, объявляется с ключевым словом async.

В задаче проверяется переменная _taskRunning , чтобы убедиться, что одновременно выполняется только один экземпляр задачи. Если задача еще не запущена, _taskRunning задано значение true, чтобы предотвратить повторную работу задачи. В во время цикла вызывается Interlocked.Exchange для копирования из буфера обмена во временный SoftwareBitmap, пока изображение в буфере обмена не станет null. Для каждого заполнения временной растровой карты свойство источника объекта Image приводится к типу SoftwareBitmapSource, а затем вызывается метод SetBitmapAsync для задания источника изображения.

Наконец, переменная _taskRunning возвращает значение false, чтобы задача была запущена снова при следующем вызове обработчика.

Замечание

Если вы обращаетесь к объектам SoftwareBitmap или Direct3DSurface, предоставляемым свойством VideoMediaFrame из MediaFrameReference, система создает жесткую ссылку на эти объекты, что означает, что они не будут удалены при вызове Dispose на содержащем MediaFrameReference. Необходимо явно вызвать метод DisposeSoftwareBitmap или Direct3DSurface непосредственно для немедленного удаления объектов. В противном случае сборщик мусора в конечном итоге освобождает память для этих объектов, но вы не можете знать, когда это произойдет, и если количество выделенных растровых карт или поверхностей превышает максимально допустимое системой количество, поток новых кадров остановится. Например, можно скопировать извлеченные кадры с помощью метода SoftwareBitmap.Copy , а затем освободить исходные кадры, чтобы преодолеть это ограничение. Кроме того, при создании MediaFrameReader с помощью перегрузки CreateFrameReaderAsync(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.

Вспомогательный класс FrameRenderer

В этом разделе представлен полный список кода для вспомогательного класса, который упрощает отображение кадров из цветов, инфракрасных и глубинных источников в приложении. Как правило, вы хотите сделать что-то больше с глубиной и инфракрасными данными, чем просто отобразить его на экране, но этот вспомогательный класс является полезным инструментом для демонстрации функции чтения кадров и отладки собственной реализации средства чтения кадров. Код в этом разделе адаптирован из примера кадров камеры.

Вспомогательный класс FrameRenderer реализует следующие методы.

  • конструктор FrameRenderer — конструктор инициализирует вспомогательный класс для использования элемента XAML Image для отображения кадров мультимедиа.
  • ProcessFrame - Этот метод отображает кадр мультимедиа, представленный MediaFrameReference, в элементе Image, который вы передали в конструктор. Обычно этот метод следует вызывать из вашего FrameArrived обработчика событий, передавая кадр, возвращенный методом TryAcquireLatestFrame.
  • ConvertToDisplayableImage — этот метод проверяет формат кадра мультимедиа и при необходимости преобразует его в отображаемый формат. Для цветных изображений это означает, что цветовой формат — BGRA8, и альфа-режим растрового изображения предварительно умножен. Для кадров глубины или инфракрасного диапазона каждая линия сканирования обрабатывается для преобразования значений глубины или инфракрасного диапазона в псевдоцветовой градиент с использованием класса PsuedoColorHelper, который также включен в пример и указан ниже.

Замечание

Чтобы выполнять обработку пикселей на изображениях SoftwareBitmap , необходимо получить доступ к собственному буферу памяти. Для этого необходимо использовать COM-интерфейс IMemoryBufferByteAccess, включенный в список кода ниже, и необходимо обновить свойства проекта, чтобы разрешить компиляцию небезопасного кода. Дополнительные сведения см. в разделе "Создание, изменение и сохранение растровых изображений".

[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. Одним из ограничений использования этого нового метода является то, что события поступления кадров происходят только с частотой самого медленного источника захвата. Будут удалены дополнительные кадры из более быстрых источников. Кроме того, поскольку система ожидает, что кадры будут поступать из разных источников с разной скоростью, она не распознает автоматически, если источник вовсе перестал их передавать. В примере кода в этом разделе показано, как использовать событие для создания собственной логики времени ожидания, которая вызывается, если коррелированные кадры не приходят в пределах определенного приложением времени.

Действия по использованию MultiSourceMediaFrameReader аналогичны инструкциям по использованию MediaFrameReader , описанным ранее в этой статье. В этом примере используется источник цвета и источник глубины. Объявите некоторые строковые переменные для хранения исходных идентификаторов кадров мультимедиа, которые будут использоваться для выбора кадров из каждого источника. Затем объявите ManualResetEventSlim, CancellationTokenSourceи 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 для цветовой и глубинной камеры. Сохраните идентификатор для каждого источника, чтобы выбрать поступающий кадр для соответствующего источника.

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;

Создайте и инициализируйте MultiSourceMediaFrameReader путем вызова CreateMultiSourceFrameReaderAsync и передачи массива источников кадров, используемых средством чтения. Зарегистрируйте обработчик событий для события FrameArrived . В этом примере создается экземпляр вспомогательного класса FrameRenderer, описанный ранее в этой статье, для отрисовки кадров в элемент управления Image. Запустите фрейм-ридер, вызвав StartAsync.

Зарегистрируйте обработчик событий для события CorellationFailed, объявленного ранее в примере. Мы будем сигнализировать об этом событии, если один из источников кадров мультимедиа, используемых, останавливает производство кадров. Наконец, вызовите 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. Это означает, что событие будет инициировано в ритме самого медленного источника мультимедиа. Если один источник создает несколько кадров в то время, когда медленный источник создает один кадр, будут удалены дополнительные кадры из быстрого источника.

Получите MultiSourceMediaFrameReference, связанное с событием, вызвав TryAcquireLatestFrame. Получите MediaFrameReference, связанные с каждым источником кадра мультимедиа, вызвав TryGetFrameReferenceBySourceId, передавая строки идентификаторов, которые были сохранены при инициализации средства чтения кадров.

Вызовите метод Set объекта ManualResetEventSlim, чтобы сообщить о поступлении кадров. Мы проверим это событие в методе 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, то, вероятно, один из источников мультимедийных кадров перестал считывать кадры. В этом случае обычно требуется отключить модуль чтения кадров, поэтому инициируйте событие, определенное приложением 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 года, можно установить свойство AcquisitionMode объекта MediaFrameReader или MultiSourceMediaFrameReader в значение Буферизованный, чтобы сохранить последовательность кадров, передаваемых в приложение из источника кадров.

m_mediaFrameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Buffered;

В режиме приобретения по умолчанию Realtime, если из источника поступают несколько кадров, пока ваше приложение все еще обрабатывает событие FrameArrived для предыдущего кадра, система отправит вашему приложению последний полученный кадр и отбросит дополнительные кадры, ожидающие в буфере. Это предоставляет приложению последний доступный кадр в любое время. Обычно это наиболее полезный режим для приложений компьютерного зрения в режиме реального времени.

В режиме буферизованного захвата система будет хранить все кадры в буфере и предоставлять их приложению через событие FrameArrived в полученном порядке. Обратите внимание, что в этом режиме, когда буфер системы для кадров заполнен, система перестанет получать новые кадры, пока приложение не завершит событие FrameArrived для предыдущих кадров, освобождая больше места в буфере.

Использование MediaSource для отображения кадров в MediaPlayerElement

Начиная с Windows версии 1709, вы можете отображать кадры, полученные из MediaFrameReader непосредственно в элементе управления MediaPlayerElement на странице XAML. Это достигается с помощью MediaSource.CreateFromMediaFrameSource для создания объекта MediaSource, который можно использовать непосредственно MediaPlayer, связанного с MediaPlayerElement. Подробные сведения о работе с MediaPlayer и MediaPlayerElement см. в разделе "Воспроизведение звука и видео" с помощью MediaPlayer.

В следующих примерах кода показана простая реализация, которая отображает кадры с передней и задней камеры одновременно на странице XAML.

Сначала добавьте два элемента управления MediaPlayerElement на страницу XAML.

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

Затем, используя методы, описанные в предыдущих разделах этой статьи, выберите MediaFrameSourceGroup , содержащий объекты MediaFrameSourceInfo для цветных камер на передней панели и задней панели. Обратите внимание, что 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];

Инициализируйте объект MediaCapture, чтобы использовать выбранную MediaFrameSourceGroup.

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, чтобы создать MediaSource для каждого источника кадра, используя свойство Id связанного объекта MediaFrameSourceInfo, чтобы выбрать один из источников кадров в коллекции MediaCapture объекта FrameSources. Создать новый объект MediaPlayer и присвоить его MediaPlayerElement путем вызова SetMediaPlayer. Затем установите свойство источника в только что созданный объект 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 . В следующем примере кода выполняется поиск профиля видео, который поддерживает HDR с широким цветовым охватом (WCG) и возвращает объект MediaCaptureInitializationSettings, который можно использовать для инициализации MediaCapture для использования выбранного устройства и профиля.

Сначала вызовите 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;
    }
}

Дополнительные сведения об использовании профилей камеры см. в разделе Профили камеры.