Establecer el formato, la resolución y la velocidad de fotogramas para MediaCapture

En este artículo se muestra cómo usar la interfaz IMediaEncodingProperties para establecer la resolución y la velocidad de fotogramas de la secuencia de vista previa de la cámara y las fotos capturadas y el vídeo. También se muestra cómo asegurarse de que la relación de aspecto de la secuencia de vista previa coincida con la de la secuencia multimedia capturada.

Los perfiles de cámara ofrecen una forma más avanzada de descubrir y establecer las propiedades de la secuencia de la cámara, pero no se admiten en todos los dispositivos. Para obtener más información, consulte Perfiles de cámara.

El código de este artículo es una adaptación de la muestra de CameraResolution. Puedes descargar la muestra para ver el código usado en contexto o para emplearla como punto de partida para tu propia aplicación.

Nota

Este artículo se basa en los conceptos y el código analizados en Captura básica de fotos, audio y vídeo con MediaCapture, donde se describen los pasos para implementar la captura básica de fotos y vídeo. Se recomienda que te familiarices con el patrón de captura de multimedia básico de ese artículo antes de pasar a escenarios más avanzados de captura. El código que encontrarás en este artículo se ha agregado suponiendo que la aplicación ya tiene una instancia de MediaCapture inicializada correctamente.

Clase auxiliar de propiedades de codificación multimedia

Crear una clase auxiliar simple para encapsular la funcionalidad de la interfaz IMediaEncodingProperties facilita la selección de un conjunto de propiedades de codificación que cumplan criterios particulares. Esta clase auxiliar resulta especialmente útil debido al comportamiento de la característica de propiedades de codificación siguiente:

Advertencia El método VideoDeviceController.GetAvailableMediaStreamProperties toma un miembro de la enumeración MediaStreamType , como VideoRecord o Photo, y devuelve una lista de objetos ImageEncodingProperties o VideoEncodingProperties que transmiten la configuración de codificación de secuencias, como la resolución de la foto o el vídeo capturados. Los resultados de la llamada a GetAvailableMediaStreamProperties puede incluir ImageEncodingProperties o VideoEncodingProperties, independientemente de qué valor MediaStreamType se especifique. Por este motivo, siempre debe comprobar el tipo de cada valor devuelto y convertirlo al tipo apropiado antes de intentar acceder a cualquiera de los valores de propiedad.

La clase auxiliar que se define a continuación controla la comprobación y la conversión del tipo de ImageEncodingProperties o VideoEncodingProperties para que el código de la aplicación no tenga que distinguir entre los dos tipos. Además, la clase auxiliar expone las propiedades de la relación de aspecto de las propiedades, la velocidad de fotogramas (solo de las propiedades de codificación de vídeo) y un nombre descriptivo que facilita la visualización de las propiedades de codificación en la interfaz de usuario de la aplicación.

Debes incluir el espacio de nombres Windows.Media.MediaProperties en el archivo de origen de la clase 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 si las secuencias de vista previa y captura son independientes

En algunos dispositivos, se usa el mismo pin de hardware para las secuencias de vista previa y captura. En estos dispositivos, al establecer las propiedades de codificación de uno también se establecen las del otro. En los dispositivos que usan pines de hardware diferentes para la vista previa y la captura, las propiedades pueden establecerse por separado para cada secuencia. Usa el siguiente código para determinar si las capturas de vista previa y captura son independientes. Debes ajustar la interfaz de usuario para habilitar o deshabilitar la configuración de las secuencias de forma independiente en función del resultado de esta prueba.

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

Obtener una lista de propiedades de secuencia disponibles

Obtén una lista de las propiedades de secuencia disponibles para un dispositivo de captura obteniendo el VideoDeviceController del objeto MediaCapture de tu aplicación y, después, llama a GetAvailableMediaStreamProperties y pasa uno de los valores de MediaStreamType, VideoPreview, VideoRecord o Photo. En este ejemplo, se usa la sintaxis de Linq para crear una lista de objetos StreamPropertiesHelper, que se definió anteriormente en este artículo, para cada uno de los valores de IMediaEncodingProperties devueltos de GetAvailableMediaStreamProperties. En primer lugar, en este ejemplo se usan los métodos de extensión Linq para ordenar las propiedades devueltas primero según la resolución y, luego, según la velocidad de fotogramas.

Si la aplicación tiene requisitos de velocidad de fotogramas o de resolución específicos, puedes seleccionar un conjunto de propiedades de codificación multimedia mediante programación. En su lugar, una aplicación de cámara típica expondrá la lista de propiedades disponibles en la interfaz de usuario y permitirá que el usuario seleccione la configuración que desee. Se crea un ComboBoxItem para cada elemento de la lista de objetos StreamPropertiesHelper de la lista. El contenido se establece en el nombre descriptivo devuelto por la clase auxiliar y la etiqueta se establece en la clase auxiliar en sí, para que se pueda usar más adelante con el fin de recuperar las propiedades de codificación asociadas. Cada objeto ComboBoxItem se agrega posteriormente al objeto ComboBox que se pasa al 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);
    }
}

Establecer las propiedades de secuencia deseadas

Indica al controlador de dispositivo de vídeo que use tus propiedades de codificación deseadas, para lo cual debe llamar a SetMediaStreamPropertiesAsync y pasar el valor MediaStreamType que indica si se deben establecer las propiedades de foto, vídeo o vista previa. En este ejemplo se establecen las propiedades de codificación solicitadas cuando el usuario selecciona un elemento en uno de los objetos ComboBox que se rellenan con el 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);
    }
}

Coincidir con la relación de aspecto de las secuencias de vista previa y captura

Una aplicación de cámara típica proporcionará la interfaz de usuario para el usuario de modo que pueda seleccionar la resolución de captura de vídeo o foto, pero establecerá mediante programación la resolución de vista previa. Hay algunas estrategias diferentes para seleccionar la mejor resolución de secuencia de vista previa de la aplicación:

  • Selecciona la mayor resolución de vista previa disponible, dejando que el marco de trabajo de la interfaz de usuario realice la escala necesaria para la vista previa.

  • Selecciona la resolución de vista previa más cercana a la resolución de captura para que la vista previa muestre la representación más cercana al contenido multimedia capturado final.

  • Selecciona la resolución de vista previa más cercana al tamaño del objeto CaptureElement, de modo que no se canalicen en la secuencia de vista previa más píxeles que los que se necesiten.

Importante Es posible, en algunos dispositivos, establecer una relación de aspecto diferente para la secuencia de vista previa de la cámara y la secuencia de captura. El recorte de fotogramas que provoca este error de coincidencia puede dar como resultado contenido presente en los archivos multimedia capturados que no eran visibles en la vista previa, lo que puede provocar una experiencia de usuario negativa. Se recomienda encarecidamente usar la misma relación de aspecto, con un período de tolerancia reducido, para las secuencias de vista previa y captura. Se pueden tener resoluciones totalmente diferentes habilitadas para la captura y la vista previa, siempre que la relación de aspecto sea muy aproximada.

Para garantizar que las secuencias de captura de foto o vídeo coincidan con la relación de aspecto de la secuencia de vista previa, este ejemplo llama a VideoDeviceController.GetMediaStreamProperties y pasa el valor de enumeración VideoPreview para solicitar las propiedades de secuencia actuales de la secuencia de vista previa. A continuación, se define un período de tolerancia de relación de aspecto reducido para que se puedan incluir relaciones de aspecto que no sean exactamente iguales que las de la secuencia de vista previa, siempre que sean aproximadas. A continuación, se usa un método de extensión Linq para seleccionar solo los objetos StreamPropertiesHelper donde la relación de aspecto se encuentra dentro del intervalo de tolerancia definido de la secuencia de vista previa.

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