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

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

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

Примечание

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

Примечание

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

Примечание

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

Настройка проекта

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

Добавление возможностей в манифест приложения

  1. В Microsoft Visual Studio откройте конструктор манифеста приложения, дважды щелкнув элемент package.appxmanifest в Обозревателе решений.
  2. Перейдите на вкладку Возможности.
  3. Выставьте флажок для пункта Веб-камера и поле для параметра Микрофон.
  4. Для доступа к библиотеке изображений и видео установите флажки Библиотека изображений и Библиотека видео.

В примере кода в этой статье используются API из следующих пространств имен в дополнение к включенным в шаблон проекта по умолчанию.

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;

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

Многим приложениям, обрабатывающим кадры мультимедиа, требуется получать кадры из нескольких источников одновременно, например от цветных камер и камер с эффектом глубины. Объект 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 для преобразования объектов MediaFrameSourceGroup в списке frameSourceGroups в анонимный объект с двумя полями: sourceGroup, представляющим саму группу, и colorSourceInfo, представляющим источник цветных кадров в этой группе. В поле colorSourceInfo содержится результат FirstOrDefault, который выбирает первый объект, для которого используемое условие возвращает значение true. В этом случае условие приобретает значение true, если типом потока является VideoPreview, типом источника — Color, а камерой является расположенная на лицевой панели устройства.

В списке анонимных объектов, возвращаемом по рассмотренному выше запросу, метод расширения Where используется для выбора только тех объектов, у которых в поле colorSourceInfo не стоит значение null. В заключение производится вызов FirstOrDefault, чтобы выбрать первый элемент в списке.

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

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

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

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

if (selectedGroup == null)
{
    return;
}

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

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

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

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

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

Примечание

Начиная с Windows 10 версии 1803, вы можете использовать класс MediaCaptureVideoProfile для выбора источника кадров мультимедиа с набором требуемых возможностей. Дополнительные сведения см. в последующем разделе Использование профилей видео для выбора источника кадров этой статьи.

Инициализация объекта MediaCapture для использования выбранной группы источников кадров

Далее нужно инициализировать объект MediaCapture для использования выбранной на предыдущем этапе группы источников кадров.

Объект MediaCapture обычно используется из разных расположений внутри приложения, поэтому следует объявить переменную-член класса, чтобы хранить его.

MediaCapture mediaCapture;

Создайте экземпляр объекта MediaCapture, вызвав конструктор. Затем создайте объект MediaCaptureInitializationSettings , который будет использоваться для инициализации объекта MediaCapture . В этом примере использованы следующие параметры:

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

Вызовите InitializeAsync для инициализации MediaCapture с требуемыми параметрами. Не забудьте поместить этот вызов в блок try на случай сбоя инициализации.

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

Установка предпочтительного формата для источника кадров

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

Свойство MediaFrameSource.SupportedFormats содержит список объектов MediaFrameFormat , описывающих поддерживаемые форматы для источника кадров. Используйте метод расширения Linq Where для выбора формата на основе требуемых свойств. В этом примере выбран формат с шириной 1080 пикселей, позволяющий получать кадры в 32-разрядном формате RGB. Метод расширения FirstOrDefault выбирает первый элемент в списке. Если выбранный формат — null, запрошенный формат не поддерживается источником кадров. Если формат поддерживается, вы можете запросить использование источником этого формата путем вызова 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);

Создание средство чтения кадров для источника кадров

Для получения кадров от источника кадров мультимедиа используйте MediaFrameReader.

MediaFrameReader mediaFrameReader;

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

После создания ридера кадров зарегистрируйте обработчик для события FrameArrived, которое создается при поступлении нового кадра из источника.

Дайте системе команду на запуск чтения кадров из источника путем вызова StartAsync.

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

Обработка события поступления кадра

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

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

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

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

private SoftwareBitmap backBuffer;
private bool taskRunning = false;

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

imageElement.Source = new SoftwareBitmapSource();

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

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

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

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

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

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

Примечание

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

Очистка ресурсов

По завершении чтения кадров не забудьте остановить ридер кадров мультимедиа путем вызова StopAsync, отмены регистрации обработчика FrameArrived и удаления объекта MediaCapture.

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

Подробные сведения об удалении объектов захвата мультимедиа при приостановке вашего приложения см. в разделе Отображение просмотра камеры.

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

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

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

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

Примечание

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

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

Используйте MultiSourceMediaFrameReader для получения коррелирующих по времени кадров из нескольких источников.

Начиная с Windows 10 версии 1607, можно использовать MultiSourceMediaFrameReader для получения коррелирующих по времени кадров из нескольких источников. Этот API упрощает обработку, если требуются кадры из нескольких источников, сделанные примерно в одно время, например с использованием класса DepthCorrelatedCoordinateMapper. Одним из ограничений этого нового метода является то, что события поступления кадра создаются со скоростью самого медленного источника записи. Дополнительные кадры из более быстрых источников не обрабатываются. Кроме того, поскольку система ожидает, что кадры будут поступать из разных источников с разной скоростью, она автоматически не распознает, прекратил ли источник генерировать кадры. В примере кода из этого раздела показано, как использовать событие для создания собственной логики тайм-аута, которая вызывается, если коррелирующие кадры не поступают в течение определяемого приложением лимита времени.

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

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;

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

mediaCapture = new MediaCapture();

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

await mediaCapture.InitializeAsync(settings);

После инициализации объекта MediaCapture извлеките объекты MediaFrameSource для камер цвета и глубины. Сохраните идентификатор для каждого источника, чтобы можно было выбрать входящий кадр для соответствующего источника.

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;

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

Зарегистрируйте обработчик событий для события CorellationFailed, объявленного ранее в этом примере. Мы сигнализируем это событие, если один из используемых источников мультимедийных кадров прекратит создание кадров. Наконец, вызовите метод Task.Run, чтобы вызвать вспомогательный метод тайм-аута (NotifyAboutCorrelationFailure) в отдельном потоке. Реализации этого метода приводится далее в этой статье.

_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));

Событие 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(_colorSourceId))
    using (MediaFrameReference depthFrame =
        muxedFrame.TryGetFrameReferenceBySourceId(_depthSourceId))
    {
        // Notify the listener thread that the frame has been received.
        _frameReceived.Set();
        _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, _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;
}

Используйте буферизованный режим получения кадров, чтобы сохранить последовательность полученных кадров

Начиная с Windows 10 версии 1709, вы можете задать значение Buffered свойству AcquisitionMode объекта MediaFrameReader или MultiSourceMediaFrameReader, чтобы переданная в приложение из источника последовательность кадров сохранялась.

mediaFrameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Buffered;

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

В режиме получения Buffered система держит все кадры в буфере и предоставляет их приложению с помощью события 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.

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

Наконец, вызовите метод MediaSource.CreateFromMediaFrameSource, чтобы создать MediaSource для каждого источника кадров, указав свойство Id связанного объекта MediaFrameSourceInfo для выбора одного из источников кадров в коллекции FrameSources объекта MediaCapture. Инициализируйте новый объект MediaPlayer и назначьте его MediaPlayerElement, вызвав метод SetMediaPlayer. Затем задайте свойство Source у созданного объекта MediaSource.

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;

Использование профилей видео для выбора источника кадров

Профиль камеры, представляемый объектом MediaCaptureVideoProfile — это набор возможностей, доступных в данном устройстве записи. К Это может быть, например, частота кадров, разрешение или дополнительные функции, такие как захват в HDR-формате. Устройство захвата может поддерживать несколько профилей, что позволяет выбрать тот, который лучше всего оптимизирован для вашего сценария захвата. Начиная с Windows 10 версии 1803, вы можете использовать класс MediaCaptureVideoProfile для выбора источника кадров мультимедиа с набором определенных возможностей до инициализации объекта MediaCapture. В следующем примере метод ищет видео профиль, который поддерживает HDR с широким диапазоном цвета (WCG), и возвращает объект MediaCaptureInitializationSettings, который можно передать в метод инициализации MediaCapture для использования выбранного устройства и профиля.

Сначала вызовите метод MediaFrameSourceGroup.FindAllAsync, чтобы получить список всех групп источников кадров мультимедиа, доступных на текущем устройстве. В цикле пройдитесь по каждой группе источников и вызовите метод MediaCapture.FindKnownVideoProfiles, чтобы получить список всех профилей видео для текущей группы источников, которые поддерживают указанный профиль (в данном случае это HDR с фотографией WCG). Если найден профиль, который соответствует критериям, создайте объект MediaCaptureInitializationSettings и укажите значение VideoProfile, чтобы выбрать профиль, а в значении VideoDeviceId свойства Id укажите текущую группу источников кадров мультимедиа.

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

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