設定 MediaCapture 的格式、解析度和畫面播放速率
本文示範如何使用 IMediaEncodingProperties 介面,設定相機預覽串流以及擷取的相片和影片的解析度和畫面播放速率。 這也示範了如何確保預覽串流的外觀比例與所擷取媒體的外觀比例相符。
相機設定檔提供更進階的方式來探索和設定相機的串流屬性,但並非所有裝置都支援它們。 更多詳細資訊,請參閱相機設定檔。
本文中的程式碼改編自 CameraResolution 範例。 您可以下載範例以查看內容中使用的程式碼,或使用該範例做為您自己的應用程式的起點。
注意
本文以使用 MediaCapture 進行基本相片、視訊和音訊的擷取中所討論的概念和程式碼為基礎,說明實作基本相片和視訊擷取的步驟。 建議您先熟悉該文章中的基本媒體擷取模式後,再繼續進行更進階的擷取案例。 本文中的程式碼假設您的應用程式已經有已正確初始化的 MediaCapture 執行個體。
媒體編碼屬性協助程式類別
建立簡單的協助程式類別來包裝 IMediaEncodingProperties 介面的功能,可讓您更輕鬆地選取一組符合特定準則的編碼屬性。 由於編碼屬性功能的下列行為,此協助程式類別特別有用:
警告:VideoDeviceController.GetAvailableMediaStreamProperties 方法採用 MediaStreamType 列舉的成員 (例如 VideoRecord 或 Photo),並傳回 ImageEncodingProperties 或 VideoEncodingProperties 物件的清單,這些物件傳達串流編碼設定 (例如擷取的相片或影片的解析度)。 呼叫 GetAvailableMediaStreamProperties 的結果可能包含 ImageEncodingProperties 或 VideoEncodingProperties,而不論指定了哪些 MediaStreamType 值。 基於這個理由,您應該一律檢查每個傳回值的型別,並將它轉換成適當的類型,然後再嘗試存取任何屬性值。
下面定義的協助程式類別會處理 ImageEncodingProperties 或 VideoEncodingProperties 的類型檢查和轉換,因此您的應用程式程式碼不需要區分這兩種類型。 此外,協助程式類別也會公開屬性的外觀比例、畫面速率 (僅限視訊編碼屬性),以及易記名稱,讓您更輕鬆地在應用程式的 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 值、VideoPreview、VideoRecord 或 Photo。 在此範例中,Linq 語法可用來針對 GetAvailableMediaStreamProperties 傳回的每個 IMediaEncodingProperties 值,建立本文先前定義的 StreamPropertiesHelper 物件清單。 此範例會先使用 Linq 擴充方法,先根據解析度,再根據畫面速率來排序傳回的屬性。
如果您的應用程式有特定的解析度或畫面速率需求,您可以透過程式設計方式選取一組媒體編碼屬性。 一般的相機應用程式會改為公開 UI 中可用的屬性清單,並允許使用者選取其所需的設定。 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);
}
}
比對預覽和擷取串流的外觀比例
典型的相機應用程式會為使用者提供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;
}