Настройка формата, разрешения и частоты кадров для 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;
}