Композиции мультимедиа и редактирование

В этой статье показано, как использовать API в пространстве имен Windows.Media.Editing для быстрого разработки приложений, позволяющих пользователям создавать композиции мультимедиа из звуковых и видео-исходных файлов. Возможности платформы включают возможность программно добавлять несколько видеоклипов вместе, добавлять видео и изображения наложения, добавлять фоновый звук и применять эффекты аудио и видео. После создания композиции мультимедиа можно отобразить в неструктурированный файл мультимедиа для воспроизведения или совместного использования, но композиции также можно сериализовать в диск и десериализировать с диска, что позволяет пользователю загружать и изменять созданные ранее композиции. Все эти функции предоставляются в простом интерфейсе среда выполнения Windows, который значительно снижает объем и сложность кода, необходимых для выполнения этих задач, по сравнению с низкоуровневым API Microsoft Media Foundation.

Создание новой композиции носителей

Класс MediaComposition — это контейнер для всех клипов мультимедиа, составляющих композицию, и отвечает за отрисовку окончательной композиции, загрузки и сохранения композиций на диск и предоставления предварительного просмотра потока композиции, чтобы пользователь смог просмотреть его в пользовательском интерфейсе. Чтобы использовать MediaComposition в приложении, включите пространство имен Windows.Media.Editing, а также пространство имен Windows.Media.Core, которое предоставляет связанные API, которые вам потребуются.

using Windows.Media.Editing;
using Windows.Media.Core;
using Windows.Media.Playback;
using System.Threading.Tasks;

Объект MediaComposition будет получен из нескольких точек в коде, поэтому обычно вы объявите переменную-член, в которой он будет храниться.

private MediaComposition composition;

Конструктор для MediaComposition не принимает аргументы.

composition = new MediaComposition();

Добавление клипов мультимедиа в композицию

Композиции мультимедиа обычно содержат один или несколько клипов видео. Вы можете использовать FileOpenPicker , чтобы разрешить пользователю выбрать видеофайл. После выбора файла создайте новый объект MediaClip, чтобы содержать клип, вызвав MediaClip.CreateFromFileAsync. Затем вы добавите клип в список клипов объекта MediaComposition.

private async Task PickFileAndAddClip()
{
    var picker = new Windows.Storage.Pickers.FileOpenPicker();
    picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.VideosLibrary;
    picker.FileTypeFilter.Add(".mp4");
    Windows.Storage.StorageFile pickedFile = await picker.PickSingleFileAsync();
    if (pickedFile == null)
    {
        ShowErrorMessage("File picking cancelled");
        return;
    }

    // These files could be picked from a location that we won't have access to later
    var storageItemAccessList = Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList;
    storageItemAccessList.Add(pickedFile);

    var clip = await MediaClip.CreateFromFileAsync(pickedFile);
    composition.Clips.Add(clip);

}
  • Клипы мультимедиа отображаются в MediaComposition в том же порядке, что и в списке клипов .

  • MediaClip может быть включен только в композицию один раз. Попытка добавить MediaClip , который уже используется композицией, приведет к ошибке. Чтобы повторно использовать клип видео несколько раз в композиции, вызовите Клонировать , чтобы создать новые объекты MediaClip , которые затем можно добавить в композицию.

  • У универсальных приложений Windows нет разрешения на доступ ко всей файловой системе. Свойство FutureAccessList класса служба хранилища ApplicationPermissions позволяет приложению хранить запись файла, выбранного пользователем, чтобы сохранить разрешения на доступ к файлу. FutureAccessList имеет максиум из 1000 записей, поэтому ваше приложение должно управлять списком, чтобы убедиться, что он не станет полным. Это особенно важно, если вы планируете поддерживать загрузку и изменение ранее созданных композиций.

  • MediaComposition поддерживает видеоклипы в формате MP4.

  • Если видеофайл содержит несколько внедренных звуковых треков, можно выбрать, какой звуковой трек используется в композиции, задав свойство SelectedEmbeddedAudioTrackIndex .

  • Создайте MediaClip с одним цветом заливки всего кадра, вызвав CreateFromColor и указав цвет и длительность клипа.

  • Создайте MediaClip из файла изображения, вызвав CreateFromImageFileAsync и указав файл изображения и длительность клипа.

  • Создайте MediaClip из IDirect3DSurface, вызвав CreateFromSurface и указав поверхность и длительность из клипа.

Предварительная версия композиции в MediaElement

Чтобы пользователь мог просматривать композицию мультимедиа, добавьте MediaPlayerElement в XAML-файл, определяющий пользовательский интерфейс.

<MediaPlayerElement x:Name="mediaPlayerElement" AutoPlay="False" Margin="5" HorizontalAlignment="Stretch" AreTransportControlsEnabled="True" />

Объявите переменную члена типа MediaStreamSource.

private MediaStreamSource mediaStreamSource;

Вызовите метод GeneratePreviewMediaStreamSource объекта MediaComposition, чтобы создать MediaStreamSource для композиции. Создайте объект MediaSource путем вызова метода фабрики CreateFromMediaStreamSource и назначьте его свойству Source объекта MediaPlayerElement. Теперь композицию можно просмотреть в пользовательском интерфейсе.

public void UpdateMediaElementSource()
{

    mediaStreamSource = composition.GeneratePreviewMediaStreamSource(
        (int)mediaPlayerElement.ActualWidth,
        (int)mediaPlayerElement.ActualHeight);

    mediaPlayerElement.Source = MediaSource.CreateFromMediaStreamSource(mediaStreamSource);

}
  • MediaComposition должен содержать по крайней мере один клип мультимедиа перед вызовом GeneratePreviewMediaStreamSource, или возвращенный объект будет иметь значение NULL.

  • Временная шкала MediaElement не обновляется автоматически, чтобы отразить изменения в композиции. Рекомендуется вызывать как GeneratePreviewMediaStreamSource, так и задавать свойство MediaPlayerElementSource каждый раз, когда вы вносите набор изменений в композицию и хотите обновить пользовательский интерфейс.

Рекомендуется задать для объекта MediaStreamSource и свойства Source Объекта MediaPlayerElement значение NULL, если пользователь переходит от страницы, чтобы освободить связанные ресурсы.

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    mediaPlayerElement.Source = null;
    mediaStreamSource = null;
    base.OnNavigatedFrom(e);

}

Отрисовка композиции в видеофайл

Чтобы отобразить композицию мультимедиа в неструктурированный видеофайл, чтобы его можно было совместно использовать на других устройствах, необходимо использовать API из пространства имен Windows.Media.Transcoding. Чтобы обновить пользовательский интерфейс при выполнении асинхронной операции, вам также потребуется API из пространства имен Windows.UI.Core.

using Windows.Media.Transcoding;
using Windows.UI.Core;

После разрешения пользователю выбрать выходной файл с файлом FileSavePicker отрисуйте композицию в выбранный файл, вызвав объект RenderToFileAsync объекта MediaComposition. Остальная часть кода в следующем примере просто соответствует шаблону обработки AsyncOperationWithProgress.

private async Task RenderCompositionToFile()
{
    var picker = new Windows.Storage.Pickers.FileSavePicker();
    picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.VideosLibrary;
    picker.FileTypeChoices.Add("MP4 files", new List<string>() { ".mp4" });
    picker.SuggestedFileName = "RenderedComposition.mp4";

    Windows.Storage.StorageFile file = await picker.PickSaveFileAsync();
    if (file != null)
    {
        // Call RenderToFileAsync
        var saveOperation = composition.RenderToFileAsync(file, MediaTrimmingPreference.Precise);

        saveOperation.Progress = new AsyncOperationProgressHandler<TranscodeFailureReason, double>(async (info, progress) =>
        {
            await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() =>
            {
                ShowErrorMessage(string.Format("Saving file... Progress: {0:F0}%", progress));
            }));
        });
        saveOperation.Completed = new AsyncOperationWithProgressCompletedHandler<TranscodeFailureReason, double>(async (info, status) =>
        {
            await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() =>
            {
                try
                {
                    var results = info.GetResults();
                    if (results != TranscodeFailureReason.None || status != AsyncStatus.Completed)
                    {
                        ShowErrorMessage("Saving was unsuccessful");
                    }
                    else
                    {
                        ShowErrorMessage("Trimmed clip saved to file");
                    }
                }
                finally
                {
                        // Update UI whether the operation succeeded or not
                    }

            }));
        });
    }
    else
    {
        ShowErrorMessage("User cancelled the file selection");
    }
}
  • MediaTrimmingPreference позволяет определить приоритет скорости операции транскодирования и точности обрезки смежных клипов мультимедиа. Быстрая обработка приводит к тому, что транскодирование будет быстрее при более низкой точности обрезки, точность приводит к замедлению транскодирования, но с более точной обрезкой.

Обрезка видеоклипа

Обрезать длительность клипа в композиции, задав свойство TrimTimeFromStart объекта TrimTimeFromStart, свойство TrimTimeFromEnd или оба.

private void TrimClipBeforeCurrentPosition()
{
    var currentClip = composition.Clips.FirstOrDefault(
        mc => mc.StartTimeInComposition <= mediaPlayerElement.MediaPlayer.PlaybackSession.Position &&
        mc.EndTimeInComposition >= mediaPlayerElement.MediaPlayer.PlaybackSession.Position);

    TimeSpan positionFromStart = mediaPlayerElement.MediaPlayer.PlaybackSession.Position - currentClip.StartTimeInComposition;
    currentClip.TrimTimeFromStart = positionFromStart;

}
  • Вы можете использовать любой пользовательский интерфейс, который требуется разрешить пользователю указать значения начальной и конечной обрезки. В приведенном выше примере свойство Position объекта MediaPlaybackSession, связанного с MediaPlayerElement, сначала определяет, какой MediaClip воспроизводит обратно в текущей позиции композиции, проверка запуска StartTimeInComposition и EndTimeInComposition. Затем свойства Position и StartTimeInComposition используются еще раз, чтобы вычислить время для обрезки с начала клипа. Метод FirstOrDefault — это метод расширения из пространства имен System.Linq, упрощающий выбор элементов из списка.
  • Свойство OriginalDuration объекта MediaClip позволяет узнать длительность клипа мультимедиа без применения вырезки.
  • Свойство TrimmedDuration позволяет узнать длительность клипа мультимедиа после применения обрезки.
  • Указание значения обрезки, превышающего исходную длительность клипа, не вызывает ошибки. Однако, если композиция содержит только один клип и обрезается до нулевой длины, указав большое значение обрезки, последующий вызов GeneratePreviewMediaStreamSource вернет значение NULL, как если бы композиция не имеет клипов.

Добавление фоновой звуковой дорожки в композицию

Чтобы добавить фоновую дорожку в композицию, загрузите звуковой файл и создайте объект BackgroundAudioTrack, вызвав метод BackgroundAudioTrack.CreateFromFileAsync. Затем добавьте BackgroundAudioTrack в свойство BackgroundAudioTracks композиции.

private async Task AddBackgroundAudioTrack()
{
    // Add background audio
    var picker = new Windows.Storage.Pickers.FileOpenPicker();
    picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.MusicLibrary;
    picker.FileTypeFilter.Add(".mp3");
    picker.FileTypeFilter.Add(".wav");
    picker.FileTypeFilter.Add(".flac");
    Windows.Storage.StorageFile audioFile = await picker.PickSingleFileAsync();
    if (audioFile == null)
    {
        ShowErrorMessage("File picking cancelled");
        return;
    }

    // These files could be picked from a location that we won't have access to later
    var storageItemAccessList = Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList;
    storageItemAccessList.Add(audioFile);

    var backgroundTrack = await BackgroundAudioTrack.CreateFromFileAsync(audioFile);

    composition.BackgroundAudioTracks.Add(backgroundTrack);

}
  • MediaComposition поддерживает фоновые звуковые дорожки в следующих форматах: MP3, WAV, FLAC

  • Фоновая звуковая дорожка

  • Как и в случае с видеофайлами, следует использовать класс служба хранилища ApplicationPermissions для сохранения доступа к файлам в композиции.

  • Как и в Случае с MediaClip, backgroundAudioTrack может быть включен только в композицию один раз. Попытка добавить BackgroundAudioTrack , которая уже используется композицией, приведет к ошибке. Чтобы повторно использовать звуковую дорожку несколько раз в композиции, вызовите Clone, чтобы создать новые объекты MediaClip, которые затем можно добавить в композицию.

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

Добавление наложения в композицию

Наложения позволяют стекать несколько слоев видео поверх друг друга в композиции. Композиция может содержать несколько слоев наложения, каждый из которых может включать несколько наложений. Создайте объект MediaOverlay, передав mediaClip в его конструктор. Задайте положение и непрозрачность наложения, а затем создайте новый MediaOverlayLayer и добавьте MediaOverlay в список наложений. Наконец, добавьте MediaOverlayLayer в список Overlayers композиции.

private void AddOverlay(MediaClip overlayMediaClip, double scale, double left, double top, double opacity)
{
    Windows.Media.MediaProperties.VideoEncodingProperties encodingProperties =
        overlayMediaClip.GetVideoEncodingProperties();

    Rect overlayPosition;

    overlayPosition.Width = (double)encodingProperties.Width * scale;
    overlayPosition.Height = (double)encodingProperties.Height * scale;
    overlayPosition.X = left;
    overlayPosition.Y = top;

    MediaOverlay mediaOverlay = new MediaOverlay(overlayMediaClip);
    mediaOverlay.Position = overlayPosition;
    mediaOverlay.Opacity = opacity;

    MediaOverlayLayer mediaOverlayLayer = new MediaOverlayLayer();
    mediaOverlayLayer.Overlays.Add(mediaOverlay);

    composition.OverlayLayers.Add(mediaOverlayLayer);
}
  • Наложения в слое упорядочены по z на основе их порядка в списке наложений слоя. Более высокие индексы в списке отображаются поверх более низких индексов. То же самое относится к слоям наложения внутри композиции. Слой с более высоким индексом в списке Overlayers композиции будет отображаться поверх более низких индексов.

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

Добавление эффектов в клип мультимедиа

Каждый MediaClip в композиции содержит список звуковых и видео эффектов, к которым можно добавить несколько эффектов. Эффекты должны реализовывать IAudioEffectDefinition и IVideoEffectDefinition соответственно. В следующем примере используется текущее положение MediaPlayerElement для выбора текущего представления MediaClip, а затем создается новый экземпляр VideoStabilizationEffectDefinition и добавляет его в список VideoEffectDefinitions клипа мультимедиа.

private void AddVideoEffect()
{
    var currentClip = composition.Clips.FirstOrDefault(
        mc => mc.StartTimeInComposition <= mediaPlayerElement.MediaPlayer.PlaybackSession.Position &&
        mc.EndTimeInComposition >= mediaPlayerElement.MediaPlayer.PlaybackSession.Position);

    VideoStabilizationEffectDefinition videoEffect = new VideoStabilizationEffectDefinition();
    currentClip.VideoEffectDefinitions.Add(videoEffect);
}

Сохранение композиции в файле

Композиции мультимедиа можно сериализовать в файл, который будет изменен позже. Выберите выходной файл и вызовите метод SaveAsync mediaComposition, чтобы сохранить композицию.

private async Task SaveComposition()
{
    var picker = new Windows.Storage.Pickers.FileSavePicker();
    picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.VideosLibrary;
    picker.FileTypeChoices.Add("Composition files", new List<string>() { ".cmp" });
    picker.SuggestedFileName = "SavedComposition";

    Windows.Storage.StorageFile compositionFile = await picker.PickSaveFileAsync();
    if (compositionFile == null)
    {
        ShowErrorMessage("User cancelled the file selection");
    }
    else
    {
        var action = composition.SaveAsync(compositionFile);
        action.Completed = (info, status) =>
        {
            if (status != AsyncStatus.Completed)
            {
                ShowErrorMessage("Error saving composition");
            }

        };
    }
}

Загрузка композиции из файла

Композиции мультимедиа можно десериализировать из файла, чтобы пользователь могли просматривать и изменять композицию. Выберите файл композиции и вызовите метод LoadAsync метода MediaComposition, чтобы загрузить композицию.

private async Task OpenComposition()
{
    var picker = new Windows.Storage.Pickers.FileOpenPicker();
    picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.VideosLibrary;
    picker.FileTypeFilter.Add(".cmp");

    Windows.Storage.StorageFile compositionFile = await picker.PickSingleFileAsync();
    if (compositionFile == null)
    {
        ShowErrorMessage("File picking cancelled");
    }
    else
    {
        composition = null;
        composition = await MediaComposition.LoadAsync(compositionFile);

        if (composition != null)
        {
            UpdateMediaElementSource();

        }
        else
        {
            ShowErrorMessage("Unable to open composition");
        }
    }
}
  • Если файл мультимедиа в композиции не находится в расположении, к которому можно получить доступ приложению и не находится в свойстве FutureAccessList класса служба хранилища ApplicationPermissions для приложения, при загрузке композиции возникает ошибка.