Composiciones y edición multimedia

En este artículo se muestra cómo usar las API del espacio de nombres Windows.Media.Editing para desarrollar rápidamente aplicaciones que permiten a los usuarios crear composiciones multimedia a partir de archivos de origen de audio y vídeo. Las características del marco incluyen la capacidad de anexar mediante programación varios clips de vídeo juntos, agregar superposiciones de vídeo e imagen, agregar audio de fondo y aplicar efectos de audio y vídeo. Una vez creadas, las composiciones multimedia se pueden representar en un archivo multimedia plano para la reproducción o el uso compartido, pero las composiciones también se pueden serializar y deserializar en el disco, lo que permite al usuario cargar y modificar las composiciones que han creado anteriormente. Toda esta funcionalidad se proporciona en una interfaz de Windows Runtime fácil de usar que reduce drásticamente la cantidad y complejidad del código necesario para realizar estas tareas en comparación con la API de Microsoft Media Foundation de bajo nivel.

Crear una nueva composición de multimedia

La clase MediaComposition es el contenedor de todos los clips multimedia que componen la composición y es responsable de representar la composición final, cargar y guardar composiciones en disco y proporcionar una secuencia de vista previa de la composición para que el usuario pueda verla en la interfaz de usuario. Para usar MediaComposition en la aplicación, incluya el espacio de nombres Windows.Media.Editing, así como el espacio de nombres Windows.Media.Core que proporciona las API relacionadas que necesitará.

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

Se tendrá acceso al objeto MediaComposition desde varios puntos del código, por lo que normalmente se declarará una variable miembro en la que almacenarlo.

private MediaComposition composition;

El constructor de MediaComposition no toma ningún argumento.

composition = new MediaComposition();

Agregar clips multimedia a una composición

Las composiciones multimedia suelen contener uno o varios clips de vídeo. Puede usar un FileOpenPicker para permitir al usuario seleccionar un archivo de vídeo. Una vez seleccionado el archivo, cree un nuevo objeto MediaClip para incluir el clip de vídeo llamando a MediaClip.CreateFromFileAsync. A continuación, agregue el clip a la lista Clips del objeto 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);

}
  • Los clips multimedia aparecen en MediaComposition en el mismo orden que aparecen en la lista Clips.

  • Un MediaClip solo se puede incluir en una composición una vez. Si se intenta agregar un MediaClip que ya se usa en la composición, se producirá un error. Para reutilizar un clip de vídeo varias veces en una composición, llame a Clone para crear nuevos objetos MediaClip que luego se pueden agregar a la composición.

  • Las aplicaciones de Universal Windows no tienen permiso para acceder a todo el sistema de archivos. La propiedad FutureAccessList de la clase StorageApplicationPermissions permite a la aplicación almacenar un registro de un archivo seleccionado por el usuario para que pueda conservar los permisos para acceder al archivo. FutureAccessList tiene un máximo de 1000 entradas, por lo que la aplicación debe administrar la lista para asegurarse de que no se llena. Esto es especialmente importante si tiene previsto admitir la carga y modificación de composiciones creadas anteriormente.

  • MediaComposition admite clips de vídeo en formato MP4.

  • Si un archivo de vídeo contiene varias pistas de audio incrustadas, puede seleccionar qué pista de audio se usa en la composición estableciendo la propiedad SelectedEmbeddedAudioTrackIndex.

  • Cree un MediaClip con un único color que rellene todo el marco llamando a CreateFromColor y especificando un color y una duración para el clip.

  • Cree un MediaClip a partir de un archivo de imagen llamando a CreateFromImageFileAsync y especificando un archivo de imagen y una duración para el clip.

  • Cree un MediaClip desde un IDirect3DSurface llamando a CreateFromSurface y especificando una superficie y una duración del clip.

Vista previa de la composición en un objeto MediaElement

Para permitir que el usuario vea la composición multimedia, agregue un objeto MediaPlayerElement al archivo XAML que define la interfaz de usuario.

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

Declare una variable miembro de tipo MediaStreamSource.

private MediaStreamSource mediaStreamSource;

Llame al método GeneratePreviewMediaStreamSource del objeto MediaComposition para crear un objeto MediaStreamSource para la composición. Cree un objeto MediaSource llamando al método factory CreateFromMediaStreamSource y asígnelo a la propiedad Source de MediaPlayerElement. Ahora la composición se puede ver en la interfaz de usuario.

public void UpdateMediaElementSource()
{

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

    mediaPlayerElement.Source = MediaSource.CreateFromMediaStreamSource(mediaStreamSource);

}
  • MediaComposition debe contener al menos un clip multimedia antes de llamar a GeneratePreviewMediaStreamSource o el objeto devuelto será NULL.

  • La escala de tiempo MediaElement no se actualiza automáticamente para reflejar los cambios en la composición. Se recomienda llamar a GeneratePreviewMediaStreamSource y establecer la propiedad MediaPlayerElementSource cada vez que realice un conjunto de cambios en la composición y quiera actualizar la interfaz de usuario.

Se recomienda establecer el objeto MediaStreamSource y la propiedad Source de MediaPlayerElement en NULL cuando el usuario se aleja de la página para liberar los recursos asociados.

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

}

Representación de la composición en un archivo de vídeo

Para representar una composición multimedia en un archivo de vídeo plano para que se pueda compartir y ver en otros dispositivos, deberá usar las API del espacio de nombres Windows.Media.Transcoding. Para actualizar la interfaz de usuario en el progreso de la operación asincrónica, también necesitará las API del espacio de nombres Windows.UI.Core.

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

Después de permitir que el usuario seleccione un archivo de salida con FileSavePicker, represente la composición en el archivo seleccionado llamando a RenderToFileAsync del objeto MediaComposition. El resto del código del ejemplo siguiente simplemente sigue el patrón de control de 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 permite priorizar la velocidad de la operación de transcodificación frente a la precisión del recorte de clips multimedia adyacentes. Fast hace que la transcodificación sea más rápida con un recorte de menor precisión; Precise hace que la transcodificación sea más lenta pero con un recorte más preciso.

Recortar un clip de vídeo

Recorte la duración de un clip de vídeo en una composición estableciendo la propiedad TrimTimeFromStart de los objetos MediaClip, la propiedad TrimTimeFromEnd o ambas.

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;

}
  • Puede usar cualquier interfaz de usuario que desee para permitir al usuario especificar los valores de recorte inicial y final. En el ejemplo anterior se usa la propiedad Posición de MediaPlaybackSession asociada a MediaPlayerElement para determinar primero qué MediaClip se está reproduciendo en la posición actual de la composición comprobando StartTimeInComposition y EndTimeInComposition. A continuación, las propiedades Position y StartTimeInComposition se vuelven a usar para calcular la cantidad de tiempo que se va a recortar desde el principio del clip. El método FirstOrDefault es un método de extensión del espacio de nombres System.Linq que simplifica el código para seleccionar elementos de una lista.
  • La propiedad OriginalDuration del objeto MediaClip le permite conocer la duración del clip multimedia sin aplicar ningún recorte.
  • La propiedad TrimmedDuration le permite saber la duración del clip multimedia después de aplicar el recorte.
  • Si se especifica un valor de recorte mayor que la duración original del clip, no se produce un error. Sin embargo, si una composición contiene solo un solo clip y se recorta a una longitud cero especificando un valor de recorte grande, una llamada posterior a GeneratePreviewMediaStreamSource devolverá NULL, como si la composición no tuviera clips.

Agregar una pista de audio de fondo a una composición

Para agregar una pista de fondo a una composición, cargue un archivo de audio y, a continuación, cree un objeto BackgroundAudioTrack llamando al método factory BackgroundAudioTrack.CreateFromFileAsync. A continuación, agregue BackgroundAudioTrack a la propiedad BackgroundAudioTracks de la composición.

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 admite pistas de audio en segundo plano en los siguientes formatos: MP3, WAV, FLAC

  • Pista de audio en segundo plano

  • Al igual que con los archivos de vídeo, debe usar la clase StorageApplicationPermissions para conservar el acceso a los archivos de la composición.

  • Al igual que con MediaClip, una BackgroundAudioTrack solo se puede incluir en una composición una vez. Si se intenta agregar una BackgroundAudioTrack que ya se usa en la composición, se producirá un error. Para reutilizar una pista de audio varias veces en una composición, llame a Clone para crear nuevos objetos MediaClip que luego se pueden agregar a la composición.

  • De forma predeterminada, las pistas de audio en segundo plano comienzan a reproducirse al principio de la composición. Si hay varias pistas de fondo presentes, todas las pistas comenzarán a reproducirse al principio de la composición. Para hacer que una pista de audio en segundo plano comience la reproducción en otro momento, establezca la propiedad Delay con el retardo de tiempo deseado.

Agregar una superposición a una composición

Las superposiciones permiten apilar varias capas de vídeo unas encima de las otras en una composición. Una composición puede contener varias capas de superposición, cada una de las cuales puede incluir varias superposiciones. Cree un objeto MediaOverlay pasando un objeto MediaClip a su constructor. Establezca la posición y opacidad de la superposición y, a continuación, cree una nueva clase MediaOverlayLayer y agregue la MediaOverlay a su lista de Overlays. Por último, agregue MediaOverlayLayer a la lista OverlayLayers de la composición.

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);
}
  • Las superposiciones dentro de una capa se ordenan en z en función de su orden en la lista de Overlays de la capa contenedora. Los índices más altos de la lista se representan en la parte superior de los índices inferiores. Lo mismo sucede con las capas de superposición dentro de una composición. Una capa con un índice superior en la lista OverlayLayers de la composición se representará en la parte superior de los índices inferiores.

  • Dado que las superposiciones se apilan entre sí en lugar de reproducirse secuencialmente, todas las superposiciones inician la reproducción al principio de la composición de forma predeterminada. Para hacer que una superposición inicie la reproducción en otro momento, establezca la propiedad Delay con el desfase de tiempo deseado.

Agregar efectos a un clip multimedia

Cada MediaClip de una composición tiene una lista de efectos de audio y vídeo a los que se pueden agregar varios efectos. Los efectos deben implementar IAudioEffectDefinition e IVideoEffectDefinition respectivamente. En el ejemplo siguiente se usa la posición MediaPlayerElement actual para elegir el objeto MediaClip visto actualmente y, a continuación, se crea una nueva instancia de VideoStabilizationEffectDefinition y se anexa a la lista VideoEffectDefinitions del clip multimedia.

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);
}

Guardar una composición en un archivo

Las composiciones multimedia se pueden serializar en un archivo que se va a modificar más adelante. Elija un archivo de salida y, a continuación, llame al método MediaCompositionSaveAsync para guardar la composición.

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");
            }

        };
    }
}

Cargar una composición desde un archivo

Las composiciones multimedia se pueden deserializar desde un archivo para permitir al usuario ver y modificar la composición. Elija un archivo de composición y, a continuación, llame al método MediaCompositionLoadAsync para cargar la composición.

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");
        }
    }
}
  • Si un archivo multimedia de la composición no está en una ubicación a la que pueda acceder la aplicación y no se encuentra en la propiedad FutureAccessList de la clase StorageApplicationPermissions de la aplicación, se producirá un error al cargar la composición.