Обработка кадров мультимедиа с помощью MediaFrameReader
В этой статье показано, как использовать MediaFrameReader с MediaCapture для получения кадров мультимедиа из одного или нескольких доступных источников, включая цвет, глубину и инфракрасные камеры, звуковые устройства или даже пользовательские источники кадров, такие как те, которые создают скелетные кадры отслеживания. Эта функция предназначена для использования приложениями, выполняющими обработку кадров мультимедиа в режиме реального времени, таких как дополненная реальность и приложения камеры с поддержкой глубины.
Если вы хотите просто записать видео или фотографии, например типичное приложение для фотографии, то вы, вероятно, хотите использовать один из других методов захвата, поддерживаемых MediaCapture. Список доступных методов записи мультимедиа и статей, показывающих их использование, см. в разделе "Камера".
Примечание.
Функции, рассмотренные в этой статье, доступны только начиная с Windows 10 версии 1607.
Примечание.
Существует пример универсального приложения Для Windows, демонстрирующий использование MediaFrameReader для отображения кадров из разных источников кадров, включая цвет, глубину и инфракрасные камеры. Дополнительные сведения см. в примере кадров камеры.
Примечание.
В Windows 10 версии 1803 появилась новая набор API-интерфейсов для использования MediaFrameReader с звуковыми данными. Дополнительные сведения см. в разделе "Обработка аудиокадров с помощью MediaFrameReader".
Настройка проекта
Как и в любом приложении, использующего MediaCapture, необходимо объявить, что приложение использует возможность веб-камеры, прежде чем пытаться получить доступ к любому устройству камеры. Если ваше приложение будет записывать с звукового устройства, вы также должны объявить возможность устройства микрофона.
Добавление возможностей в манифест приложения
- В Microsoft Visual Studio в Обозреватель решений откройте конструктор манифеста приложения, дважды щелкнув элемент package.appxmanifest.
- Перейдите на вкладку Возможности.
- Установите флажок для веб-камеры и флажок для микрофона.
- Чтобы получить доступ к библиотеке изображений и видео, установите флажки для библиотеки изображений и поле для библиотеки видео.
В примере кода в этой статье используются 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 extension используется для преобразования объектов 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 — если указать ЦП, система будет использовать память ЦП, которая гарантирует, что при поступлении кадров они будут доступны как объекты SoftwareBitmap. При указании авто система будет динамически выбирать оптимальное расположение памяти для хранения кадров. Если система решит использовать память 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 , представляющий источник. Этот объект получается путем доступа к словарю Frame для инициализированного объекта MediaCapture, указывая идентификатор источника кадра, который требуется использовать. Поэтому мы сохранили объект MediaFrameSourceInfo при выборе исходной группы кадров.
Свойство MediaFrameSource.SupportedFormats содержит список объектов MediaFrameFormat , описывающих поддерживаемые форматы источника кадров. Используйте метод расширения Where Linq, чтобы выбрать формат на основе требуемых свойств. В этом примере выбран формат с шириной 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.
<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 . При вызове обработчика параметр отправителя содержит ссылку на объект MediaFrameReader , который вызвал событие. Вызов TryAcquireLatestFrame в этом объекте, чтобы попытаться получить последний кадр. Как подразумевает имя, TryAcquireLatestFrame может не получиться при возврате кадра. Таким образом, когда вы обращаетесь к VideoMediaFrame, а затем свойства SoftwareBitmap, обязательно проверьте значение NULL. В этом примере условный оператор NULL ? используется для доступа к SoftwareBitmap , а затем извлекаемый объект проверяется на значение NULL.
Элемент управления "Изображение" может отображать только изображения в формате BRGA8 с предварительно умноженным или без альфа-выражения. Если поступающий кадр не имеет этого формата, статический метод Convert используется для преобразования растрового изображения программного обеспечения в правильный формат.
Затем метод Interlocked.Exchange используется для замены ссылки на поступающий растровый рисунок с растровым изображением backbuffer. Этот метод переключает эти ссылки в атомарную операцию, которая является потокобезопасной. После переключения старый образ backbuffer теперь в переменной softwareBitmap удаляется для очистки ресурсов.
Затем coreDispatcher, связанный с элементом Image, используется для создания задачи, которая будет выполняться в потоке пользовательского интерфейса путем вызова RunAsync. Так как асинхронные задачи будут выполняться в задаче, лямбда-выражение, переданное RunAsync, объявляется с помощью асинхронного ключевого слова.
В задаче проверяется переменная _taskRunning, чтобы убедиться, что одновременно выполняется только один экземпляр задачи. Если задача еще не запущена, _taskRunning задано значение true, чтобы предотвратить повторную работу задачи. В некоторое время цикл Interlocked.Exchange вызывается для копирования из backbuffer во временный SoftwareBitmap до тех пор, пока образ backbuffer не равен null. Для каждого заполнения временной растровой карты свойство Source изображения приведение к SoftwareBitmapSource, а затем вызывается setBitmapAsync, чтобы задать источник изображения.
Наконец, переменная _taskRunning возвращает значение false, чтобы задача была запущена снова при следующем вызове обработчика.
Примечание.
Если вы обращаетесь к объектам SoftwareBitmap или Direct3DSurface, предоставляемым свойством VideoMediaFrame объекта MediaFrameReference, система создает надежную ссылку на эти объекты, что означает, что они не будут удалены при вызове Dispose в содержащее MediaFrameReference. Для немедленного удаления объектов необходимо явно вызвать метод Dispose SoftwareBitmap или Direct3DSurface. В противном случае сборщик мусора в конечном итоге освобождает память для этих объектов, но вы не можете знать, когда это произойдет, и если количество выделенных растровых карт или поверхностей превышает максимально допустимое системой количество, поток новых кадров остановится. Например, можно скопировать извлеченные кадры с помощью метода SoftwareBitmap.Copy , а затем освободить исходные кадры, чтобы преодолеть это ограничение. Кроме того, при создании MediaFrameReader с помощью перегрузки CreateFrameReaderAsync(Windows.Media.Capture.Frame.MediaFrameSource inputSource, System.String outputSubtype, Windows.Graphics.Imaging.BitmapSize outputSize) или CreateFrameReaderAsync(Windows.Media.Capture.Frame.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, и что альфа-режим растрового изображения предварительно переопределен. Для глубины или инфракрасных кадров каждая линия сканирования обрабатывается для преобразования значений глубины или инфракрасного диапазона в градиент psuedocolor, используя класс PsuedoColorHelper , который также включен в пример и указан ниже.
Примечание.
Чтобы выполнять обработку пикселей на изображениях SoftwareBitmap , необходимо получить доступ к собственному буферу памяти. Для этого необходимо использовать COM-интерфейс IMemoryBufferByteAccess, включенный в список кода ниже, и необходимо обновить свойства проекта, чтобы разрешить компиляцию небезопасного кода. Дополнительные сведения см. в разделе "Создание, изменение и сохранение растровых изображений".
[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, ОтменаTokenSource и 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 секунд является разумным значением- и задача не была отменена с помощью ОтменыToken, то вероятно, что один из источников медиакадр перестал считывать кадры. В этом случае обычно требуется завершить работу средства чтения кадров, поэтому вызовите событие 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, можно задать для свойства AcquisitionMode mediaFrameReader или MultiSourceMediaFrameReader значение Buffered, чтобы сохранить последовательность кадров, передаваемых в приложение из источника кадров.
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.
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 с расширенным цветом Gamut (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);
}
Дополнительные сведения об использовании профилей камер см. в разделе "Профили камеры".
См. также