Установка формата, разрешения и частоты кадров для MediaCapture

В этой статье рассказывается, как с помощью интерфейса IMediaEncodingProperties задать разрешение и частоту кадров потока предварительного просмотра камеры, а также снятых фотографий и видео. Кроме того, здесь рассказывается, как обеспечить соответствие пропорций потока предварительного просмотра пропорциям записи мультимедиа.

Профили камеры предлагают более современный способ обнаружения и задания свойств потока камеры, но они поддерживаются не на всех устройствах. Дополнительные сведения см. в разделе Профили камеры.

В этой статье используется код, адаптированный из примера CameraResolution. Вы можете скачать этот пример, чтобы просмотреть код в контексте или использовать пример как отправную точку для настройки собственного приложения.

Примечание

В этой статье используются понятия и код из статьи Основные принципы фото-, аудио- и видеозахвата с помощью MediaCapture, в которой описаны этапы реализации основных принципов фото- и видеозахвата. Мы рекомендуем ознакомиться с базовым шаблоном захвата мультимедиа в этой статье, прежде чем перейти к более сложным сценариям захвата. Код в этой статье подразумевает, что ваше приложение уже содержит экземпляр MediaCapture, инициализированный надлежащим образом.

Вспомогательный класс свойств кодирования мультимедиа

Если создать простой вспомогательный класс, включающий функции интерфейса IMediaEncodingProperties, будет удобнее выбирать набор свойств кодирования, которые отвечают конкретным критериям. Этот вспомогательный класс особенно полезен для следующего поведения свойств кодирования.

Предупреждение Метод VideoDeviceController.GetAvailableMediaStreamProperties принимает элемент перечисления MediaStreamType , например VideoRecord или Photo, и возвращает список объектов ImageEncodingProperties или VideoEncodingProperties , которые передают параметры кодирования потока, такие как разрешение захваченной фотографии или видео. Результаты вызова GetAvailableMediaStreamProperties могут включать ImageEncodingProperties или VideoEncodingProperties, независимо от того, какое указано значение MediaStreamType. По этой причине необходимо всегда проверять тип каждого возвращенного значения и приводить его к соответствующему типу, прежде чем пытаться получить доступ к какому-либо значению свойства.

Вспомогательный класс, определенный ниже, выполняет проверку и приведение типа для параметра ImageEncodingProperties или VideoEncodingProperties, чтобы коду приложения не нужно было различать два типа. В дополнение к этому, вспомогательный класс представляет свойства для пропорций свойств, частоты кадров (только для свойств кодирования видео) и понятного имени, что упрощает отображение свойств кодирования в пользовательском интерфейсе приложения.

Вы должны включить пространство имен Windows.Media.MediaProperties в исходный файл для вспомогательного класса.

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

Определение независимости потоков предварительного просмотра и захвата

На некоторых устройствах один и тот же контакт оборудования используется как для потока предварительного просмотра, так и для потока захвата. На таких устройствах при задании свойств кодирования для одного потока свойства задаются и для другого потока. На устройствах, на которых для захвата и предварительного просмотра используются разные контакты, свойства можно задать для каждого потока независимо. Используйте следующий код, чтобы определить, независимы ли потоки предварительного просмотра и захвата. Необходимо настроить пользовательский интерфейс, чтобы он включал и отключал параметр потоков независимо на основе результата этой проверки.

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

Получение списка доступных свойств потока

Чтобы получить список доступных свойств потока для устройства захвата, получите VideoDeviceController для объекта MediaCapture своего приложения, а затем вызовите GetAvailableMediaStreamProperties и передайте одно из значений MediaStreamType, VideoPreview, VideoRecord или Photo. В этом примере синтаксис Linq используется для создания списка объектов StreamPropertiesHelper, определенных ранее в этой статье, для каждого из значений IMediaEncodingProperties, которые возвращаются функцией GetAvailableMediaStreamProperties. В этом примере методы расширения Linq сначала используются для расстановки возвращаемых свойств сперва на основе разрешения, а потом уже на основе частоты кадров.

Если у вашего приложения есть конкретные требования к разрешению или частоте кадров, можно выбрать набор свойств кодирования мультимедиа программным путем. Тогда обычное приложение камеры отобразит список доступных свойств в пользовательском интерфейсе и позволит пользователю выбрать нужные параметры. ComboBoxItem создается для каждого элемента в списке объектов StreamPropertiesHelper в списке. Для содержимого устанавливается понятное имя, возвращенное вспомогательным классом, а для тега устанавливается сам вспомогательный класс, чтобы его можно было использовать позже для получения связанных свойств кодирования. Каждый элемент ComboBoxItem затем добавляется в объект ComboBox, который передается в метод.

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

Установка необходимых свойств потока

Чтобы контроллер видеоустройств использовал нужные свойства кодирования, вызовите SetMediaStreamPropertiesAsync и передайте значение MediaStreamType, определяющее, нужно ли задавать свойства фотографии, видео или предварительного просмотра. В этом примере задаются запрашиваемые свойства кодирования, когда пользователь выбирает элемент в одном из объектов ComboBox, которые заполняются вспомогательным методом 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);
    }
}

Сопоставление пропорций потоков предварительного просмотра и захвата

Типичное приложение камеры предоставляет пользовательский интерфейс для выбора разрешения захвата видео или фото, но разрешение предварительного просмотра устанавливает программным способом. Существуют несколько стратегий выбора наилучшего разрешения потока предварительного просмотра для вашего приложения.

  • Выберите наивысшее доступное разрешение предварительного просмотра, предоставив инфраструктуре пользовательского интерфейса выполнять необходимое масштабирование предварительного просмотра.

  • Выберите разрешение предварительного просмотра, наиболее близкое к разрешению захвата, чтобы предварительный просмотр был как можно ближе к итоговому захваченному мультимедиа.

  • Выберите разрешение предварительного просмотра, наиболее близкое к размеру CaptureElement, чтобы через конвейер потока предварительного просмотра проходило столько пикселов, сколько нужно, но не более того.

Важно На некоторых устройствах можно задать другое соотношение сторон для потока предварительного просмотра и захвата камеры. Обрезка кадра в результате такого несоответствия может привести к наличию в записи мультимедиа содержимого, которого не было видно в режиме предварительного просмотра, что может вызвать недовольство пользователей. Настоятельно рекомендуется использовать одинаковые пропорции (с небольшим диапазоном допуска) для потоков предварительного просмотра и захвата. Можно иметь совершенно различные разрешения, доступные для захвата и предварительного просмотра, пока их пропорции максимально соответствуют друг другу.

Чтобы обеспечить соответствие потоков фото- и видеозахвата пропорциям потока предварительного просмотра, в этом примере вызывается VideoDeviceController.GetMediaStreamProperties и передается значение перечисления VideoPreview для запроса текущих свойств потока предварительного просмотра. Затем определяется небольшой диапазон допуска для пропорций, чтобы можно было включать пропорции, которые не совпадают с потоком предварительного просмотра полностью, но которые близки к этому. Далее метод расширения Linq используется для выбора только тех объектов StreamPropertiesHelper, пропорции которых входят в указанный диапазон допуска потока предварительного просмотра.

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