共用方式為


在 WinUI 3 應用程式中設定 MediaCapture 的格式、解析度和幀速率

本文說明如何使用 IMediaEncodingProperties 介面來設定相機預覽串流解析度和幀速率,以及擷取的相片和視訊。 它也會示範如何確保預覽串流的縱橫比符合所擷取媒體的縱橫比。

相機配置檔提供更簡單、更高層級的機制來探索和設定相機的串流屬性,但並非所有裝置都支持它們。 如需詳細資訊,請參閱 相機設定檔

判斷預覽和擷取數據流是否獨立

在某些裝置上,相同的硬體針腳用於預覽和擷取串流。 在這些裝置上,在一個裝置上設定格式、解析度和幀速率等編碼屬性會影響兩者。 在使用不同硬體針腳進行擷取和預覽的裝置上,可以個別設定每個數據流的屬性。 使用下列程式代碼來判斷預覽和擷取數據流是否獨立。 本範例會設定布爾全域變數,當數據流為共用或獨立時,可用來切換應用程式的行為。

if (m_mediaCapture.MediaCaptureSettings.VideoDeviceCharacteristic == VideoDeviceCharacteristic.AllStreamsIdentical ||
    m_mediaCapture.MediaCaptureSettings.VideoDeviceCharacteristic == VideoDeviceCharacteristic.PreviewRecordStreamsIdentical)
{
    m_captureAndPreviewStreamsIdentical = true;
}

媒體編碼屬性輔助類別

建立簡單的協助程式類別來包裝 IMediaEncodingProperties 介面的功能,可讓您更輕鬆地選取符合特定準則的編碼屬性集。 由於編碼屬性功能的下列行為,此協助程式類別特別有用:

注意

VideoDeviceController.GetAvailableMediaStreamProperties 方法會使用 MediaStreamType 列舉中的成員,例如 VideoRecordPhoto,並傳回 ImageEncodingPropertiesVideoEncodingProperties 物件的清單,例如所擷取相片或視訊的解析度。 呼叫 GetAvailableMediaStreamProperties 的結果可能包含 ImageEncodingPropertiesVideoEncodingProperties,不論指定了哪些 MediaStreamType 值。 基於這個理由,您應該一律檢查每個傳回值的型別,並將它轉換成適當的類型,然後再嘗試存取任何屬性值。

以下定義的協助程式類別會處理 ImageEncodingPropertiesVideoEncodingProperties 的類型檢查和轉換,因此您的應用程式程式代碼不需要區分這兩種類型。 此外,協助程式類別也會公開屬性的外觀比例、幀速率(僅限視訊編碼屬性),以及易記名稱,可讓您更輕鬆地在UI中顯示編碼屬性。

class StreamPropertiesHelper
{
    private IMediaEncodingProperties _properties;

    public StreamPropertiesHelper(IMediaEncodingProperties properties)
    {
        if (properties == null)
        {
            throw new ArgumentNullException(nameof(properties));
        }

        // This helper class only uses ImageEncodingProperties 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;
    }

}

取得可用資料流屬性的清單

取得擷取裝置可用數據流屬性的清單,方法是取得 app MediaCapture 物件的 VideoDeviceController,然後呼叫 GetAvailableMediaStreamProperties,並傳入其中一個 MediaStreamType 值, VideoPreviewVideoRecordPhoto。 在此範例中,會針對先前在本文中定義的 StreamPropertiesHelper 物件清單,針對從 getAvailableMediaStreamProperties傳 回 值的每個 IMediaEncodingProperties 建立。 此範例會先根據解析度,然後根據幀速率來排序傳回的屬性。

如果您的 app 有特定的解析度或幀速率需求,您可以透過程式設計方式選取一組媒體編碼屬性。 一般的相機應用程式會改為公開UI中可用的屬性清單,並允許用戶選取其所需的設定。 將為清單中的 StreamPropertiesHelper 物件的每個項目建立一個 ComboBoxItem。 內容會設定為協助程式類別傳回的易記名稱,而標記則會設定為協助程式類別本身,以便於稍後擷取其相關的編碼屬性。 接著,每個 ComboBoxItem 都會被加入到 UI 中定義的 ComboBox

private void bGetStreamProperties_Click(object sender, RoutedEventArgs e)
{
    // Query all properties of the specified stream type 
    IEnumerable<StreamPropertiesHelper> allStreamProperties =
        m_mediaCapture.VideoDeviceController.GetAvailableMediaStreamProperties(MediaStreamType.VideoRecord).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();
        comboBoxItem.Tag = property;
        cbStreamProperties.Items.Add(comboBoxItem);
    }
}

設定所需的數據流屬性

藉由呼叫 setMediaStreamPropertiesAsync呼叫 SetMediaStreamPropertiesAsync,告訴影片裝置控制器使用您想要的編碼屬性,並傳入 MediaStreamType 值,指出是否應該設定相片、視訊或預覽屬性。 本範例會使用在上一節中填充的 ComboBox,在該範例中,會從選取項目的標記屬性中擷取出媒體串流屬性。

private async void bSetStreamProperties_Click(object sender, RoutedEventArgs e)
{

    if (m_exclusiveCameraAccess)
    {
        var selectedItem = cbStreamProperties.SelectedItem as ComboBoxItem;
        var encodingProperties = (selectedItem.Tag as StreamPropertiesHelper).EncodingProperties;
        await m_mediaCapture.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.VideoPreview, encodingProperties);
    }
}

請注意,您的應用程式必須擁有擷取裝置的獨佔控制權,才能變更媒體串流屬性。

匹配預覽與擷取流的長寬比

典型的相機應用程式會為使用者提供UI來選取視訊或相片擷取解析度,但會以程式設計方式設定預覽解析度。 為您的應用程式選取最佳預覽串流解析度有幾個不同的策略:

  • 選取最高可用的預覽解析度,讓UI架構執行任何必要的預覽調整。

  • 選取最接近擷取解析度的預覽解析度,讓預覽顯示最接近最終擷取媒體的表示法。

  • 選擇與 CaptureElement 大小最接近的預覽解析度,以確保預覽流管線中僅傳遞必要的像素。

注意

在某些裝置上,可能會為相機的預覽串流和擷取串流設定不同的畫面比例。 此不相符所造成的畫面格裁剪可能會導致內容出現在預覽中看不到的擷取媒體中,這可能會導致負面用戶體驗。 強烈建議您在小型容錯視窗中,針對預覽和擷取串流使用相同的外觀比例。 只要長寬比非常相符,啟用擷取和預覽不同的解析度是完全可以的。

為了確保相片或視訊擷取串流符合預覽串流的長寬比,本範例會呼叫 VideoDeviceController.GetMediaStreamProperties,並傳入 VideoPreview 列舉值,以要求預覽串流目前的屬性。 接下來會定義小型長寬比容忍度窗口,以便我們可以包含不是完全與預覽串流相同的長寬比,只要它們足夠接近。 接下來,選取 StreamPropertiesHelper 物件,其中長寬比位於預覽流所定義的允許誤差範圍內。

// Query all properties of the specified stream type
IEnumerable<StreamPropertiesHelper> allVideoProperties =
    m_mediaCapture.VideoDeviceController.GetAvailableMediaStreamProperties(MediaStreamType.VideoRecord).Select(x => new StreamPropertiesHelper(x));

// Query the current preview settings
StreamPropertiesHelper previewProperties = new StreamPropertiesHelper(m_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
cbStreamProperties.Items.Clear();
foreach (var property in allVideoProperties)
{
    ComboBoxItem comboBoxItem = new ComboBoxItem();
    comboBoxItem.Content = property.GetFriendlyName();
    comboBoxItem.Tag = property;
    cbStreamProperties.Items.Add(comboBoxItem);
}

Snippet比對預覽長寬比