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

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

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

Класс 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 = null!;

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

composition = new MediaComposition();

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

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

private async Task PickFileAndAddClip()
{
    if (composition == null)
    {
        ShowErrorMessage("Create a composition before adding clips");
        return;
    }

    var picker = new Windows.Storage.Pickers.FileOpenPicker();
    WinRT.Interop.InitializeWithWindow.Initialize(picker, _hwnd);
    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;
    }

    var storageItemAccessList = Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList;
    storageItemAccessList.Add(pickedFile);

    var clip = await MediaClip.CreateFromFileAsync(pickedFile);
    composition.Clips.Add(clip);
}
  • Клипы мультимедиа отображаются в MediaComposition в том же порядке, что и в списке клипов .

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

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

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

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

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

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

Предварительный просмотр композиции в MediaPlayerElement

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

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

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

private MediaStreamSource mediaStreamSource = null!;

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

public void UpdateMediaElementSource()
{
    if (composition == null)
    {
        ShowErrorMessage("Create or open a composition first");
        return;
    }

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

    mediaPlayerElement.Source = MediaSource.CreateFromMediaStreamSource(mediaStreamSource);
}
  • MediaComposition должен содержать по крайней мере один клип мультимедиа перед вызовом GeneratePreviewMediaStreamSource, или возвращенный объект будет иметь значение NULL.

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

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

private void OnWindowClosed(object sender, WindowEventArgs args)
{
    mediaPlayerElement.Source = null;
    mediaStreamSource = null!;
}

Визуализировать композицию в видеофайл

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

using Windows.Media.Transcoding;
using Microsoft.UI.Dispatching;

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

private async Task RenderCompositionToFile()
{
    if (composition == null)
    {
        ShowErrorMessage("Create or open a composition first");
        return;
    }

    var picker = new Windows.Storage.Pickers.FileSavePicker();
    WinRT.Interop.InitializeWithWindow.Initialize(picker, _hwnd);
    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)
    {
        var saveOperation = composition.RenderToFileAsync(file, MediaTrimmingPreference.Precise);

        saveOperation.Progress = new AsyncOperationProgressHandler<TranscodeFailureReason, double>((info, progress) =>
        {
            _dispatcherQueue.TryEnqueue(() =>
            {
                ShowErrorMessage(string.Format("Saving file... Progress: {0:F0}%", progress));
            });
        });
        saveOperation.Completed = new AsyncOperationWithProgressCompletedHandler<TranscodeFailureReason, double>((info, status) =>
        {
            _dispatcherQueue.TryEnqueue(() =>
            {
                try
                {
                    var results = info.GetResults();
                    if (results != TranscodeFailureReason.None || status != AsyncStatus.Completed)
                    {
                        ShowErrorMessage("Saving was unsuccessful");
                    }
                    else
                    {
                        ShowErrorMessage("Composition saved to file");
                    }
                }
                finally
                {
                }
            });
        });
    }
    else
    {
        ShowErrorMessage("User cancelled the file selection");
    }
}
  • Параметр MediaTrimmingPreference позволяет задать приоритет скорости операции транскодирования по сравнению с точностью обрезки соседних медиаклипов. Fast ускоряет транскодирование, но обрезка выполняется с меньшей точностью; Precise замедляет транскодирование, но обеспечивает более точную обрезку.

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

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

private void TrimClipBeforeCurrentPosition()
{
    if (composition == null || mediaPlayerElement.MediaPlayer == null)
    {
        ShowErrorMessage("Create or open a composition first");
        return;
    }

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

    if (currentClip == null)
    {
        ShowErrorMessage("No clip exists at the current playback position");
        return;
    }

    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()
{
    if (composition == null)
    {
        ShowErrorMessage("Create or open a composition first");
        return;
    }

    var picker = new Windows.Storage.Pickers.FileOpenPicker();
    WinRT.Interop.InitializeWithWindow.Initialize(picker, _hwnd);
    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;
    }

    var storageItemAccessList = Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList;
    storageItemAccessList.Add(audioFile);

    var backgroundTrack = await BackgroundAudioTrack.CreateFromFileAsync(audioFile);

    composition.BackgroundAudioTracks.Add(backgroundTrack);
}
  • MediaComposition поддерживает фоновые звуковые дорожки в следующих форматах: MP3, WAV, FLAC

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

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

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

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

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

private void AddOverlay(MediaClip overlayMediaClip, double scale, double left, double top, double opacity)
{
    if (composition == null)
    {
        ShowErrorMessage("Create or open a composition first");
        return;
    }

    Windows.Media.MediaProperties.VideoEncodingProperties encodingProperties =
        overlayMediaClip.GetVideoEncodingProperties();

    Rect overlayPosition = new Rect();

    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 этого объекта media clip.

private void AddVideoEffect()
{
    if (composition == null || mediaPlayerElement.MediaPlayer == null)
    {
        ShowErrorMessage("Create or open a composition first");
        return;
    }

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

    if (currentClip == null)
    {
        ShowErrorMessage("No clip exists at the current playback position");
        return;
    }

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

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

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

private async Task SaveComposition()
{
    if (composition == null)
    {
        ShowErrorMessage("Create or open a composition first");
        return;
    }

    var picker = new Windows.Storage.Pickers.FileSavePicker();
    WinRT.Interop.InitializeWithWindow.Initialize(picker, _hwnd);
    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)
            {
                _dispatcherQueue.TryEnqueue(() => ShowErrorMessage("Error saving composition"));
            }
            else
            {
                _dispatcherQueue.TryEnqueue(() => ShowErrorMessage("Composition saved"));
            }
        };
    }
}

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

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

private async Task OpenComposition()
{
    var picker = new Windows.Storage.Pickers.FileOpenPicker();
    WinRT.Interop.InitializeWithWindow.Initialize(picker, _hwnd);
    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");
        }
    }
}
  • Если файл мультимедиа в композиции не находится в расположении, к которому может получить доступ ваше приложение, при загрузке композиции возникает ошибка.

См. также