Partilhar via


Processar quadros de mídia com MediaFrameReader

Este artigo mostra como usar um MediaFrameReader em conjunto com MediaCapture para obter frames 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 frames personalizadas, como aquelas que produzem frames de rastreamento esquelético. Esse recurso foi projetado para ser usado por aplicativos que executam processamento em tempo real de quadros de mídia, como aplicativos de realidade aumentada e câmera com reconhecimento de profundidade.

Se você está interessado em simplesmente capturar vídeo ou fotos, como um aplicativo de fotografia típico, então você provavelmente quer usar uma das outras técnicas de captura suportadas pelo MediaCapture. Para obter uma lista de técnicas de captura de mídia disponíveis e artigos mostrando como usá-las, 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 Processar quadros de áudio com MediaFrameReader.

Selecionar fontes de quadros e grupos de fontes

Muitos aplicativos que processam quadros de mídia precisam obter quadros de várias fontes ao mesmo tempo, como câmeras coloridas e de profundidade de um dispositivo. O objeto MediaFrameSourceGroup representa um conjunto de fontes de quadros 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 quadros suportados pelo 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 fontes de quadros disponíveis no dispositivo forem alterados, como quando uma câmera externa for conectada. Para obter mais informações, consulte Enumerar Dispositivos.

Um MediaFrameSourceGroup tem uma coleção de objetos MediaFrameSourceInfo que descrevem as fontes de quadros incluídas no grupo. Depois de recuperar os grupos de fontes de quadros disponíveis no dispositivo, você pode selecionar o grupo que expõe as fontes de quadros nas quais você está interessado.

O exemplo a seguir mostra a maneira mais simples de selecionar um grupo de origem de frames. Esse código simplesmente itera sobre todos os grupos disponíveis e, em seguida, itera sobre cada item na coleção SourceInfos. Cada MediaFrameSourceInfo é verificado para ver se suporta os recursos que estamos procurando. 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 fonte fornece quadros coloridos.

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 fontes de quadros desejado e as fontes de quadros funciona para casos simples, mas se você quiser selecionar fontes de quadros com base em critérios mais complexos, ele pode rapidamente se tornar complicado. Outro método é usar 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 listaframeSourceGroups em um objeto anônimo com dois campos: sourceGroup, representando o próprio grupo, e colorSourceInfo, que representa a fonte do quadro de cores no grupo. O campo colorSourceInfo é definido como o resultado de FirstOrDefault, que seleciona o primeiro objeto para o qual o predicado fornecido seja verdadeiro. Neste caso, o predicado é verdadeiro se o tipo de fluxo for VideoPreview, o tipo de origem for Colore 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. Finalmente, FirstOrDefault é chamado para selecionar o primeiro item na lista.

Agora podes usar os campos do objeto selecionado para obteres referências ao MediaFrameSourceGroup selecionado e ao objeto MediaFrameSourceInfo que representa a câmara colorida. Eles serão usados posteriormente para inicializar o objeto MediaCapture e criar um MediaFrameReader para a fonte selecionada. Finalmente, você deve testar para ver se o grupo de origem é nulo, o que significa que o dispositivo atual não tem as 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 à descrita acima para selecionar um grupo de fontes que contém cores, profundidade e câmeras infravermelhas.

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 de quadros 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 - Isso 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 de 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 quadros, mesmo que eles estejam em uso por outro aplicativo, mas não poderá alterar as configurações dos dispositivos.
  • MemoryPreference - Se tu especificares CPU, o sistema usará a memória da CPU, o que garante que, quando os quadros chegarem, eles estarão disponíveis como objetos SoftwareBitmap. Se especificar Auto, o sistema escolherá dinamicamente o local ideal de memória para armazenar quadros. Se o sistema optar por usar a memória GPU, os quadros de mídia chegarão como um IDirect3DSurface objeto e não como um SoftwareBitmap.
  • StreamingCaptureMode - Defina isso como Vídeo para indicar que o áudio não precisa ser transmitido.

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 preferido para a origem do quadro

Para definir o formato preferido para uma fonte de quadro, você precisa obter um MediaFrameSource objeto que representa a fonte. Você obtém este objeto ao aceder ao dicionário Frames #A0 do objeto inicializado MediaCapture, especificando o identificador da fonte do quadro que pretende 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 suportados para a origem do quadro. Neste exemplo, é selecionado um formato que tem uma largura de 1080 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 será suportado pela origem do quadro. Se o formato for suportado, você poderá solicitar que a fonte 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 frames para a origem do quadro

Para receber quadros para uma fonte de quadros de mídia, use um MediaFrameReader.

MediaFrameReader m_mediaFrameReader;

Instanciar o leitor de quadros ao chamar CreateFrameReaderAsync no seu objeto inicializado MediaCapture. O primeiro argumento para este método é a fonte de frame da qual se deseja receber frames. 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 evitar que você tenha que fazer suas próprias conversões em quadros à medida que eles chegam. Observe que, se especificar um formato que não é suportado pela origem do frame, uma exceção será lançada, portanto, certifique-se de que esse valor esteja na coleção SupportedFormats.

Depois de criar o leitor de quadros, registe um manipulador para o evento FrameArrived , que é gerado sempre que um novo quadro estará disponível a partir da origem.

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

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

Manipular o evento de chegada do frame

O evento MediaFrameReader.FrameArrived é gerado sempre que um novo quadro 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. Este 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 Image.

<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á usada como um buffer traseiro para o qual todas as imagens recebidas serão copiadas. Observe que os dados da imagem em si não são copiados, apenas as referências do objeto. Além disso, declare um booleano para controlar 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 permita usar um SoftwareBitmap como a origem de umde Controle de XAML. Você deve definir a fonte da imagem em algum lugar no seu 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 sender parâmetro contém uma referência ao MediaFrameReader objeto que gerou o evento. Chame TryAcquireLatestFrame neste objeto para tentar obter o quadro mais recente. Como o nome indica, TryAcquireLatestFrame pode não conseguir retornar um quadro. Portanto, ao aceder às propriedades VideoMediaFrame e SoftwareBitmap, certifica-te de testar para "null". Neste exemplo, o operador condicional nulo ? é 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 sem alfa. Se o quadro que chega não estiver nesse formato, o método estático Convert é usado para converter o bitmap do software para o formato correto.

Em seguida, o método Interlocked.Exchange é usado para trocar a referência do bitmap que está a chegar com o bitmap do backbuffer. Esse método troca essas referências em uma operação atômica que é segura para threads. Após a troca, a imagem antiga do buffer de fundo, agora na variável softwareBitmap, é descartada para limpar a sua memória.

Em seguida, o CoreDispatcher associado ao elemento Image é usado para criar uma tarefa que será executada no thread da UI chamando RunAsync. Como as tarefas assíncronas serão executadas dentro da tarefa, a expressão lambda passada para RunAsync é declarada com a palavra-chave async.

Dentro da tarefa, a variável _taskRunning é verificada para garantir que apenas uma instância da tarefa esteja sendo executada de cada 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 temporário SoftwareBitmap até que a imagem backbuffer seja nula. Para cada vez que o bitmap temporário é preenchido, a propriedade Source da Image é convertida em SoftwareBitmapSource, e, em seguida, SetBitmapAsync é chamado para definir a fonte da imagem.

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

Observação

Se você acessar o SoftwareBitmap ou objetos Direct3DSurface fornecidos pela propriedade VideoMediaFrame de umMediaFrameReference , o sistema criará uma referência forte a esses objetos, o que significa que eles não serão descartados quando você chamar Dispose noMediaFrameReference que contém . Você deve chamar explicitamente o método Dispose do SoftwareBitmap ou Direct3DSurface diretamente para que os objetos sejam imediatamente descartados. Caso contrário, o coletor de lixo acabará liberando a memória para esses objetos, mas você não pode 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, em seguida, liberar os quadros originais para superar essa limitação. Além disso, se 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 frames retornados são cópias dos dados dos frames originais e, por isso, não interrompem a aquisição de frames quando são retidos.

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

Quando terminar de ler os quadros, certifique-se de parar o leitor de molduras chamando o método StopAsync , desregistar o manipulador de FrameArrived e descartar 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 é suspenso, consulte Mostrar a visualização da câmera em um aplicativo WinUI 3.

A classe auxiliar do FrameRenderer

Esta secção fornece a listagem completa do código para uma classe auxiliar que facilita a exibição dos quadros de fontes de cor, infravermelho e profundidade na sua aplicação. Normalmente, você vai querer fazer algo mais com profundidade e dados infravermelhos do que apenas exibi-los na tela, mas esta 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 do Exemplo de quadros de câmera.

O FrameRenderer classe auxiliar implementa os seguintes métodos.

  • FrameRenderer construtor - O construtor inicializa a classe auxiliar para usar o elemento XAML Image que o utilizador passa para exibir quadros de mídia.
  • ProcessFrame - Este método exibe um quadro de mídia, representado por um MediaFrameReference, no Image elemento que você passou para o construtor. Normalmente, deve-se chamar este método do manipulador de eventos FrameArrived, passando o frame retornado por TryAcquireLatestFrame.
  • ConvertToDisplayableImage - Este método verifica o formato do quadro de mídia e, se necessário, converte-o em um formato exibível. Para imagens coloridas, isso significa certificar-se de que o formato de cor é BGRA8 e que o modo alfa de bitmap é 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 PseudoColorHelper que também está incluída no exemplo e listada abaixo.

Observação

Para fazer manipulação de pixel em imagens do tipo SoftwareBitmap , deve aceder a um buffer de memória nativa. Para fazer isso, você deve usar a interface COM IMemoryBufferByteAccess incluída na listagem de código abaixo e você deve 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 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;
                });
        }
    }
}

Utilize o MultiSourceMediaFrameReader para obter quadros tempo-correlacionados de várias fontes

A partir do Windows 10, versão 1607, você pode usar MultiSourceMediaFrameReader para receber quadros correlacionados ao tempo de várias fontes. Esta API facilita o processamento que requer frames de várias fontes que foram capturados em estreita proximidade temporal, tal como a utilização da classe DepthCorrelatedCoordinateMapper. Uma limitação do uso desse novo método é que os eventos de chegada ao quadro só são aumentados na taxa da fonte de captura mais lenta. Quadros extras de fontes mais rápidas serão descartados. Além disso, como o sistema espera que os quadros cheguem de diferentes fontes em 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 é 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 CancellationTokenSourcee um EventHandler que será usado para implementar a lógica de tempo limite 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 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 de 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 de quadros 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 MediaFrameSource para as câmeras de cor e profundidade. Armazene o ID de cada fonte para que você possa selecionar o quadro de chegada 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 de fontes de quadros que o leitor usará. Registe um manipulador de eventos para o evento FrameArrived. Este exemplo cria uma instância do FrameRenderer classe auxiliar, descrita anteriormente neste artigo, para renderizar quadros para 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 quadros de mídia que estão sendo usadas parar de produzir quadros. Finalmente, chame Task.Run para chamar o método auxiliar de tempo limite, NotifyAboutCorrelationFailure, num thread separado. A implementação desse método é mostrada mais adiante 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 é gerado 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á acionado 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 produz 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 quadro de mídia chamando TryGetFrameReferenceBySourceId, passando as cadeias de caracteres de ID armazenadas quando o leitor de quadros foi inicializado.

Chame o método Set do objeto ManualResetEventSlim para sinalizar que os quadros chegaram. Verificaremos este evento no método NotifyCorrelationFailure que está a ser executado em uma thread separada.

Finalmente, realize o processamento necessário nos quadros de media correlacionados ao tempo. Este exemplo limita-se a exibir a imagem 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 num tópico separado depois de o leitor de quadros ter sido iniciado. Neste método, verifique se o evento de recebimento do quadro foi sinalizado. Lembre-se, no manipulador de FrameArrived do , não nos esquecemos de definir este evento sempre que chega um conjunto de quadros correlacionados. Se o evento não tiver sido sinalizado durante um período de tempo definido pela aplicação - sendo 5 segundos um valor razoável - e a tarefa não tiver sido cancelada utilizando o CancellationToken, então é provável que uma das fontes de frames de média tenha parado de ler frames. Neste caso, normalmente deseja desligar o leitor de quadros, por isso, gere o evento CorrelationFailed definido pela aplicação . No manipulador para este evento, você pode parar o leitor de quadros e limpar seus 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;
}

Use o modo de aquisição de quadros em buffer para preservar a sequência de quadros adquiridos

A partir do Windows 10, versão 1709, pode definir a propriedade AcquisitionMode de um MediaFrameReader ou MultiSourceMediaFrameReader para Buffered para preservar a sequência de frames passados para o seu aplicativo a partir da fonte de frames.

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 manipulando o evento FrameArrived para um quadro anterior, o sistema enviará ao seu aplicativo o quadro adquirido mais recentemente e soltará quadros adicionais aguardando no buffer. Isto garante que o seu aplicativo tenha sempre o quadro mais recente disponível. Este é normalmente o modo mais útil para aplicações de visão computacional em tempo real.

No modo de aquisição Buffered, o sistema manterá todos os quadros no buffer e os fornecerá ao seu aplicativo por meio do evento FrameArrived na ordem recebida. Observe que, nesse modo, quando o buffer do sistema para quadros for preenchido, o sistema interromperá a aquisição de novos quadros até que seu aplicativo conclua o evento FrameArrived para quadros anteriores, liberando mais espaço no buffer.

Use 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 controle MediaPlayerElement em sua página XAML. Isso é conseguido utilizando o MediaSource.CreateFromMediaFrameSource para criar um objeto MediaSource que pode ser usado diretamente por um MediaPlayer associado a um MediaPlayerElement. Para obter informações detalhadas sobre como trabalhar com o 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 à sua 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 contenha objetos MediaFrameSourceInfo para câmaras coloridas no painel frontal e no painel traseiro. Observe que o MediaPlayer não converte automaticamente quadros de formatos não coloridos, como dados de profundidade ou infravermelhos, em dados coloridos. O uso de outros tipos de sensores 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 grupo de fontes de mídia MediaFrameSourceGroup selecionado.

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

Finalmente, chame MediaSource.CreateFromMediaFrameSource, para criar um MediaSource para cada fonte de quadro, usando a propriedade Id do objeto associado MediaFrameSourceInfo 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 objeto MediaSource recém-criado.

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 origem de frames

Um perfil de câmera, representado por um objeto MediaCaptureVideoProfile , representa um conjunto de recursos que um determinado dispositivo de captura oferece, como taxas de quadros, resoluções ou recursos avançados, como captura HDR. Um dispositivo de captura pode suportar vários perfis, permitindo que você selecione o que é otimizado para o seu cenário de captura. A partir do Windows 10, versão 1803, pode-se usar MediaCaptureVideoProfile para selecionar uma fonte de quadro de mídia com capacidades específicas antes de inicializar o objeto MediaCapture. O código de exemplo a seguir procura um perfil de vídeo que ofereça suporte a 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 suportam o perfil especificado, neste caso HDR com foto WCG. Se um perfil que atenda 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 origem do frame 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.