为 MediaCapture 设置格式、分辨率和帧速率

本文向你演示如何使用 IMediaEncodingProperties 界面设置相机预览流以及已捕获照片和视频的分辨率和帧速率。 还将展示如何确保预览流的纵横比与已捕获媒体的纵横比相匹配。

相机配置文件提供发现和设置相机流属性的更高级方法,但并非所有设备均支持这些文件。 有关详细信息,请参阅相机配置文件

本文中的代码源自 CameraResolution 示例。 你可以下载该示例以查看上下文中使用的代码,或将该示例用作你自己的应用的起始点。

注意

本文以使用 MediaCapture 捕获基本的照片、视频和音频中讨论的概念和代码为基础,该文章介绍了实现基本照片和视频捕获的步骤。 建议你先熟悉该文中的基本媒体捕获模式,然后再转到更高级的捕获方案。 本文中的代码假设你的应用已有一个正确完成初始化的 MediaCapture 的实例。

媒体编码属性帮助程序类

创建包装 IMediaEncodingProperties 界面功能的简单帮助程序类使选择一组符合特定标准的编码属性变得更简单。 此帮助程序类因具有以下编码属性功能的行为而极为有用:

警告VideoDeviceController.GetAvailableMediaStreamProperties 方法获取 MediaStreamType 枚举的成员(例如 VideoRecord 或 Photo),并返回 ImageEncodingPropertiesVideoEncodingProperties 对象的列表,这些对象可传达流编码设置,例如已捕获照片或视频的分辨率。 调用 GetAvailableMediaStreamProperties 的结果可能包括 ImageEncodingPropertiesVideoEncodingProperties,不论指定的 MediaStreamType 值是什么。 出于此原因,在尝试访问任何属性值之前,你都应始终查看每个返回的值的类型并将其转换到相应类型。

以下定义的帮助程序类为 ImageEncodingPropertiesVideoEncodingProperties 处理类型检查和转换,因此你的应用代码无需分辨这两种类型。 除此之外,此帮助程序类还会公开属性的纵横比属性、帧速率(仅适用于视频编码属性)以及使在应用 UI 中显示编码属性变得更简单的友好名称。

必须将 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;
    }
    
}

确定预览流和捕获流是否独立。

在某些设备上,相同的硬件引脚可同时用于预览流和捕获流。 在这些设备上,设置某个流的编码属性也将设置其他流的编码属性。 在将不同的硬件引脚用于捕获和预览的设备上,可单独为每个流设置属性。 使用以下代码确定预览流和捕获流是否独立。 你应该根据此测试结果,将 UI 调整为独立启用或禁用流设置。

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

获取可用流属性列表

通过获取应用 MediaCapture 对象的 VideoDeviceController,然后调用 GetAvailableMediaStreamProperties 并在 MediaStreamType 值的 VideoPreviewVideoRecordPhoto 的其中一项属性中传递,获取捕获设备的可用流属性列表。 在此示例中,Linq 语法用于为从 GetAvailableMediaStreamProperties 返回的每个 IMediaEncodingProperties 值创建 StreamPropertiesHelper 对象(在本文前文中定义)列表。 该示例首先使用 Linq 扩展方法先基于分辨率随后基于帧速率为返回的属性排序。

如果你的应用具有特定分辨率或帧速率要求,你可以按编程方式选择一组媒体编码属性。 典型的相机应用将公开 UI 中的可用属性列表,并允许用户选择所需设置。 在列表中为 StreamPropertiesHelper 对象列表中的每个项创建 ComboBoxItem。 内容设置为帮助程序类返回的友好名称,而标记设置为帮助程序类本身,以便可以在稍后用于检索相关联的编码属性。 然后将每个 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,告知视频设备控制器使用所需的编码属性。 本示例在用户选择使用 PopulateStreamPropertiesUI 帮助程序方法填充的 ComboBox 对象之一中的项目时,设置所请求的编码属性。

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

匹配预览流和捕获流的纵横比

典型相机应用会为用户提供用于选择视频或照片捕获分辨率的 UI,但会以编程方式设置预览分辨率。 有以下几种不同的策略可用于选择最适合你的应用的预览流分辨率:

  • 选择最高可用的预览分辨率,从而让 UI 框架对预览执行任何必要的缩放。

  • 选择最接近捕获分辨率的预览分辨率,以便预览向最终捕获的媒体显示最接近的表示形式。

  • 选择最接近 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;
}