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 manera más avanzada de detectar y establecer las propiedades de secuencia de la cámara, pero no son compatibles con todos los dispositivos. Para obtener más información, consulte Perfiles de cámara.

El código de este artículo se adaptó del ejemplo CameraResolution. Puede descargar el ejemplo para ver el código usado en contexto o para usar el ejemplo como punto de partida para su 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.

Una clase auxiliar de propiedades de codificación multimedia

Crear una clase auxiliar sencilla para ajustar la funcionalidad de la interfaz IMediaEncodingProperties facilita la selección de un conjunto de propiedades de codificación que cumplen determinados criterios. Esta clase auxiliar es especialmente útil debido al siguiente comportamiento de la característica de propiedades de codificación:

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 llamar a GetAvailableMediaStreamProperties pueden incluir ImageEncodingProperties o VideoEncodingProperties independientemente del valor mediaStreamType especificado. Por este motivo, siempre debe comprobar el tipo de cada valor devuelto y convertirlo al tipo adecuado antes de intentar acceder a cualquiera de los valores de propiedad.

La clase auxiliar definida a continuación controla la comprobación y conversión de tipos para ImageEncodingProperties o VideoEncodingProperties para que el código de la aplicación no necesite distinguir entre los dos tipos. Además de esto, la clase auxiliar expone propiedades para la relación de aspecto de las propiedades, la velocidad de fotogramas (solo para 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.

Debe 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, establecer las propiedades de codificación de una también establecerá la otra. En los dispositivos que usan diferentes patillas de hardware para la captura y vista previa, las propiedades se pueden establecer para cada secuencia de forma independiente. Use el código siguiente para determinar si las secuencias de vista previa y captura son independientes. Debe 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 las propiedades de flujo disponibles

Obtenga una lista de las propiedades de secuencia disponibles para un dispositivo de captura obteniendo videoDeviceController para el objeto MediaCapture de la aplicación y, a continuación, llamando a GetAvailableMediaStreamProperties y pasando uno de los valores MediaStreamType, VideoPreview, VideoRecord o Photo. En este ejemplo, se usa la sintaxis linq para crear una lista de objetos StreamPropertiesHelper, definidos anteriormente en este artículo, para cada uno de los valores IMediaEncodingProperties devueltos de GetAvailableMediaStreamProperties. En este ejemplo se usan primero métodos de extensión Linq para ordenar las propiedades devueltas en función de la resolución y, a continuación, de la velocidad de fotogramas.

Si la aplicación tiene requisitos específicos de resolución o velocidad de fotogramas, puede 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á al usuario seleccionar su configuración deseada. 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 propia clase auxiliar para que se pueda usar más adelante para recuperar las propiedades de codificación asociadas. A continuación, cada comboBoxItem se agrega al comboBox pasado 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);
    }
}

Establecimiento de las propiedades de flujo deseadas

Indique al controlador de dispositivo de vídeo que use las propiedades de codificación deseadas mediante una llamada a SetMediaStreamPropertiesAsync, pasando el valor MediaStreamType que indica si se deben establecer las propiedades photo, video o preview. En este ejemplo se establecen las propiedades de codificación solicitadas cuando el usuario selecciona un elemento en uno de los objetos ComboBox rellenados 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);
    }
}

Coincidencia de 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 que el usuario seleccione 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 secuencias en versión preliminar para la aplicación:

  • Seleccione la resolución de vista previa más alta disponible, lo que permite que el marco de interfaz de usuario realice cualquier escalado necesario de la versión preliminar.

  • Seleccione 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 medio capturado final.

  • Seleccione la resolución de vista previa más cercana al tamaño de CaptureElement para que no haya más píxeles de los necesarios en la canalización de secuencia de vista previa.

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 causado por esta falta de coincidencia puede dar lugar a que el contenido esté presente en los medios capturados que no eran visibles en la vista previa, lo que puede dar lugar a una experiencia de usuario negativa. Se recomienda encarecidamente usar la misma relación de aspecto, dentro de una ventana de tolerancia pequeña, para las secuencias de vista previa y captura. Está bien tener resoluciones completamente diferentes habilitadas para la captura y vista previa siempre que la relación de aspecto coincida estrechamente.

Para asegurarse de que las secuencias de captura de fotos o vídeo coinciden con la relación de aspecto de la secuencia de vista previa, en este ejemplo se llama a VideoDeviceController.GetMediaStreamProperties y se pasa el valor de enumeración VideoPreview para solicitar las propiedades actuales de la secuencia de vista previa. A continuación, se define una ventana de tolerancia de relación de aspecto pequeña para que podamos incluir relaciones de aspecto que no son exactamente iguales que la secuencia de vista previa, siempre y cuando estén cerca. A continuación, se usa un método de extensión Linq para seleccionar solo los objetos StreamPropertiesHelper donde la relación de aspecto está 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;
}