MediaCapture용 형식, 해상도 및 프레임 속도 선택

이 문서에서는 IMediaEncodingProperties 인터페이스를 사용하여 카메라 미리 보기 스트림 및 캡처된 사진 및 비디오의 해상도 및 프레임 속도를 설정하는 방법을 보여 줍니다. 또한 미리 보기 스트림의 가로 세로 비율이 캡처된 미디어의 가로 세로 비율과 일치하는지 확인하는 방법도 보여줍니다.

카메라 프로필은 카메라의 스트림 속성을 검색하고 설정하는 고급 방법을 제공하지만 모든 디바이스에서 지원되지는 않습니다. 자세한 내용은 Camera profiles를 참조하세요.

이 문서의 코드는 UWP CameraResolution 샘플에서 조정되었습니다. 샘플을 다운로드하여 컨텍스트에서 사용된 코드를 보거나 사용자 고유의 앱의 시작점으로 사용할 수 있습니다.

참고 항목

이 문서는 기본 사진 및 비디오 캡처 구현 단계를 설명하는 MediaCapture를 사용한 기본적인 사진, 비디오 및 오디오 캡처에 설명된 개념 및 코드를 토대로 작성되었습니다. 좀 더 수준 높은 캡처 시나리오를 진행하기 전에 해당 문서의 기본 미디어 캡처 패턴을 좀 더 잘 이해하는 것이 좋습니다. 이 문서의 코드는 앱에 적절히 초기화된 MediaCapture의 인스턴스가 이미 있다고 가정합니다.

미디어 인코딩 속성 도우미 클래스

IMediaEncodingProperties 인터페이스의 기능을 래핑하는 간단한 도우미 클래스를 만들면 특정 조건을 충족하는 인코딩 속성 집합을 더 쉽게 선택할 수 있습니다. 이 도우미 클래스는 인코딩 속성 기능의 다음 동작으로 인해 특히 유용합니다.

경고VideoDeviceController.GetAvailableMediaStreamProperties 메서드는 VideoRecord 또는 Photo와 같은 MediaStreamType 열거형의 멤버를 가져오고, 캡처한 사진 또는 동영상의 해상도와 같은 스트림 인코딩 설정을 전달하는 ImageEncodingProperties 또는 VideoEncodingProperties 개체의 목록을 반환합니다. GetAvailableMediaStreamProperties를 호출한 결과에는 지정된 MediaStreamType 값에 관계없이 ImageEncodingProperties 또는 VideoEncodingProperties가 포함될 수 있습니다. 이러한 이유로 반환된 각 값의 형식을 항상 검사 속성 값에 액세스하기 전에 해당 형식으로 캐스팅해야 합니다.

아래에 정의된 도우미 클래스는 앱 코드가 두 형식을 구분할 필요가 없도록 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에서 사용 가능한 속성 목록을 노출하고 사용자가 원하는 설정을 선택할 수 있도록 합니다. 목록의 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;
}