Обработка ориентации устройства с помощью MediaCapture

Когда приложение захватывает фото или видео, предназначенные для просмотр за пределами приложения, например для сохранения в файл на устройстве пользователя или совместного использования в сети, важно закодировать изображение, используя правильные метаданные ориентации, чтобы при отображении в другом приложении или на другом устройстве изображение было правильно ориентировано. Определить правильную ориентацию данных для включения в файл мультимедиа — непростая задача, потому что нужно принять во внимание несколько переменных, включая ориентацию корпуса устройства, ориентацию дисплея и размещение камеры в корпусе (фронтальная или тыловая).

Чтобы упростить работу с ориентацией изображений, рекомендуется использовать вспомогательный класс CameraRotationHelper, полное определение которого дано в конце этой статьи. Можно добавить этот класс в свой проект, а затем выполнить пошаговую инструкцию из этой статьи, чтобы добавить поддержку ориентации в приложение камеры. Кроме того, вспомогательный класс упрощает поворот элементов управления в пользовательском интерфейсе камеры, так что они правильно отображаются для пользователя.

Примечание

Эта статья основана на примерах кода и понятиях, рассматриваемых в статье Основные принципы фото-, аудио- и видеозахвата с помощью MediaCapture. Рекомендуется ознакомиться с базовыми принципами использования класса MediaCapture, прежде чем добавлять поддержку ориентации в приложение.

Пространства имен, используемые в этой статье

В примере кода из этой статьи используются API из следующих пространств имен, которые необходимо включить в код.

using Windows.Devices.Enumeration;
using Windows.UI.Core;

Первым шагом для добавления поддержки ориентации в приложение является блокировка дисплея, чтобы он не поворачивался автоматически при повороте устройства. Автоматический поворот пользовательского интерфейса подходит для большинства типов приложений, но пользователям непонятен поворот образца для предварительного просмотра с камеры. Чтобы заблокировать ориентацию дисплея, присвойте свойству DisplayInformation.AutoRotationPreferences значение DisplayOrientations.Landscape.

DisplayInformation.AutoRotationPreferences = DisplayOrientations.Landscape;

Отслеживание расположения устройства камеры

Чтобы вычислить правильную ориентацию записанного мультимедиа, приложение должно определить расположение камеры на корпусе устройства. Добавьте логическую переменную-член, чтобы отследить, является ли камера внешней по отношению к устройств, например веб-камерой USB. Добавьте другую логическую переменную, чтобы отследить, требуется ли зеркальное отражение предварительного просмотра (как в случае с фронтальной камерой). Добавьте переменную для хранения объекта DeviceInformation, который представляет выбранную камеру.

private bool _externalCamera;
private bool _mirroringPreview;
DeviceInformation _cameraDevice;

Выберите камеру и инициализируйте объект MediaCapture

В статье Основные принципы фото-, аудио- и видеозахвата с помощью MediaCapture показано, как инициализировать объект MediaCapture, используя всего пару строк кода. Чтобы обеспечить поддержку ориентации камеры, добавим еще несколько шагов в процесс инициализации.

Сначала вызовем метод DeviceInformation.FindAllAsync, передав средство выбора устройств DeviceClass.VideoCapture, чтобы получить список всех доступных устройств видеозахвата. Затем выберите первое устройство в списке, если известно расположение панели камеры и если оно соответствует указанному значению (в данном примере это фронтальная камера). Если на нужной панели камера не обнаружена, используется первая доступная камера или камера по умолчанию.

Если устройство камеры обнаружено, создается новый объект MediaCaptureInitializationSettings, а для выбранного устройства задается свойство VideoDeviceId. Затем создайте объект MediaCapture и вызовите метод InitializeAsync, передав объект параметров, чтобы система использовала выбранную камеру.

Наконец, проверьте соответствует ли выбранной панели устройства значение null или «Неизвестно». Если это так, то камера является внешней по отношению к устройству, что означает, что ее поворот не связан с поворотом устройства. Если панель известна и находится в передней части корпуса устройства, мы понимаем, что требуется зеркальное отображение предварительного просмотра, поэтому настраивается отслеживание переменной.

var allVideoDevices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
DeviceInformation desiredDevice = allVideoDevices.FirstOrDefault(x => x.EnclosureLocation != null 
    && x.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front);
_cameraDevice = desiredDevice ?? allVideoDevices.FirstOrDefault();


if (_cameraDevice == null)
{
    System.Diagnostics.Debug.WriteLine("No camera device found!");
    return;
}

var settings = new MediaCaptureInitializationSettings { VideoDeviceId = _cameraDevice.Id };

mediaCapture = new MediaCapture();
mediaCapture.RecordLimitationExceeded += MediaCapture_RecordLimitationExceeded;
mediaCapture.Failed += MediaCapture_Failed;

try
{
    await mediaCapture.InitializeAsync(settings);
}
catch (UnauthorizedAccessException)
{
    System.Diagnostics.Debug.WriteLine("The app was denied access to the camera");
    return;
}

// Handle camera device location
if (_cameraDevice.EnclosureLocation == null || 
    _cameraDevice.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Unknown)
{
    _externalCamera = true;
}
else
{
    _externalCamera = false;
    _mirroringPreview = (_cameraDevice.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front);
}

Инициализация класса CameraRotationHelper

Итак, приступим к использованию класса CameraRotationHelper. Объявите переменную-член класса для хранения объекта. Вызовите конструктор, передав расположение корпуса выбранной камеры. Вспомогательный класс использует эти данные для вычисления нужной ориентации записанного мультимедиа, потока предварительного просмотра и пользовательского интерфейса. Зарегистрируйте обработчик для события OrientationChanged вспомогательного класса, который будет вызываться при необходимости обновления ориентации пользовательского интерфейса или потока предварительного просмотра.

private CameraRotationHelper _rotationHelper;
_rotationHelper = new CameraRotationHelper(_cameraDevice.EnclosureLocation);
_rotationHelper.OrientationChanged += RotationHelper_OrientationChanged;

Добавление данных об ориентации в поток предварительного просмотра камеры

Добавление правильных сведений об ориентации в метаданные потока предварительного просмотра не влияет на то, как предварительный просмотр отображается пользователю, но помогает системе правильно закодировать все кадры, захватываемые из потока предварительного просмотра.

Чтобы запустить предварительный просмотр камеры, вызовите метод MediaCapture.StartPreviewAsync. Перед этим проверьте переменную-член, чтобы узнать, требуется ли зеркальное отображение предварительного просмотра (для фронтальной камеры). В этом случае задайте свойству FlowDirection элемента CaptureElement (в этом примере оно называется PreviewControl) значение FlowDirection.RightToLeft. После запуска предварительного просмотра вызовите вспомогательный метод SetPreviewRotationAsync, чтобы задать поворот предварительного просмотра. Ниже показана реализация этого метода.

PreviewControl.Source = mediaCapture;
PreviewControl.FlowDirection = _mirroringPreview ? FlowDirection.RightToLeft : FlowDirection.LeftToRight;

await mediaCapture.StartPreviewAsync();
await SetPreviewRotationAsync();

Поворот предварительного просмотра задается в отдельном методе, чтобы была возможность выполнить обновление при изменении ориентации телефона, не инициализируя поток предварительного просмотра повторно. Если камера является внешней по отношению к устройству, ничего не происходит. В противном случае вызывается метод CameraRotationHelperGetCameraPreviewOrientation, который возвращает правильную ориентацию для потока предварительного просмотра.

Чтобы задать метаданные, свойства потока предварительного просмотра извлекаются вызовом метода VideoDeviceController.GetMediaStreamProperties. Затем создайте GUID, представляющий атрибут Media Foundation Transform (MFT) для поворота видеопотока. В C++ можно использовать константу MF_MT_VIDEO_ROTATION, однако в C# значение GUID необходимо задать вручную.

Добавьте значение свойства в объект свойств потока, задав GUID в качестве ключа, а поворот предварительного просмотра — в качестве значения. Это свойство принимает значения в градусах против часовой стрелки, поэтому для преобразования простого значения ориентации используется метод CameraRotationHelperConvertSimpleOrientationToClockwiseDegrees. Наконец, вызовите метод SetEncodingPropertiesAsync, чтобы применить к потоку новое свойство поворота.

private async Task SetPreviewRotationAsync()
{
    if (!_externalCamera)
    {
        // Add rotation metadata to the preview stream to make sure the aspect ratio / dimensions match when rendering and getting preview frames
        var rotation = _rotationHelper.GetCameraPreviewOrientation();
        var props = mediaCapture.VideoDeviceController.GetMediaStreamProperties(MediaStreamType.VideoPreview);
        Guid RotationKey = new Guid("C380465D-2271-428C-9B83-ECEA3B4A85C1");
        props.Properties.Add(RotationKey, CameraRotationHelper.ConvertSimpleOrientationToClockwiseDegrees(rotation));
        await mediaCapture.SetEncodingPropertiesAsync(MediaStreamType.VideoPreview, props, null);
    }
}

Затем добавьте обработчик для события CameraRotationHelper.OrientationChanged. Это событие передает аргумент, который указывает, требуется ли поворачивать поток предварительного просмотра. Если ориентация устройства была изменена лицевой стороной вверх или лицевой стороной вниз, это значение будет равно false. Если поворачивать предварительный просмотр не требуется, вызовите ранее определенный метод SetPreviewRotationAsync.

Затем в обработчике событий OrientationChanged при необходимости обновите пользовательский интерфейс. Получите текущую рекомендуемую ориентацию пользовательского интерфейса от вспомогательного класса, вызвав метод GetUIOrientation, и преобразуйте значение в градусы по часовой стрелке (эта система используется для преобразований XAML). Создайте объект RotateTransform на основе значения ориентации и настройте свойство RenderTransform элементов управления XAML. В зависимости от структуры пользовательского интерфейса в дополнение к обычному повороту элементов управления могут потребоваться дополнительные корректировки. Кроме того, помните, что все обновления пользовательского интерфейса должны выполняться в потоке пользовательского интерфейса, поэтому этот код нужно разместить в вызове метода RunAsync.

private async void RotationHelper_OrientationChanged(object sender, bool updatePreview)
{
    if (updatePreview)
    {
        await SetPreviewRotationAsync();
    }
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
        // Rotate the buttons in the UI to match the rotation of the device
        var angle = CameraRotationHelper.ConvertSimpleOrientationToClockwiseDegrees(_rotationHelper.GetUIOrientation());
        var transform = new RotateTransform { Angle = angle };

        // The RenderTransform is safe to use (i.e. it won't cause layout issues) in this case, because these buttons have a 1:1 aspect ratio
        CapturePhotoButton.RenderTransform = transform;
        CapturePhotoButton.RenderTransform = transform;
    });
}

Захват фото с данными об ориентации

В статье Основные принципы фото-, аудио- и видеозахвата с помощью MediaCapture показано, как выполнить захват фото в файл. Сначала выполняется захват в поток в памяти, а затем данные изображения считываются из потока с помощью декодера и перекодируются в файл с помощью кодировщика. Данные об ориентации, полученные из класса CameraRotationHelper, можно добавить в файл изображения во время перекодировки.

В следующем примере выполняется захват фото в поток InMemoryRandomAccessStream с вызовом метода CapturePhotoToStreamAsync, а из потока создается BitmapDecoder. Затем создается и открывается файл StorageFile, чтобы извлечь поток IRandomAccessStream для записи в файл.

Перед перекодированием файла ориентация фото извлекается из метода GetCameraCaptureOrientation вспомогательного класса. Этот метод возвращает объект SimpleOrientation, который преобразуется в PhotoOrientation с вспомогательным методом ConvertSimpleOrientationToPhotoOrientation. Затем создается новый объект BitmapPropertySet и добавляется свойство с ключом System.Photo.Orientation и значением, указывающим на ориентацию фото (значение выражается как BitmapTypedValue). System.Photo.Orientation — это одно из многих свойств Windows, которые можно добавить в файл изображения в качестве метаданных. Список всех связанных с фото свойств см. в разделе Свойства Windows — фото. Дополнительные сведения о работе с метаданными в изображениях см. в разделе Метаданные изображений.

Наконец, для кодировщика задается набор свойств, который включает данные об ориентации. Для этого используется вызов метода SetPropertiesAsync, а для перекодировки изображения — метод FlushAsync.

private async Task CapturePhotoWithOrientationAsync()
{
    var captureStream = new InMemoryRandomAccessStream();

    try
    {
        await mediaCapture.CapturePhotoToStreamAsync(ImageEncodingProperties.CreateJpeg(), captureStream);
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine("Exception when taking a photo: {0}", ex.ToString());
        return;
    }


    var decoder = await BitmapDecoder.CreateAsync(captureStream);
    var file = await KnownFolders.PicturesLibrary.CreateFileAsync("SimplePhoto.jpeg", CreationCollisionOption.GenerateUniqueName);

    using (var outputStream = await file.OpenAsync(FileAccessMode.ReadWrite))
    {
        var encoder = await BitmapEncoder.CreateForTranscodingAsync(outputStream, decoder);
        var photoOrientation = CameraRotationHelper.ConvertSimpleOrientationToPhotoOrientation(
            _rotationHelper.GetCameraCaptureOrientation());
        var properties = new BitmapPropertySet {
            { "System.Photo.Orientation", new BitmapTypedValue(photoOrientation, PropertyType.UInt16) } };
        await encoder.BitmapProperties.SetPropertiesAsync(properties);
        await encoder.FlushAsync();
    }
}

Захват видео с данными об ориентации

Основы захвата видео описаны в статье Основные принципы фото-, аудио- и видеозахвата с помощью MediaCapture. Данные об ориентации добавляются в кодировку захваченного видео с использованием той же техники, которая была описана ранее в разделе о добавлении данных об ориентации в поток предварительного просмотра.

В следующем примере создается файл, в который будет записано захваченное видео. Профиль кодировки MP4 создается с использованием статического метода CreateMp4. Класс CameraRotationHelper позволяет получить данные о правильной ориентации видео вызовом метода GetCameraCaptureOrientation. Поскольку свойство поворота требует выражения ориентации в градусах против часовой стрелки, вспомогательный метод ConvertSimpleOrientationToClockwiseDegrees вызывается для преобразования значения ориентации. Затем создайте GUID, представляющий атрибут Media Foundation Transform (MFT) для поворота видеопотока. В C++ можно использовать константу MF_MT_VIDEO_ROTATION, однако в C# значение GUID необходимо задать вручную. Добавьте значение свойства в объект свойств потока, задав GUID в качестве ключа, а поворот — в качестве значения. Вызовите метод StartRecordToStorageFileAsync, чтобы начать запись видео, кодировка которого содержит данные об ориентации.

private async Task StartRecordingWithOrientationAsync()
{
    try
    {
        var videoFile = await KnownFolders.VideosLibrary.CreateFileAsync("SimpleVideo.mp4", CreationCollisionOption.GenerateUniqueName);

        var encodingProfile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Auto);

        var rotationAngle = CameraRotationHelper.ConvertSimpleOrientationToClockwiseDegrees(
            _rotationHelper.GetCameraCaptureOrientation());
        Guid RotationKey = new Guid("C380465D-2271-428C-9B83-ECEA3B4A85C1");
        encodingProfile.Video.Properties.Add(RotationKey, PropertyValue.CreateInt32(rotationAngle));

        await mediaCapture.StartRecordToStorageFileAsync(encodingProfile, videoFile);
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine("Exception when starting video recording: {0}", ex.ToString());
    }
}

Полный текст кода для метода CameraRotationHelper

В следующем фрагменте кода представлен полный код класса CameraRotationHelper, который управляет датчиками ориентации оборудования, вычисляет правильные значения ориентации для фото и видео и предоставляет вспомогательные методы для преобразования разных представлений ориентации, используемых разными компонентами Windows. Если следовать инструкциям в статье выше, можно добавить этот класс свой проект «как есть», не внося никаких изменений. Конечно, в код можно внести изменения в соответствии с конкретными потребностями.

В этом вспомогательном классе датчик SimpleOrientationSensor устройства используется, чтобы определить текущую ориентацию корпуса устройства, а класс DisplayInformation — чтобы определить текущую ориентацию дисплея. Каждый из этих классов предоставляет события, вызываемые при изменении текущей ориентации. Панель, на которой установлено устройство захвата (фронтальная, тыловая или внешняя), используется, чтобы установить, требуется ли зеркальное отражение потока предварительного просмотра. Кром того, свойство EnclosureLocation.RotationAngleInDegreesClockwise, поддерживаемое некоторыми устройствами, используется для определения ориентации установленной камеры в корпусе.

Чтобы получить рекомендованные значения ориентации для указанных задач приложения камеры, можно использовать следующие методы.

  • GetUIOrientation: возвращает рекомендуемую ориентацию элементов пользовательского интерфейса камеры.
  • GetCameraCaptureOrientation: возвращает рекомендуемую ориентацию для внесения в метаданные изображения в качестве кода.
  • GetCameraPreviewOrientation: возвращает рекомендуемую ориентацию для потока предварительного просмотра, чтобы сделать пользовательский интерфейс более естественным.
class CameraRotationHelper
{
    private EnclosureLocation _cameraEnclosureLocation;
    private DisplayInformation _displayInformation = DisplayInformation.GetForCurrentView();
    private SimpleOrientationSensor _orientationSensor = SimpleOrientationSensor.GetDefault();
    public event EventHandler<bool> OrientationChanged;

    public CameraRotationHelper(EnclosureLocation cameraEnclosureLocation)
    {
        _cameraEnclosureLocation = cameraEnclosureLocation;
        if (!IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            _orientationSensor.OrientationChanged += SimpleOrientationSensor_OrientationChanged;
        }
        _displayInformation.OrientationChanged += DisplayInformation_OrientationChanged;
    }

    private void SimpleOrientationSensor_OrientationChanged(SimpleOrientationSensor sender, SimpleOrientationSensorOrientationChangedEventArgs args)
    {
        if (args.Orientation != SimpleOrientation.Faceup && args.Orientation != SimpleOrientation.Facedown)
        {
            HandleOrientationChanged(false);
        }
    }

    private void DisplayInformation_OrientationChanged(DisplayInformation sender, object args)
    {
        HandleOrientationChanged(true);
    }

    private void HandleOrientationChanged(bool updatePreviewStreamRequired)
    {
        var handler = OrientationChanged;
        if (handler != null)
        {
            handler(this, updatePreviewStreamRequired);
        }
    }

    public static bool IsEnclosureLocationExternal(EnclosureLocation enclosureLocation)
    {
        return (enclosureLocation == null || enclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Unknown);
    }

    private bool IsCameraMirrored()
    {
        // Front panel cameras are mirrored by default
        return (_cameraEnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front);
    }

    private SimpleOrientation GetCameraOrientationRelativeToNativeOrientation()
    {
        // Get the rotation angle of the camera enclosure
        return ConvertClockwiseDegreesToSimpleOrientation((int)_cameraEnclosureLocation.RotationAngleInDegreesClockwise);
    }

    // Gets the rotation to rotate ui elements
    public SimpleOrientation GetUIOrientation()
    {
        if (IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            // Cameras that are not attached to the device do not rotate along with it, so apply no rotation
            return SimpleOrientation.NotRotated;
        }

        // Return the difference between the orientation of the device and the orientation of the app display
        var deviceOrientation = _orientationSensor.GetCurrentOrientation();
        var displayOrientation = ConvertDisplayOrientationToSimpleOrientation(_displayInformation.CurrentOrientation);
        return SubOrientations(displayOrientation, deviceOrientation);
    }

    // Gets the rotation of the camera to rotate pictures/videos when saving to file
    public SimpleOrientation GetCameraCaptureOrientation()
    {
        if (IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            // Cameras that are not attached to the device do not rotate along with it, so apply no rotation
            return SimpleOrientation.NotRotated;
        }

        // Get the device orienation offset by the camera hardware offset
        var deviceOrientation = _orientationSensor.GetCurrentOrientation();
        var result = SubOrientations(deviceOrientation, GetCameraOrientationRelativeToNativeOrientation());

        // If the preview is being mirrored for a front-facing camera, then the rotation should be inverted
        if (IsCameraMirrored())
        {
            result = MirrorOrientation(result);
        }
        return result;
    }

    // Gets the rotation of the camera to display the camera preview
    public SimpleOrientation GetCameraPreviewOrientation()
    {
        if (IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            // Cameras that are not attached to the device do not rotate along with it, so apply no rotation
            return SimpleOrientation.NotRotated;
        }

        // Get the app display rotation offset by the camera hardware offset
        var result = ConvertDisplayOrientationToSimpleOrientation(_displayInformation.CurrentOrientation);
        result = SubOrientations(result, GetCameraOrientationRelativeToNativeOrientation());

        // If the preview is being mirrored for a front-facing camera, then the rotation should be inverted
        if (IsCameraMirrored())
        {
            result = MirrorOrientation(result);
        }
        return result;
    }

    public static PhotoOrientation ConvertSimpleOrientationToPhotoOrientation(SimpleOrientation orientation)
    {
        switch (orientation)
        {
            case SimpleOrientation.Rotated90DegreesCounterclockwise:
                return PhotoOrientation.Rotate90;
            case SimpleOrientation.Rotated180DegreesCounterclockwise:
                return PhotoOrientation.Rotate180;
            case SimpleOrientation.Rotated270DegreesCounterclockwise:
                return PhotoOrientation.Rotate270;
            case SimpleOrientation.NotRotated:
            default:
                return PhotoOrientation.Normal;
        }
    }

    public static int ConvertSimpleOrientationToClockwiseDegrees(SimpleOrientation orientation)
    {
        switch (orientation)
        {
            case SimpleOrientation.Rotated90DegreesCounterclockwise:
                return 270;
            case SimpleOrientation.Rotated180DegreesCounterclockwise:
                return 180;
            case SimpleOrientation.Rotated270DegreesCounterclockwise:
                return 90;
            case SimpleOrientation.NotRotated:
            default:
                return 0;
        }
    }

    private SimpleOrientation ConvertDisplayOrientationToSimpleOrientation(DisplayOrientations orientation)
    {
        SimpleOrientation result;
        switch (orientation)
        {
            case DisplayOrientations.Landscape:
                result = SimpleOrientation.NotRotated;
                break;
            case DisplayOrientations.PortraitFlipped:
                result = SimpleOrientation.Rotated90DegreesCounterclockwise;
                break;
            case DisplayOrientations.LandscapeFlipped:
                result = SimpleOrientation.Rotated180DegreesCounterclockwise;
                break;
            case DisplayOrientations.Portrait:
            default:
                result = SimpleOrientation.Rotated270DegreesCounterclockwise;
                break;
        }

        // Above assumes landscape; offset is needed if native orientation is portrait
        if (_displayInformation.NativeOrientation == DisplayOrientations.Portrait)
        {
            result = AddOrientations(result, SimpleOrientation.Rotated90DegreesCounterclockwise);
        }

        return result;
    }

    private static SimpleOrientation MirrorOrientation(SimpleOrientation orientation)
    {
        // This only affects the 90 and 270 degree cases, because rotating 0 and 180 degrees is the same clockwise and counter-clockwise
        switch (orientation)
        {
            case SimpleOrientation.Rotated90DegreesCounterclockwise:
                return SimpleOrientation.Rotated270DegreesCounterclockwise;
            case SimpleOrientation.Rotated270DegreesCounterclockwise:
                return SimpleOrientation.Rotated90DegreesCounterclockwise;
        }
        return orientation;
    }

    private static SimpleOrientation AddOrientations(SimpleOrientation a, SimpleOrientation b)
    {
        var aRot = ConvertSimpleOrientationToClockwiseDegrees(a);
        var bRot = ConvertSimpleOrientationToClockwiseDegrees(b);
        var result = (aRot + bRot) % 360;
        return ConvertClockwiseDegreesToSimpleOrientation(result);
    }

    private static SimpleOrientation SubOrientations(SimpleOrientation a, SimpleOrientation b)
    {
        var aRot = ConvertSimpleOrientationToClockwiseDegrees(a);
        var bRot = ConvertSimpleOrientationToClockwiseDegrees(b);
        //add 360 to ensure the modulus operator does not operate on a negative
        var result = (360 + (aRot - bRot)) % 360;
        return ConvertClockwiseDegreesToSimpleOrientation(result);
    }

    private static VideoRotation ConvertSimpleOrientationToVideoRotation(SimpleOrientation orientation)
    {
        switch (orientation)
        {
            case SimpleOrientation.Rotated90DegreesCounterclockwise:
                return VideoRotation.Clockwise270Degrees;
            case SimpleOrientation.Rotated180DegreesCounterclockwise:
                return VideoRotation.Clockwise180Degrees;
            case SimpleOrientation.Rotated270DegreesCounterclockwise:
                return VideoRotation.Clockwise90Degrees;
            case SimpleOrientation.NotRotated:
            default:
                return VideoRotation.None;
        }
    }

    private static SimpleOrientation ConvertClockwiseDegreesToSimpleOrientation(int orientation)
    {
        switch (orientation)
        {
            case 270:
                return SimpleOrientation.Rotated90DegreesCounterclockwise;
            case 180:
                return SimpleOrientation.Rotated180DegreesCounterclockwise;
            case 90:
                return SimpleOrientation.Rotated270DegreesCounterclockwise;
            case 0:
            default:
                return SimpleOrientation.NotRotated;
        }
    }
}