Definir o formato, a resolução e a taxa de quadros para o MediaCapture

Este artigo mostra como usar a interface IMediaEncodingProperties para definir a resolução e a taxa de quadros do fluxo de visualização da câmera e das fotos e vídeos capturados. Ele também mostra como garantir que a taxa de proporção do fluxo de visualização corresponda ao da mídia capturada.

Os perfis de câmera oferecem uma maneira mais avançada de descobrir e definir as propriedades de fluxo da câmera, mas não são compatíveis com todos os dispositivos. Para obter mais informações, consulte Perfis de câmera.

O código neste artigo foi adaptado do exemplo CameraResolution. Você pode baixar o exemplo para ver o código usado no contexto ou para usar o exemplo como ponto de partida para seu próprio aplicativo.

Observação

Este artigo se baseia em conceitos e códigos discutidos em Captura básica de fotos, áudio e vídeo com o MediaCapture, que descreve as etapas para implementar uma captura básica de fotos e vídeos. É recomendável que você se familiarize com o padrão de captura de mídia básica neste artigo antes de passar para cenários de captura mais avançados. O código neste artigo presume que seu aplicativo já tenha uma instância de MediaCapture inicializada corretamente.

Uma classe auxiliar de propriedades de codificação de mídia

A criação de uma classe auxiliar simples para encapsular a funcionalidade da interface IMediaEncodingProperties facilita a seleção de um conjunto de propriedades de codificação que atendem a critérios específicos. Essa classe auxiliar é particularmente útil devido ao seguinte comportamento do recurso de propriedades de codificação:

Aviso O método VideoDeviceController.GetAvailableMediaStreamProperties usa um membro da enumeração MediaStreamType, como VideoRecord ou Photo, e retorna uma lista de objetos ImageEncodingProperties ou VideoEncodingProperties que transmitem as configurações de codificação de fluxo, como a resolução da foto ou do vídeo capturado. Os resultados da chamada GetAvailableMediaStreamProperties podem incluir ImageEncodingProperties ou VideoEncodingProperties, independentemente do valor MediaStreamType especificado. Por esse motivo, você deve sempre verificar o tipo de cada valor retornado e convertê-lo no tipo apropriado antes de tentar acessar qualquer um dos valores de propriedade.

A classe auxiliar definida abaixo lida com a verificação de tipo e a conversão de ImageEncodingProperties ou VideoEncodingProperties para que o código do aplicativo não precise distinguir entre os dois tipos. Além disso, a classe auxiliar expõe propriedades para a taxa de proporção das propriedades, a taxa de quadros (somente para propriedades de codificação de vídeo) e um nome amigável que facilita a exibição das propriedades de codificação na interface do usuário do aplicativo.

Você deve incluir o namespace Windows.Media.MediaProperties no arquivo de origem para a classe auxiliar.

using Windows.Media.MediaProperties;
using Windows.Media.Capture.Frames;
class StreamPropertiesHelper
{
    private IMediaEncodingProperties _properties;

    public StreamPropertiesHelper(IMediaEncodingProperties properties)
    {
        if (properties == null)
        {
            throw new ArgumentNullException(nameof(properties));
        }

        // This helper class only uses VideoEncodingProperties or VideoEncodingProperties
        if (!(properties is ImageEncodingProperties) && !(properties is VideoEncodingProperties))
        {
            throw new ArgumentException("Argument is of the wrong type. Required: " + typeof(ImageEncodingProperties).Name
                + " or " + typeof(VideoEncodingProperties).Name + ".", nameof(properties));
        }

        // Store the actual instance of the IMediaEncodingProperties for setting them later
        _properties = properties;
    }

    public uint Width
    {
        get
        {
            if (_properties is ImageEncodingProperties)
            {
                return (_properties as ImageEncodingProperties).Width;
            }
            else if (_properties is VideoEncodingProperties)
            {
                return (_properties as VideoEncodingProperties).Width;
            }

            return 0;
        }
    }

    public uint Height
    {
        get
        {
            if (_properties is ImageEncodingProperties)
            {
                return (_properties as ImageEncodingProperties).Height;
            }
            else if (_properties is VideoEncodingProperties)
            {
                return (_properties as VideoEncodingProperties).Height;
            }

            return 0;
        }
    }

    public uint FrameRate
    {
        get
        {
            if (_properties is VideoEncodingProperties)
            {
                if ((_properties as VideoEncodingProperties).FrameRate.Denominator != 0)
                {
                    return (_properties as VideoEncodingProperties).FrameRate.Numerator / 
                        (_properties as VideoEncodingProperties).FrameRate.Denominator;
                }
           }

            return 0;
        }
    }

    public double AspectRatio
    {
        get { return Math.Round((Height != 0) ? (Width / (double)Height) : double.NaN, 2); }
    }

    public IMediaEncodingProperties EncodingProperties
    {
        get { return _properties; }
    }

    public string GetFriendlyName(bool showFrameRate = true)
    {
        if (_properties is ImageEncodingProperties ||
            !showFrameRate)
        {
            return Width + "x" + Height + " [" + AspectRatio + "] " + _properties.Subtype;
        }
        else if (_properties is VideoEncodingProperties)
        {
            return Width + "x" + Height + " [" + AspectRatio + "] " + FrameRate + "FPS " + _properties.Subtype;
        }

        return String.Empty;
    }
    
}

Determinar se os fluxos de visualização e captura são independentes

Em alguns dispositivos, o mesmo pino de hardware é usado para fluxos de visualização e captura. Nesses dispositivos, definir as propriedades de codificação de um também definirá o outro. Em dispositivos que usam pinos de hardware diferentes para captura e visualização, as propriedades podem ser definidas para cada fluxo de forma independente. Use o código a seguir para determinar se os fluxos de visualização e captura são independentes. Você deve ajustar sua interface do usuário para habilitar ou desabilitar a configuração dos fluxos de forma independente com base no resultado deste teste.

private void CheckIfStreamsAreIdentical()
{
    if (_mediaCapture.MediaCaptureSettings.VideoDeviceCharacteristic == VideoDeviceCharacteristic.AllStreamsIdentical ||
        _mediaCapture.MediaCaptureSettings.VideoDeviceCharacteristic == VideoDeviceCharacteristic.PreviewRecordStreamsIdentical)
    {
        ShowMessageToUser("Preview and video streams for this device are identical. Changing one will affect the other");
    }
}

Obter uma lista de propriedades de stream disponíveis

Obtenha uma lista das propriedades de fluxo disponíveis para um dispositivo de captura obtendo o VideoDeviceController para o objeto MediaCapture do aplicativo e, em seguida, chamando GetAvailableMediaStreamProperties e passando um dos valores MediaStreamType, VideoPreview, VideoRecord ou Photo. Neste exemplo, a sintaxe Linq é usada para criar uma lista de objetos StreamPropertiesHelper, definidos anteriormente neste artigo, para cada um dos valores IMediaEncodingProperties retornados de GetAvailableMediaStreamProperties. Este exemplo usa primeiro métodos de extensão Linq para ordenar as propriedades retornadas com base primeiro na resolução e, em seguida, na taxa de quadros.

Se seu aplicativo tiver requisitos específicos de resolução ou taxa de quadros, você poderá selecionar um conjunto de propriedades de codificação de mídia programaticamente. Em vez disso, um aplicativo de câmera típico exporá a lista de propriedades disponíveis na interface do usuário e permitirá que o usuário selecione as configurações desejadas. Um ComboBoxItem é criado para cada item na lista de objetos StreamPropertiesHelper na lista. O conteúdo é definido como o nome amigável retornado pela classe auxiliar e a marca é definida como a própria classe auxiliar para que possa ser usada posteriormente para recuperar as propriedades de codificação associadas. Cada ComboBoxItem é adicionado ao ComboBox passado para o método.

private void PopulateStreamPropertiesUI(MediaStreamType streamType, ComboBox comboBox, bool showFrameRate = true)
{
    // Query all properties of the specified stream type 
    IEnumerable<StreamPropertiesHelper> allStreamProperties = 
        _mediaCapture.VideoDeviceController.GetAvailableMediaStreamProperties(streamType).Select(x => new StreamPropertiesHelper(x));

    // Order them by resolution then frame rate
    allStreamProperties = allStreamProperties.OrderByDescending(x => x.Height * x.Width).ThenByDescending(x => x.FrameRate);

    // Populate the combo box with the entries
    foreach (var property in allStreamProperties)
    {
        ComboBoxItem comboBoxItem = new ComboBoxItem();
        comboBoxItem.Content = property.GetFriendlyName(showFrameRate);
        comboBoxItem.Tag = property;
        comboBox.Items.Add(comboBoxItem);
    }
}

Defina as propriedades de fluxo desejadas

Diga ao controlador de dispositivo de vídeo para usar as propriedades de codificação desejadas chamando SetMediaStreamPropertiesAsync, passando o valor MediaStreamType indicando se as propriedades de foto, vídeo ou visualização devem ser definidas. Este exemplo define as propriedades de codificação solicitadas quando o usuário seleciona um item em um dos objetos ComboBox preenchidos com o método auxiliar PopulateStreamPropertiesUI.

private async void PreviewSettings_Changed(object sender, RoutedEventArgs e)
{
    if (_isPreviewing)
    {
        var selectedItem = (sender as ComboBox).SelectedItem as ComboBoxItem;
        var encodingProperties = (selectedItem.Tag as StreamPropertiesHelper).EncodingProperties;
        await _mediaCapture.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.VideoPreview, encodingProperties);
    }
}
private async void PhotoSettings_Changed(object sender, RoutedEventArgs e)
{
    if (_isPreviewing)
    {
        var selectedItem = (sender as ComboBox).SelectedItem as ComboBoxItem;
        var encodingProperties = (selectedItem.Tag as StreamPropertiesHelper).EncodingProperties;
        await _mediaCapture.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.Photo, encodingProperties);
    }
}
private async void VideoSettings_Changed(object sender, RoutedEventArgs e)
{
    if (_isPreviewing)
    {
        var selectedItem = (sender as ComboBox).SelectedItem as ComboBoxItem;
        var encodingProperties = (selectedItem.Tag as StreamPropertiesHelper).EncodingProperties;
        await _mediaCapture.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.VideoRecord, encodingProperties);
    }
}

Corresponder à proporção dos fluxos de visualização e captura

Um aplicativo de câmera típico fornecerá interface do usuário para o usuário selecionar a resolução de captura de vídeo ou foto, mas definirá programaticamente a resolução de visualização. Há algumas estratégias diferentes para selecionar a melhor resolução de fluxo de visualização para seu aplicativo:

  • Selecione a resolução de visualização mais alta disponível, permitindo que a estrutura da interface do usuário execute qualquer dimensionamento necessário da visualização.

  • Selecione a resolução de visualização mais próxima da resolução de captura para que a visualização exiba a representação mais próxima da mídia capturada final.

  • Selecione a resolução de visualização mais próxima do tamanho do CaptureElement para que não haja mais pixels do que o necessário passando pelo pipeline de fluxo de visualização.

Importante É possível, em alguns dispositivos, definir uma proporção diferente para o fluxo de visualização e o fluxo de captura da câmera. O corte de quadros causado por essa incompatibilidade pode resultar na presença de conteúdo na mídia capturada que não estava visível na visualização, o que pode resultar em uma experiência negativa do usuário. É altamente recomendável que você use a mesma proporção, dentro de uma pequena janela de tolerância, para os fluxos de visualização e captura. Não há problema em ter resoluções totalmente diferentes habilitadas para captura e visualização, desde que a proporção corresponda de perto.

Para garantir que os fluxos de captura de foto ou vídeo correspondam à taxa de proporção do fluxo de visualização, este exemplo chama VideoDeviceController.GetMediaStreamProperties e passa o valor de enumeração VideoPreview para solicitar as propriedades atuais do fluxo para o fluxo de visualização. Em seguida, uma pequena janela de tolerância de proporção é definida para que possamos incluir proporções que não são exatamente as mesmas do fluxo de visualização, desde que estejam próximas. Em seguida, um método de extensão Linq é usado para selecionar apenas os objetos StreamPropertiesHelper em que a taxa de proporção está dentro do intervalo de tolerância definido do fluxo de visualização.

private void MatchPreviewAspectRatio(MediaStreamType streamType, ComboBox comboBox)
{
    // Query all properties of the specified stream type
    IEnumerable<StreamPropertiesHelper> allVideoProperties = 
        _mediaCapture.VideoDeviceController.GetAvailableMediaStreamProperties(streamType).Select(x => new StreamPropertiesHelper(x));

    // Query the current preview settings
    StreamPropertiesHelper previewProperties = new StreamPropertiesHelper(_mediaCapture.VideoDeviceController.GetMediaStreamProperties(MediaStreamType.VideoPreview));

    // Get all formats that have the same-ish aspect ratio as the preview
    // Allow for some tolerance in the aspect ratio comparison
    const double ASPECT_RATIO_TOLERANCE = 0.015;
    var matchingFormats = allVideoProperties.Where(x => Math.Abs(x.AspectRatio - previewProperties.AspectRatio) < ASPECT_RATIO_TOLERANCE);

    // Order them by resolution then frame rate
    allVideoProperties = matchingFormats.OrderByDescending(x => x.Height * x.Width).ThenByDescending(x => x.FrameRate);

    // Clear out old entries and populate the video combo box with new matching entries
    comboBox.Items.Clear();
    foreach (var property in allVideoProperties)
    {
        ComboBoxItem comboBoxItem = new ComboBoxItem();
        comboBoxItem.Content = property.GetFriendlyName();
        comboBoxItem.Tag = property;
        comboBox.Items.Add(comboBoxItem);
    }
    comboBox.SelectedIndex = -1;
}