Compartilhar via


Processar quadros de mídia com MediaFrameReader

Este artigo mostra como usar um MediaFrameReader com MediaCapture para obter quadros de mídia de uma ou mais fontes disponíveis, incluindo câmeras de cor, profundidade e infravermelho, dispositivos de áudio ou até mesmo fontes de quadros personalizadas, como aquelas que produzem quadros de rastreamento esquelético. Esse recurso foi projetado para ser usado por aplicativos que executam o processamento em tempo real de quadros de mídia, como realidade aumentada e aplicativos de câmera com reconhecimento de profundidade.

Se você estiver interessado em simplesmente capturar vídeos ou fotos, como um aplicativo típico de fotografia, provavelmente deseja usar uma das outras técnicas de captura compatíveis com MediaCapture. Para obter uma lista de técnicas e artigos de captura de mídia disponíveis mostrando como usá-los, consulte Camera.

Observação

Os recursos discutidos neste artigo só estão disponíveis a partir do Windows 10, versão 1607.

Observação

Há um exemplo de aplicativo Universal do Windows que demonstra o uso do MediaFrameReader para exibir quadros de diferentes fontes de quadros, incluindo cores, profundidade e câmeras infravermelhas. Para obter mais informações, consulte exemplo de quadros de câmera.

Observação

Um novo conjunto de APIs para usar MediaFrameReader com dados de áudio foi introduzido no Windows 10, versão 1803. Para obter mais informações, consulte Processamento de quadros de áudio com o MediaFrameReader.

Selecionar fontes de frames e grupos de fontes de frames

Muitos aplicativos que processam quadros de mídia precisam obter quadros de várias fontes ao mesmo tempo, como câmeras de cor e profundidade de um dispositivo. O objeto MediaFrameSourceGroup representa um conjunto de fontes de quadro de mídia que podem ser usadas simultaneamente. Chame o método estático MediaFrameSourceGroup.FindAllAsync para obter uma lista de todos os grupos de fontes de quadro compatíveis com o dispositivo atual.

var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();

Você também pode criar um DeviceWatcher usando DeviceInformation.CreateWatcher e o valor retornado de MediaFrameSourceGroup.GetDeviceSelector para receber notificações quando os grupos de origem de quadros disponíveis no dispositivo forem alterados, como por exemplo ao conectar uma câmera externa. Para obter mais informações, consulte Enumerar dispositivos.

Um MediaFrameSourceGroup tem uma coleção de objetos MediaFrameSourceInfo que descrevem as fontes de quadro incluídas no grupo. Depois de recuperar os grupos de origem de imagem disponíveis no dispositivo, você pode selecionar o grupo que expõe as fontes de imagem desejadas.

O exemplo a seguir mostra a maneira mais simples de selecionar um grupo de origem de quadro. Esse código simplesmente percorre todos os grupos disponíveis e, em seguida, percorre cada item na coleção SourceInfos. Cada MediaFrameSourceInfo é verificado para ver se ele dá suporte aos recursos que estamos buscando. Nesse caso, a propriedade MediaStreamType é verificada para o valor VideoPreview, o que significa que o dispositivo fornece um fluxo de visualização de vídeo e a propriedade SourceKind é verificada para o valor Color, indicando que a origem fornece quadros de cores.

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

Esse método de identificar o grupo de origem do quadro desejado e as fontes de quadro funciona para casos simples, mas se você quiser selecionar fontes de quadro com base em critérios mais complexos, ele pode se tornar complicado rapidamente. Outro método é usar a sintaxe linq e objetos anônimos para fazer a seleção. O exemplo a seguir usa o método de extensão Select para transformar os objetos MediaFrameSourceGroup na lista frameSourceGroups em um objeto anônimo com dois campos: sourceGroup, que representa o grupo em si, e colorSourceInfo, que representa a fonte de quadro de cor no grupo. O campo colorSourceInfo é definido como o resultado de FirstOrDefault, que seleciona o primeiro objeto para o qual o predicado fornecido é verdadeiro. Nesse caso, o predicado é verdadeiro se o tipo de fluxo for VideoPreview, o tipo de origem for Color, e se a câmera estiver no painel frontal do dispositivo.

Na lista de objetos anônimos retornados da consulta descrita acima, o método de extensão Where é usado para selecionar apenas os objetos em que o campo colorSourceInfo não é nulo. Por fim, FirstOrDefault é chamado para selecionar o primeiro item da lista.

Agora você pode usar os campos do objeto selecionado para obter referências ao MediaFrameSourceGroup selecionado e ao objeto MediaFrameSourceInfo que representa a câmera de cores. Eles serão usados posteriormente para inicializar o objeto MediaCapture e criar um MediaFrameReader para a fonte selecionada. Por fim, você deve testar para ver se o grupo de origem é nulo, o que significa que o dispositivo atual não tem suas fontes de captura solicitadas.

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

O exemplo a seguir usa uma técnica semelhante conforme descrito acima para selecionar um grupo de origem que contenha câmeras de cor, profundidade e infravermelho.

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

Observação

A partir do Windows 10, versão 1803, você pode usar a classe MediaCaptureVideoProfile para selecionar uma fonte de quadro de mídia com um conjunto de recursos desejados. Para obter mais informações, consulte a seção Usar perfis de vídeo para selecionar uma fonte de quadro mais adiante neste artigo.

Inicializar o objeto MediaCapture para usar o grupo de origem do quadro selecionado

A próxima etapa é inicializar o objeto MediaCapture para usar o grupo de origem do quadro selecionado na etapa anterior. Crie uma instância do objeto MediaCapture chamando o construtor. Em seguida, crie um objeto MediaCaptureInitializationSettings que será usado para inicializar o objeto MediaCapture. Neste exemplo, as seguintes configurações são usadas:

  • SourceGroup – informa ao sistema qual grupo de origem você usará para obter quadros. Lembre-se de que o grupo de origem define um conjunto de fontes de quadro de mídia que podem ser usadas simultaneamente.
  • SharingMode – informa ao sistema se você precisa ter controle exclusivo sobre os dispositivos de origem de captura. Se você definir isso como ExclusiveControl, isso significa que você pode alterar as configurações do dispositivo de captura, como o formato dos quadros que ele produz, mas isso significa que, se outro aplicativo já tiver controle exclusivo, seu aplicativo falhará quando tentar inicializar o dispositivo de captura de mídia. Se você definir isso como SharedReadOnly, poderá receber quadros das fontes de quadro mesmo que estejam em uso por outro aplicativo, mas não poderá alterar as configurações dos dispositivos.
  • MemoryPreference – se você especificar de CPU, o sistema usará a memória da CPU que garante que, quando os quadros chegarem, eles estarão disponíveis como objetos SoftwareBitmap. Se você especificar Automático, o sistema escolherá dinamicamente o local de memória ideal para armazenar quadros. Se o sistema optar por usar memória GPU, os quadros de mídia chegarão como um objeto IDirect3DSurface e não como um SoftwareBitmap.
  • StreamingCaptureMode – Defina isso para Vídeo para indicar que o áudio não precisa ser transmitido via streaming.

Chame InitializeAsync para inicializar o MediaCapture com as configurações desejadas. Certifique-se de chamar isso dentro de um bloco try caso a inicialização falhe.

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

Definir o formato preferencial para a origem do quadro

Para definir o formato preferencial para uma fonte de quadro, você precisa obter um objeto MediaFrameSource que representa a origem. Você obtém esse objeto acessando o dicionário Frames do objeto MediaCapture inicializado, especificando o identificador da origem do quadro que você deseja usar. É por isso que salvamos o objeto MediaFrameSourceInfo quando estávamos selecionando um grupo de origem de quadros.

A propriedade MediaFrameSource.SupportedFormats contém uma lista de objetos MediaFrameFormat que descrevem os formatos com suporte para a origem do quadro. Neste exemplo, um formato é selecionado que tem uma largura de 1.080 pixels e pode fornecer quadros no formato RGB de 32 bits. O método de extensão FirstOrDefault seleciona a primeira entrada na lista. Se o formato selecionado for nulo, o formato solicitado não terá suporte na origem do quadro. Se houver suporte para o formato, você poderá solicitar que a origem use esse formato chamando 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);

Criar um leitor de quadros para a fonte do quadro

Para receber quadros de uma fonte de quadro de mídia, use a MediaFrameReader.

MediaFrameReader m_mediaFrameReader;

Instancie o leitor de quadros chamando CreateFrameReaderAsync em seu objeto MediaCapture inicializado. O primeiro argumento para esse método é a fonte de quadros da qual você deseja receber quadros. Você pode criar um leitor de quadros separado para cada fonte de quadro que deseja usar. O segundo argumento informa ao sistema o formato de saída no qual você deseja que os quadros cheguem. Isso pode salvá-lo de ter que fazer suas próprias conversões em quadros à medida que eles chegam. Observe que, se você especificar um formato que não é suportado pela origem do quadro, uma exceção será gerada, então certifique-se de que este valor esteja na coleção SupportedFormats.

Depois de criar o leitor de quadros, registre um manipulador para o evento FrameArrived que é acionado sempre que um novo quadro estiver disponível na fonte.

Diga ao sistema para começar a ler quadros da origem chamando StartAsync.

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

Lidar com o evento de chegada do quadro

O evento MediaFrameReader.FrameArrived é gerado sempre que um novo frame está disponível. Você pode optar por processar cada quadro que chega ou usar apenas quadros quando precisar deles. Como o leitor de quadros gera o evento em seu próprio thread, talvez seja necessário implementar alguma lógica de sincronização para garantir que você não esteja tentando acessar os mesmos dados de vários threads. Esta seção mostra como sincronizar quadros de cores de desenho com um controle de imagem em uma página XAML. Esse cenário aborda a restrição de sincronização adicional que exige que todas as atualizações para controles XAML sejam executadas no thread da interface do usuário.

A primeira etapa na exibição de quadros em XAML é criar um controle de imagem.

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

Na página code behind, declare uma variável de membro de classe do tipo SoftwareBitmap que será usado como um buffer de fundo para o qual todas as imagens de entrada serão copiadas. Observe que os dados de imagem em si não são copiados, apenas as referências de objeto. Além disso, declare um booliano para acompanhar se nossa operação de interface do usuário está em execução no momento.

private SoftwareBitmap backBuffer;
private bool taskRunning = false;

Como os quadros chegarão como objetos SoftwareBitmap, você precisa criar um objeto SoftwareBitmapSource que permite que você use um SoftwareBitmap como a fonte para um controle XAML. Você deve definir a fonte da imagem em algum lugar do código antes de iniciar o leitor de quadros.

iFrameReaderImageControl.Source = new SoftwareBitmapSource();

Agora é hora de implementar o manipulador de eventos FrameArrived. Quando o manipulador é chamado, o parâmetro do remetente contém uma referência ao objeto MediaFrameReader que gerou o evento. Chame TryAcquireLatestFrame neste objeto para tentar obter o quadro mais recente. Como o nome indica, TryAcquireLatestFrame pode não ter êxito em retornar um quadro. Portanto, quando você acessar as propriedades VideoMediaFrame e SoftwareBitmap, certifique-se de verificar se são nulas. Neste exemplo do operador condicional nulo ? é usado para lidar com valores nulos de maneira segura. é usado para acessar o SoftwareBitmap e, em seguida, o objeto recuperado é verificado como nulo.

O controle Image só pode exibir imagens no formato BRGA8 com pré-multiplicado ou nenhum alfa. Se o quadro que chega não estiver nesse formato, o método estático Converter será usado para converter o bitmap de software no formato correto.

Em seguida, o método Interlocked.Exchange é usado para trocar a referência do bitmap que está chegando com o bitmap do backbuffer. Esse método troca essas referências em uma operação atômica que é thread-safe. Após a troca, a imagem antiga do backbuffer, agora na variável softwareBitmap é descartada para limpar seus recursos.

Em seguida, o CoreDispatcher associado ao elemento Image é utilizado para criar uma tarefa que será executada na thread da interface do usuário ao chamar RunAsync. Como as tarefas assíncronas serão executadas dentro da tarefa, a expressão lambda passada para RunAsync é declarada com a palavra-chave assíncrona.

Dentro da tarefa, a variável _taskRunning é verificada para verificar se apenas uma instância da tarefa está em execução por vez. Se a tarefa ainda não estiver em execução, _taskRunning será definida como true para impedir que a tarefa seja executada novamente. Em um enquanto loop, Interlocked.Exchange é chamado para copiar do backbuffer para um softwareBitmap temporário até que a imagem do backbuffer seja nula. Toda vez que o bitmap temporário é preenchido, a propriedade Source do Image é convertida em um SoftwareBitmapSourcee, em seguida, SetBitmapAsync é chamado para definir a fonte da imagem.

Por fim, a variável _taskRunning é definida como false para que a tarefa possa ser executada novamente na próxima vez que o manipulador for chamado.

Observação

Se você acessar os objetos SoftwareBitmap ou Direct3DSurface fornecidos pela propriedade VideoMediaFrame de um MediaFrameReference, o sistema criará uma referência forte a esses objetos, o que significa que eles não serão descartados quando você chamar Dispose no MediaFrameReferenceque os contém. Você deve chamar explicitamente o método Dispose do SoftwareBitmap ou do Direct3DSurface diretamente para que os objetos sejam imediatamente descartados. Caso contrário, o coletor de lixo eventualmente liberará a memória desses objetos, mas você não poderá saber quando isso ocorrerá e, se o número de bitmaps ou superfícies alocados exceder a quantidade máxima permitida pelo sistema, o fluxo de novos quadros será interrompido. Você pode copiar quadros recuperados usando o método SoftwareBitmap.Copy, por exemplo, e liberar os quadros originais para superar essa limitação. Além disso, se você criar o MediaFrameReader usando a sobrecarga CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype, Windows.Graphics.Imaging.BitmapSize outputSize) ou CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype), os quadros retornados são cópias dos quadros originais e, portanto, não fazem com que a aquisição de quadros seja interrompida caso sejam mantidos.

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

Recursos de limpeza

Certifique-se de parar o leitor de quadros de mídia ao terminar de ler os quadros chamando StopAsync, cancelando o registro do manipulador FrameArrived e descartando o objeto MediaCapture.

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

Para obter mais informações sobre como limpar objetos de captura de mídia quando seu aplicativo for suspenso, consulte Mostrar a visualização da câmera em um aplicativo WinUI 3.

A classe auxiliar FrameRenderer

Esta seção fornece a listagem de código completa para uma classe auxiliar que facilita a exibição dos quadros de fontes de cor, infravermelho e profundidade em seu aplicativo. Normalmente, você desejará fazer algo mais com dados de profundidade e infravermelho do que apenas exibi-los na tela, mas essa classe auxiliar é uma ferramenta útil para demonstrar o recurso de leitor de quadros e para depurar sua própria implementação de leitor de quadros. O código nesta seção é adaptado da amostra de quadros da câmera.

A classe auxiliar FrameRenderer implementa os métodos a seguir.

  • construtor FrameRenderer – O construtor inicializa a classe auxiliar para usar o elemento de imagem XAML que você fornece para a exibição de quadros de mídia.
  • ProcessFrame – Esse método exibe um quadro de mídia, representado por um MediaFrameReference, no elemento Image que você passou para o construtor. Normalmente, você deve chamar esse método no manipulador de eventos FrameArrived, passando o quadro retornado por TryAcquireLatestFrame.
  • ConvertToDisplayableImage – esses métodos verificam o formato do quadro de mídia e, se necessário, convertem-no em um formato exibivel. Para imagens coloridas, isso significa garantir que o formato de cor seja BGRA8 e que o modo alfa de bitmap seja pré-multiplicado. Para quadros de profundidade ou infravermelho, cada linha de varredura é processada para converter os valores de profundidade ou infravermelho em um gradiente de pseudocores, usando a classe PsuedoColorHelper que também está incluída no exemplo e listada abaixo.

Observação

Para fazer a manipulação de pixels em SoftwareBitmap imagens, você deve acessar um buffer de memória nativo. Para fazer isso, você deve usar a interface COM IMemoryBufferByteAccess incluída na listagem de código abaixo e atualizar as propriedades do projeto para permitir a compilação de código não seguro. Para obter mais informações, consulte Criar, editar e salvar imagens de bitmap.

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

Usar MultiSourceMediaFrameReader para obter quadros correlacionados com o tempo de várias fontes

A partir do Windows 10, versão 1607, você pode usar MultiSourceMediaFrameReader para receber quadros correlacionados com o tempo de várias fontes. Essa API facilita o processamento que requer quadros de várias fontes que foram tomadas em proximidade temporal estreita, como usar a classe DepthCorrelatedCoordinateMapper. Uma limitação do uso desse novo método é que os eventos de chegada de quadros são disparados apenas à taxa da fonte de captura mais lenta. Quadros extras de fontes mais rápidas serão removidos. Além disso, como o sistema espera que os quadros cheguem de diferentes fontes a taxas diferentes, ele não reconhece automaticamente se uma fonte parou de gerar quadros completamente. O código de exemplo nesta seção mostra como usar um evento para criar sua própria lógica de tempo limite que será invocada se os quadros correlacionados não chegarem dentro de um limite de tempo definido pelo aplicativo.

As etapas para usar MultiSourceMediaFrameReader são semelhantes às etapas para usar MediaFrameReader descritas anteriormente neste artigo. Este exemplo usará uma fonte de cores e uma fonte de profundidade. Declare algumas variáveis de cadeia de caracteres para armazenar as IDs de origem do quadro de mídia que serão usadas para selecionar quadros de cada fonte. Em seguida, declare um ManualResetEventSlim , um CancellationTokenSource e um EventHandler que será usado para implementar a lógica de timeout para o exemplo.

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;

Usando as técnicas descritas anteriormente neste artigo, procure por um MediaFrameSourceGroup que inclua as fontes de cor e profundidade necessárias para este cenário de exemplo. Depois de selecionar o grupo de origem do quadro desejado, obtenha o MediaFrameSourceInfo para cada fonte de quadro.

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

Crie e inicialize um objeto MediaCapture, passando o grupo de origem do quadro selecionado nas configurações de inicialização.

m_mediaCapture = new MediaCapture();

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

await m_mediaCapture.InitializeAsync(settings);

Depois de inicializar o objeto MediaCapture, recupere objetos de MediaFrameSource para as câmeras de cores e de profundidade. Armazene o ID de cada fonte para que você possa selecionar o quadro que está chegando para a fonte correspondente.

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;

Crie e inicialize o MultiSourceMediaFrameReader chamando CreateMultiSourceFrameReaderAsync e passando uma matriz das fontes de quadros que serão utilizadas pelo leitor. Registre um manipulador de eventos para o evento FrameArrived. Este exemplo cria uma instância da classe auxiliar FrameRenderer, descrita anteriormente neste artigo, para renderizar quadros em um controle Image. Inicie o leitor de quadros chamando StartAsync.

Registre um manipulador de eventos para o evento CorellationFailed declarado anteriormente no exemplo. Sinalizaremos esse evento se uma das fontes de quadro de mídia usadas parar de produzir quadros. Por fim, chame Task.Run para chamar o método auxiliar de tempo limite, NotifyAboutCorrelationFailure, em um thread separado. A implementação desse método é mostrada posteriormente neste artigo.

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

O evento FrameArrived é acionado sempre que um novo frame está disponível a partir de todas as fontes de frames de mídia gerenciadas pelo MultiSourceMediaFrameReader. Isso significa que o evento será gerado de acordo com a cadência da fonte de mídia mais lenta. Se uma fonte produzir vários quadros no tempo em que uma fonte mais lenta produzir um quadro, os quadros extras da fonte rápida serão descartados.

Obtenha o MultiSourceMediaFrameReference associado ao evento chamando TryAcquireLatestFrame. Obtenha o MediaFrameReference associado a cada fonte de frame de mídia chamando TryGetFrameReferenceBySourceId, passando as cadeias de ID armazenadas quando o leitor de frames foi inicializado.

Chame o método Set do objeto ManualResetEventSlim para sinalizar que os quadros chegaram. Verificaremos esse evento no método NotifyCorrelationFailure, que está em execução em uma thread separada.

Por fim, execute qualquer processamento nos quadros de mídia correlacionados ao tempo. Este exemplo simplesmente exibe o quadro da fonte de profundidade.

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

O método auxiliar NotifyCorrelationFailure foi executado em uma thread separada depois que o leitor de quadros foi iniciado. Neste método, verifique se o evento de quadro recebido foi sinalizado. Lembre-se de que, no manipulador FrameArrived, definimos este evento sempre que chega um conjunto de quadros correlacionados. Se o evento não tiver sido sinalizado por algum período de tempo definido pelo app - 5 segundos é um valor razoável - e a tarefa não tenha sido cancelada usando o CancellationToken, é provável que uma das fontes de frames de mídia tenha parado de ler frames. Nesse caso, você normalmente deseja desativar o receptor de frames, portanto, acione o evento CorrelationFailed definido pelo aplicativo. No manipulador para esse evento, você pode parar o leitor de quadros e limpar os recursos associados, conforme mostrado anteriormente neste artigo.

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 o modo de aquisição de quadro armazenado em buffer para preservar a sequência de quadros adquiridos

A partir do Windows 10, versão 1709, você pode definir a propriedade AcquisitionMode de um MediaFrameReader ou de um MultiSourceMediaFrameReader para Buffered, preservando assim a sequência de quadros que são passados da fonte de quadros para o seu aplicativo.

m_mediaFrameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Buffered;

No modo de aquisição padrão, em Tempo Real, se vários quadros forem adquiridos da origem enquanto seu aplicativo ainda estiver tratando o evento FrameArrived para um quadro anterior, o sistema enviará ao seu aplicativo o quadro adquirido mais recentemente e descartará quadros adicionais aguardando no buffer. Isso fornece ao seu aplicativo o quadro disponível mais recente em todos os momentos. Normalmente, esse é o modo mais útil para aplicativos de pesquisa visual computacional em tempo real.

Em modo de aquisição em buffer, o sistema armazenará todos os quadros no buffer e os fornecerá ao seu aplicativo através do evento FrameArrived na ordem recebida. Observe que, nesse modo, quando o buffer do sistema para quadros for preenchido, o sistema deixará de adquirir novos quadros até que seu aplicativo conclua o evento FrameArrived para quadros anteriores, liberando mais espaço no buffer.

Usar MediaSource para exibir quadros em um MediaPlayerElement

A partir do Windows, versão 1709, você pode exibir quadros adquiridos de um MediaFrameReader diretamente em um MediaPlayerElement control na sua página XAML. Isso é feito usando o MediaSource.CreateFromMediaFrameSource para criar o objeto MediaSource que pode ser usado diretamente por um MediaPlayer associado a um MediaPlayerElement. Para obter informações detalhadas sobre como usar MediaPlayer e MediaPlayerElement, consulte Reproduzir áudio e vídeo com o MediaPlayer.

Os exemplos de código a seguir mostram uma implementação simples que exibe os quadros de uma câmera frontal e traseira simultaneamente em uma página XAML.

Primeiro, adicione dois controles MediaPlayerElement à página XAML.

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

Em seguida, usando as técnicas mostradas nas seções anteriores deste artigo, selecione um MediaFrameSourceGroup que contém objetos MediaFrameSourceInfo para câmeras coloridas no painel frontal e no painel traseiro. Note que o MediaPlayer não converte automaticamente quadros de formatos não coloridos, como dados de profundidade ou infravermelho, em dados de cor. O uso de outros tipos de sensor pode produzir 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];

Inicialize o objeto MediaCapture para usar o MediaFrameSourceGroupselecionado.

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 fim, chame MediaSource.CreateFromMediaFrameSource para criar um MediaSource para cada fonte de quadro usando a propriedade Id do objeto MediaFrameSourceInfo associado para selecionar uma das fontes de quadro na coleção FrameSources do objeto MediaCapture. Inicialize um novo objeto MediaPlayer e atribua-o a um MediaPlayerElement chamando SetMediaPlayer. Em seguida, defina a propriedade Source para o recém-criado objeto MediaSource.

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

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

Usar perfis de vídeo para selecionar uma fonte de quadro

Um perfil de câmera, representado por um objeto MediaCaptureVideoProfile, representa um conjunto de recursos que um dispositivo de captura específico fornece, como taxas de quadros, resoluções ou recursos avançados, como captura de HDR. Um dispositivo de captura pode dar suporte a vários perfis, permitindo que você selecione aquele otimizado para o cenário de captura. A partir do Windows 10, versão 1803, você pode usar MediaCaptureVideoProfile para selecionar uma fonte de quadro de mídia com recursos específicos antes de inicializar o objeto MediaCapture. O código de exemplo a seguir procura um perfil de vídeo que dá suporte ao HDR com WCG (Wide Color Gamut) e retorna um objeto MediaCaptureInitializationSettings que pode ser usado para inicializar o MediaCapture para usar o dispositivo e o perfil selecionados.

Primeiro, chame MediaFrameSourceGroup.FindAllAsync para obter uma lista de todos os grupos de origem de quadros de mídia disponíveis no dispositivo atual. Percorra cada grupo de origem e chame MediaCapture.FindKnownVideoProfiles para obter uma lista de todos os perfis de vídeo para o grupo de origem atual que oferece suporte ao perfil especificado, nesse caso HDR com foto WCG. Se um perfil que atende aos critérios for encontrado, crie um novo objeto MediaCaptureInitializationSettings e defina o VideoProfile para o perfil selecionado e o VideoDeviceId para a propriedade Id do grupo atual de fontes de quadros de mídia.

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 obter mais informações sobre como usar perfis de câmera, consulte Perfis de câmera.