Compartir a través de


Procesar fotogramas multimedia con MediaFrameReader

En este artículo se muestra cómo usar un MediaFrameReader con MediaCapture para obtener fotogramas multimedia de uno o varios orígenes disponibles, como cámaras de color, profundidad e infrarrojos, dispositivos de audio o incluso orígenes de fotogramas personalizados, como aquellos que producen fotogramas de seguimiento esqueléticos. Esta característica está diseñada para ser utilizada por aplicaciones que realizan el procesamiento en tiempo real de fotogramas multimedia, como la realidad aumentada y las aplicaciones de cámara con reconocimiento de profundidad.

Si está interesado en simplemente capturar vídeos o fotos, como una aplicación de fotografía típica, probablemente quiera usar una de las otras técnicas de captura compatibles con MediaCapture. Para obtener una lista de las técnicas de captura multimedia disponibles y artículos que muestran cómo usarlos, consulte Cámara.

Nota:

Las características descritas en este artículo solo están disponibles a partir de Windows 10, versión 1607.

Nota:

Hay un ejemplo de aplicación universal de Windows que muestra el uso de MediaFrameReader para mostrar fotogramas de diferentes orígenes de fotogramas, como cámaras de color, profundidad e infrarrojos. Para obtener más información, consulte el ejemplo de fotogramas de cámara ,.

Nota:

Se introdujo un nuevo conjunto de API para usar MediaFrameReader con datos de audio en Windows 10, versión 1803. Para obtener más información, consulte Procesar fotogramas de audio con MediaFrameReader.

Selección de orígenes de fotogramas y grupos de orígenes de fotogramas

Muchas aplicaciones que procesan fotogramas multimedia necesitan obtener fotogramas de varios orígenes a la vez, como las cámaras de color y profundidad de un dispositivo. El objeto MediaFrameSourceGroup representa un conjunto de orígenes de fotogramas multimedia que se pueden usar simultáneamente. Llame al método estático MediaFrameSourceGroup.FindAllAsync para obtener una lista de todos los grupos de orígenes de fotogramas admitidos por el dispositivo actual.

var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();

También puede crear un DeviceWatcher utilizando DeviceInformation.CreateWatcher y el valor devuelto por MediaFrameSourceGroup.GetDeviceSelector para recibir notificaciones cuando se produzcan cambios en los grupos de origen de fotogramas disponibles en el dispositivo, como al conectar una cámara externa. Para obtener más información, vea Enumerar dispositivos.

Un MediaFrameSourceGroup tiene una colección de objetos MediaFrameSourceInfo que describen los orígenes de marco incluidos en el grupo. Después de recuperar los grupos de origen de fotogramas disponibles en el dispositivo, puede seleccionar el grupo que expone los orígenes de fotogramas que le interesan.

En el ejemplo siguiente se muestra la manera más sencilla de seleccionar un grupo de origen de fotogramas. Este código simplemente recorre en bucle todos los grupos disponibles y, a continuación, recorre en bucle cada elemento de la colección sourceInfos de . Cada mediaFrameSourceInfo se comprueba para ver si admite las características que buscamos. En este caso, la propiedad MediaStreamType se comprueba para el valor VideoPreview, lo que significa que el dispositivo proporciona una secuencia de vista previa de vídeo y la propiedad SourceKind se comprueba para el valor Color, lo que indica que el origen proporciona fotogramas de 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;
    }
}

Este método para identificar el grupo de origen de fotogramas deseado y los orígenes de fotogramas funciona en casos sencillos, pero si desea seleccionar orígenes de fotogramas en función de criterios más complejos, puede resultar complicado rápidamente. Otro método consiste en usar la sintaxis linq y los objetos anónimos para realizar la selección. En el ejemplo siguiente se usa el método de extensión Select para transformar los objetos MediaFrameSourceGroup en la lista frameSourceGroups en un objeto anónimo con dos campos: sourceGroup, que representa el propio grupo, y colorSourceInfo, que representa la fuente de cuadro de color del grupo. El campo colorSourceInfo se establece como el resultado de FirstOrDefault, que selecciona el primer objeto para el cual el predicado proporcionado se evalúa como verdadero. En este caso, el predicado es verdadero si el flujo de datos es VideoPreview, la clase de origen es Colory si la cámara está en la parte frontal del dispositivo.

En la lista de objetos anónimos devueltos por la consulta descrita anteriormente, se utiliza el método de extensión Where para seleccionar solo aquellos objetos en los que el campo colorSourceInfo no es null. Por último, se llama a FirstOrDefault para seleccionar el primer elemento de la lista.

Ahora puede usar los campos del objeto seleccionado para obtener referencias al MediaFrameSourceGroup seleccionado y al objeto MediaFrameSourceInfo que representa la cámara de color. Estos se usarán más adelante para inicializar el objeto MediaCapture y crear un MediaFrameReader para el origen seleccionado. Por último, debe probar para ver si el grupo de origen es NULL, lo que significa que el dispositivo actual no tiene los orígenes de captura solicitados.

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

En el ejemplo siguiente se usa una técnica similar como se ha descrito anteriormente para seleccionar un grupo de origen que contenga cámaras de color, profundidad e infrarrojos.

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

Nota:

A partir de Windows 10, versión 1803, puedes usar la clase MediaCaptureVideoProfile para seleccionar un origen de fotogramas multimedia con un conjunto de funcionalidades deseadas. Para obtener más información, consulte la sección Usar perfiles de vídeo para seleccionar un origen de fotogramas más adelante en este artículo.

Inicialización del objeto MediaCapture para usar el grupo de origen de fotogramas seleccionado

El siguiente paso consiste en inicializar el objeto MediaCapture para usar el grupo de origen de fotogramas seleccionado en el paso anterior. Cree una instancia del objeto MediaCapture llamando al constructor . A continuación, cree un objeto MediaCaptureInitializationSettings que se usará para inicializar el objeto MediaCapture. En este ejemplo, se usan las siguientes opciones de configuración:

  • SourceGroup: indica al sistema qué grupo de origen va a usar para obtener fotogramas. Recuerde que el grupo de origen define un conjunto de orígenes de fotogramas multimedia que se pueden usar simultáneamente.
  • SharingMode: indica al sistema si necesita un control exclusivo sobre los dispositivos de origen de captura. Si estableces esto en ExclusiveControl, significa que puedes cambiar la configuración del dispositivo de captura, como el formato de los fotogramas que genera, pero esto significa que si otra aplicación ya tiene control exclusivo, la aplicación producirá un error cuando intente inicializar el dispositivo de captura multimedia. Si establece esto en SoloLecturaCompartida, puede recibir fotogramas de las fuentes de fotogramas incluso si están en uso por otra app, pero no puede cambiar la configuración de los dispositivos.
  • MemoryPreference: si especifica CPU, el sistema usará memoria de CPU que garantiza que cuando lleguen los fotogramas, estarán disponibles como objetos SoftwareBitmap. Si especifica Auto, el sistema elegirá dinámicamente la ubicación de memoria óptima para almacenar fotogramas. Si el sistema decide usar la memoria de GPU, los fotogramas multimedia llegarán como un objeto IDirect3DSurface y no como SoftwareBitmap.
  • streamingCaptureMode: establézcalo en Video para indicar que no es necesario transmitir el audio.

Llame a InitializeAsync para inicializar el MediaCapture con la configuración deseada. Asegúrese de llamar a esto dentro de un pruebe bloque en caso de que se produzca un error en la inicialización.

m_mediaCapture = new MediaCapture();

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

Establecer el formato preferido para el origen del marco

Para establecer el formato preferido para un origen de fotogramas, debe obtener un objeto MediaFrameSource que representa el origen. Para obtener este objeto, acceda al diccionario Frames del objeto MediaCapture inicializado, especificando el identificador del origen del fotograma que desea usar. Este es el motivo por el que guardamos el objeto MediaFrameSourceInfo al seleccionar un grupo de origen de fotogramas.

La propiedad MediaFrameSource.SupportedFormats contiene una lista de objetos MediaFrameFormat que describen los formatos admitidos para el origen de fotogramas. En este ejemplo, se selecciona un formato que tiene un ancho de 1080 píxeles y puede proporcionar fotogramas en formato RGB de 32 bits. El método de extensión FirstOrDefault selecciona la primera entrada de la lista. Si el formato seleccionado es NULL, el origen del marco no admite el formato solicitado. Si se admite el formato, puede solicitar que el origen use este formato llamando a SetFormatAsync.

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

}).FirstOrDefault();

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

await colorFrameSource.SetFormatAsync(preferredFormat);

Crear un lector de fotogramas para el origen de los fotogramas

Para recibir fotogramas de una fuente de fotogramas multimedia, utilice un MediaFrameReader.

MediaFrameReader m_mediaFrameReader;

Cree una instancia del lector de fotogramas llamando a CreateFrameReaderAsync en su objeto MediaCapture inicializado. El primer argumento para este método es el origen del marco desde el que desea recibir fotogramas. Puede crear un lector de fotogramas independiente para cada origen de fotogramas que quiera usar. El segundo argumento indica al sistema el formato de salida en el que desea que lleguen fotogramas. Esto puede ahorrarle tener que realizar sus propias conversiones a fotogramas a medida que llegan. Tenga en cuenta que si especifica un formato que no es compatible con el origen del marco, se producirá una excepción, por lo que debe asegurarse de que este valor se encuentra en la colección SupportedFormats.

Después de crear el lector de fotogramas, registre un controlador para el evento FrameArrived que se genera cada vez que haya un nuevo fotograma disponible desde el origen.

Indique al sistema que empiece a leer fotogramas desde el origen llamando a StartAsync.

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

Controlar el evento de llegada del fotograma

El evento MediaFrameReader.FrameArrived se genera cada vez que hay disponible un nuevo fotograma. Puede optar por procesar cada fotograma que llegue o usar solo fotogramas cuando los necesite. Dado que el lector de fotogramas genera el evento en su propio subproceso, es posible que tenga que implementar cierta lógica de sincronización para asegurarse de que no está intentando acceder a los mismos datos desde varios subprocesos. En esta sección se explica cómo sincronizar los fotogramas de color de dibujo con un control de imagen en una página XAML. Este escenario aborda la restricción de sincronización adicional que requiere que se realicen todas las actualizaciones de los controles XAML en el subproceso de la interfaz de usuario.

El primer paso para mostrar fotogramas en XAML es crear un control Image.

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

En la página de código subyacente, declare una variable miembro de clase del tipo SoftwareBitmap que se usará como búfer de reserva en el que se copiarán todas las imágenes entrantes. Tenga en cuenta que los datos de imagen en sí no se copian, solo las referencias de objeto. Además, declare un valor booleano para realizar un seguimiento de si la operación de la interfaz de usuario se está ejecutando actualmente.

private SoftwareBitmap backBuffer;
private bool taskRunning = false;

Dado que los fotogramas llegarán como objetos SoftwareBitmap, debes crear un objeto SoftwareBitmapSource que te permita usar un SoftwareBitmap como origen de un control XAML. Debe establecer el origen de la imagen en algún lugar del código antes de iniciar el lector de fotogramas.

iFrameReaderImageControl.Source = new SoftwareBitmapSource();

Ahora es el momento para implementar el controlador de eventos FrameArrived. Cuando se llama al controlador, el parámetro remitente contiene una referencia al objeto MediaFrameReader que generó el evento. Llame a TryAcquireLatestFrame en este objeto para intentar obtener el fotograma más reciente. Como indica el nombre, es posible que tryAcquireLatestFrame no devuelva un fotograma. Por lo tanto, cuando acceda a las propiedades VideoMediaFrame y SoftwareBitmap, asegúrese de comprobar si son nulas. En este ejemplo, el operador condicional NULL ? se usa para acceder al softwareBitmap y, a continuación, se comprueba si el objeto recuperado es null.

El control Image solo puede mostrar imágenes en formato BRGA8 con pre multiplicado o sin alfa. Si el marco de llegada no está en ese formato, se utiliza el método estático Convert para convertir el mapa de bits de software al formato correcto.

A continuación, se utiliza el método Interlocked.Exchange para intercambiar la referencia del mapa de bits de llegada con el mapa de bits del buffer secundario. Este método intercambia estas referencias en una operación atómica que es segura para subprocesos. Después del intercambio, la imagen anterior del backbuffer, ahora en la variable softwareBitmap, se elimina para liberar sus recursos.

A continuación, el CoreDispatcher asociado con el elemento Image se usa para crear una tarea que se ejecutará en el hilo de la interfaz de usuario llamando a RunAsync. Dado que las tareas asincrónicas se realizarán dentro de la tarea, la expresión lambda pasada a RunAsync se declara con la palabra clave async.

Dentro de la tarea, se comprueba la variable _taskRunning para asegurarse de que solo se ejecuta una instancia de la tarea a la vez. Si la tarea aún no se está ejecutando, _taskRunning se establece en true para evitar que la tarea se vuelva a ejecutar. En un mientras bucle, se llama a interlocked.Exchange para copiar desde el búfer en un temporal softwareBitmap hasta que la imagen de backbuffer sea null. Para cada vez que se rellena el mapa de bits temporal, la propiedad Source de la Imagen se convierte en un SoftwareBitmapSourcey, a continuación, se llama a SetBitmapAsync para establecer el origen de la imagen.

Por último, la variable _taskRunning se vuelve a establecer en false para que la tarea se pueda volver a ejecutar la próxima vez que se llame al controlador.

Nota:

Si accede a los objetos SoftwareBitmap o Direct3DSurface proporcionados por la propiedad VideoMediaFrame de un MediaFrameReference, el sistema crea una referencia fuerte a estos objetos, lo que significa que no se eliminarán al llamar a Dispose en el MediaFrameReference. Debe llamar explícitamente al método Dispose de SoftwareBitmap o de Direct3DSurface directamente para que los objetos se eliminen inmediatamente. De lo contrario, el recolector de basura liberará finalmente la memoria de estos objetos, pero no se puede saber cuándo ocurrirá, y si el número de mapas de bits asignados o superficies supera el límite máximo permitido por el sistema, se detendrá el flujo de nuevos fotogramas. Puede copiar fotogramas recuperados mediante el método SoftwareBitmap.Copy por ejemplo y, a continuación, liberar los fotogramas originales para superar esta limitación. Además, si crea el mediaFrameReader de mediante la sobrecarga CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype, Windows.Graphics.Imaging.BitmapSize outputSize) o CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype), los fotogramas devueltos son copias de los datos de fotogramas originales y, por lo tanto, no hacen que la adquisición de fotogramas se detenga cuando se conserven.

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

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

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

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

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

                taskRunning = false;
            });
    }

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

Limpieza de recursos

Cuando haya terminado de leer fotogramas, asegúrese de detener el lector de fotogramas multimedia llamando a StopAsync, anulando el registro del controlador FrameArrived y eliminando el objeto MediaCapture.

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

Para obtener más información sobre cómo limpiar los objetos de captura multimedia cuando se suspende la aplicación, consulta Mostrar la vista previa de la cámara en una aplicación WinUI 3.

La clase auxiliar FrameRenderer

En esta sección se proporciona la lista de código completa para una clase auxiliar que facilita la visualización de los fotogramas de orígenes de color, infrarrojos y profundidad en la aplicación. Normalmente, querrá hacer algo más con los datos de profundidad e infrarrojos que simplemente mostrarlos en la pantalla, pero esta clase auxiliar es una herramienta valiosa para demostrar la funcionalidad de lectura de fotogramas y para depurar su propia implementación de lector de fotogramas. El código de esta sección se adapta del ejemplo de fotogramas de cámara.

La clase auxiliar FrameRenderer implementa los métodos siguientes.

  • Constructor FrameRenderer: el constructor inicializa la clase auxiliar para usar el elemento XAML Image que pasas para mostrar marcos de medios.
  • ProcessFrame: Este método muestra un marco multimedia, representado por un MediaFrameReference, dentro del elemento Image que se pasó al constructor. Normalmente, debe llamar a este método desde el controlador de eventos de FrameArrived, pasando el marco devuelto por TryAcquireLatestFrame.
  • ConvertToDisplayableImage: este método comprueba el formato del marco multimedia y, si es necesario, lo convierte en un formato que se puede mostrar. Para las imágenes de color, esto significa asegurarse de que el formato de color es BGRA8 y que el modo alfa del mapa de bits está premultiplicado. Para fotogramas infrarrojos o de profundidad, cada línea de exploración se procesa para convertir los valores de profundidad o infrarrojos en un degradado pseudocolor, utilizando la clase PsuedoColorHelper, que también se incluye en el ejemplo y se indica a continuación.

Nota:

Para realizar la manipulación de píxeles en SoftwareBitmap imágenes, debe acceder a un búfer de memoria nativo. Para ello, debe usar la interfaz COM IMemoryBufferByteAccess incluida en la lista de código siguiente y debe actualizar las propiedades del proyecto para permitir la compilación de código no seguro. Para obtener más información, vea Crear, editar y guardar imágenes de mapa de bits.

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

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

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

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

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

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

                    _taskRunning = false;
                });
        }
    }



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

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

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

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

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

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

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

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

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

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

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

        return result;
    }



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

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

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

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

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

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

        return outputBitmap;
    }



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

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

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

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

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

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

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

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

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

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

            return lut;
        }



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

            return lut;
        }



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

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

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


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

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

        #endregion

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

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

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

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

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



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

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

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

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

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

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


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

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

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

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

                    _taskRunning = false;
                });
        }
    }
}

Uso de MultiSourceMediaFrameReader para obtener fotogramas correlacionados con el tiempo de varios orígenes

A partir de Windows 10, versión 1607, puedes usar MultiSourceMediaFrameReader para recibir fotogramas correlacionados con el tiempo de varios orígenes. Esta API facilita el procesamiento que requiere marcos de varios orígenes tomados en proximidad temporal, como a través de la clase DepthCorrelatedCoordinateMapper. Una limitación del uso de este nuevo método es que los eventos de llegada de fotograma solo se generan a la velocidad del origen de captura más lento. Se descartarán fotogramas adicionales de orígenes más rápidos. Además, dado que el sistema espera que los fotogramas lleguen de diferentes orígenes a diferentes velocidades, no reconoce automáticamente si un origen ha dejado de generar fotogramas por completo. El código de ejemplo de esta sección muestra cómo usar un evento para crear su propia lógica de tiempo de espera que se invoque si los marcos correlacionados no llegan dentro de un límite de tiempo definido por la aplicación.

Los pasos para usar MultiSourceMediaFrameReader son similares a los pasos para usar MediaFrameReader descritos anteriormente en este artículo. En este ejemplo se usará un origen de color y un origen de profundidad. Declare unas variables de cadena para almacenar los identificadores de origen de los fotogramas multimedia que se usarán para seleccionar fotogramas de cada origen. A continuación, declare un ManualResetEventSlim, un CancellationTokenSource, y un EventHandler que serán usados para implementar la lógica de tiempo de espera del ejemplo.

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


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

Con las técnicas descritas anteriormente en este artículo, busque un MediaFrameSourceGroup que incluya los orígenes de color y profundidad necesarios para este escenario de ejemplo. Después de seleccionar el grupo de origen de fotogramas deseado, obtenga el MediaFrameSourceInfo para cada origen de fotogramas.

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

Cree e inicialice un objeto MediaCapture y pase el grupo de origen de fotogramas seleccionado en la configuración de inicialización.

m_mediaCapture = new MediaCapture();

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

await m_mediaCapture.InitializeAsync(settings);

Después de inicializar el objeto MediaCapture, obtenga los objetos MediaFrameSource para las cámaras de color y de profundidad. Almacene el identificador de cada origen para que pueda seleccionar el marco de llegada para el origen correspondiente.

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

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

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

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

Cree e inicialice el MultiSourceMediaFrameReader llamando a CreateMultiSourceFrameReaderAsync y pasando una matriz de fuentes de fotogramas que usará el lector. Registre un controlador de eventos para el evento FrameArrived. En este ejemplo se crea una instancia de la clase auxiliar FrameRenderer, descrita anteriormente en este artículo, para representar fotogramas en un control Image. Inicie el lector de fotogramas llamando a StartAsync.

Registre un controlador de eventos para el evento CorellationFailed declarado anteriormente en el ejemplo. Señalaremos este evento si una de las fuentes de fotogramas multimedia usadas deja de producir fotogramas. Por último, use Task.Run para invocar el método auxiliar para el tiempo de espera, NotifyAboutCorrelationFailure, en un hilo independiente. La implementación de este método se muestra más adelante en este artículo.

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

m_multiFrameReader.FrameArrived += MultiFrameReader_FrameArrived;

m_frameRenderer = new FrameRenderer(iFrameReaderImageControl);

MultiSourceMediaFrameReaderStartStatus startStatus =
    await m_multiFrameReader.StartAsync();

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

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

El evento FrameArrived se genera siempre que hay disponible un nuevo marco de todos los orígenes de marcos multimedia que son administrados por el MultiSourceMediaFrameReader. Esto significa que el evento se generará al ritmo de la fuente multimedia más lenta. Si un origen genera varios fotogramas en el tiempo en que un origen más lento genera un fotograma, se quitarán los fotogramas adicionales del origen rápido.

Obtenga el MultiSourceMediaFrameReference asociado al evento llamando a TryAcquireLatestFrame. Obtenga el MediaFrameReference asociado a cada origen de fotogramas multimedia llamando a TryGetFrameReferenceBySourceId, pasando las cadenas de identificador almacenadas cuando se inicializó el lector de fotogramas.

Llame al método Set del objeto ManualResetEventSlim para indicar que han llegado los fotogramas. Comprobaremos este evento en el método de NotifyCorrelationFailure que se ejecuta en un subproceso independiente.

Por último, realice cualquier procesamiento en los fotogramas multimedia correlacionados con el tiempo. En este ejemplo simplemente se muestra el fotograma del origen de la profundidad.

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

El método auxiliar NotifyCorrelationFailure se ejecutó en un subproceso independiente después de haber iniciado el lector de fotogramas. En este método, compruebe si se ha señalado el evento de fotograma recibido. Recuerden que, en el controlador FrameArrived, establecemos este evento cada vez que llega un conjunto de fotogramas correlacionados. Si el evento no se ha activado durante un período de tiempo definido por la aplicación (5 segundos es razonable) y la tarea no se canceló mediante el CancellationToken, es probable que una de las fuentes de fotogramas multimedia haya dejado de procesar fotogramas. En este caso, normalmente desea cerrar el lector de fotogramas, por lo que levante el evento CorrelationFailed definido por la aplicación. En el controlador de este evento, puede detener el lector de fotogramas y limpiar los recursos asociados, como se muestra anteriormente en este artículo.

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

Usar el modo de adquisición de fotogramas almacenados en búfer para conservar la secuencia de fotogramas adquiridos

A partir de Windows 10, versión 1709, puedes establecer la propiedad AcquisitionMode de un MediaFrameReader o MultiSourceMediaFrameReader en Buffered para conservar la secuencia de fotogramas pasados a la aplicación desde la fuente de fotogramas.

m_mediaFrameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Buffered;

En el modo de adquisición predeterminado, realtime, si se adquieren varios fotogramas del origen mientras la aplicación sigue controlando el evento FrameArrived para un fotograma anterior, el sistema enviará a la aplicación el marco adquirido más recientemente y quitará fotogramas adicionales en espera en el búfer. Esto proporciona a la aplicación el fotograma disponible más reciente en todo momento. Este suele ser el modo más útil para las aplicaciones computer vision en tiempo real.

En modo de adquisición almacenado en búfer, el sistema mantendrá todos los fotogramas en el búfer y los proporcionará a la aplicación a través del evento FrameArrived en el orden recibido. Ten en cuenta que en este modo, cuando se rellena el búfer del sistema para fotogramas, el sistema dejará de adquirir nuevos fotogramas hasta que la aplicación complete el evento FrameArrived para fotogramas anteriores, liberando más espacio en el búfer.

Usar MediaSource para mostrar fotogramas en mediaPlayerElement

A partir de Windows, versión 1709, puedes mostrar fotogramas adquiridos desde un MediaFrameReader directamente en un control MediaPlayerElement en tu página XAML. Esto se logra utilizando el método MediaSource.CreateFromMediaFrameSource para crear un objeto MediaSource que un MediaPlayer asociado a un MediaPlayerElementpuede usar directamente. Para obtener información detallada sobre cómo trabajar con MediaPlayer y MediaPlayerElement, consulta Reproducir audio y vídeo con MediaPlayer.

Los ejemplos de código siguientes muestran una implementación sencilla que muestra los fotogramas desde una cámara frontal y orientada hacia atrás simultáneamente en una página XAML.

En primer lugar, agrega dos controles MediaPlayerElement a tu página XAML.

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

A continuación, con las técnicas que se muestran en las secciones anteriores de este artículo, seleccione una MediaFrameSourceGroup que contenga objetos MediaFrameSourceInfo para cámaras de color en el panel frontal y el panel posterior. Tenga en cuenta que el MediaPlayer no convierte automáticamente fotogramas de formatos que no son de color, como los datos de profundidad o infrarrojos, en datos de color. El uso de otros tipos de sensor puede producir resultados inesperados.

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

Inicialice el objeto MediaCapture para usar el MediaFrameSourceGroup seleccionado.

m_mediaCapture = new MediaCapture();

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

Por último, llame a MediaSource.CreateFromMediaFrameSource para crear un mediaSource para cada origen de fotogramas mediante la propiedad id. de del objeto MediaFrameSourceInfo asociado para seleccionar uno de los orígenes de fotogramas de la colección MediaCapture FrameSources. Inicialice un nuevo objeto MediaPlayer y asígnelo a un mediaPlayerElement llamando a SetMediaPlayer. A continuación, establezca la propiedad Source en el objeto MediaSource recién creado.

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

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

Usar perfiles de vídeo para seleccionar un origen de fotogramas

Un perfil de cámara, representado por un objeto MediaCaptureVideoProfile, representa un conjunto de funcionalidades que proporciona un dispositivo de captura determinado, como velocidades de fotogramas, resoluciones o características avanzadas como la captura HDR. Un dispositivo de captura puede admitir varios perfiles, lo que le permite seleccionar el que está optimizado para el escenario de captura. A partir de Windows 10, versión 1803, puedes usar mediaCaptureVideoProfile para seleccionar un origen de fotogramas multimedia con funcionalidades concretas antes de inicializar el objeto MediaCapture. El código de ejemplo siguiente busca un perfil de vídeo que admita HDR con amplia gama de colores (WCG) y devuelve un objeto MediaCaptureInitializationSettings que se puede usar para inicializar MediaCapture para poder usar el dispositivo y el perfil seleccionados.

En primer lugar, llame a MediaFrameSourceGroup.FindAllAsync para obtener una lista de todos los grupos de origen de fotogramas multimedia disponibles en el dispositivo actual. Recorra cada grupo de origen y llame a MediaCapture.FindKnownVideoProfiles para obtener una lista de todos los perfiles de vídeo del grupo de origen actual que admiten el perfil especificado, en este caso HDR con la foto WCG. Si se encuentra un perfil que cumpla los criterios, cree un nuevo objeto MediaCaptureInitializationSettings y establezca el VideoProfile en el perfil de selección y el VideoDeviceId a la propiedad id. de del grupo de origen del marco multimedia actual.

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

Para obtener más información sobre el uso de perfiles de cámara, consulte Perfiles de cámara.