Composições e edição de mídia

Este artigo mostra como usar as APIs no namespace Windows.Media.Editing para desenvolver rapidamente aplicativos que permitem que os usuários criem composições de mídia de arquivos de origem de áudio e vídeo. Recursos da estrutura incluem a capacidade acrescentar vários videoclipes juntos, adicionar sobreposições de vídeo e imagem, adicionar áudio em segundo plano e aplicar efeitos de áudio e vídeos de forma programática. Uma vez criadas, composições de mídia podem ser renderizadas em um arquivo de mídia simples para reprodução ou compartilhamento, mas composições também podem ser serializadas para o disco e desserializadas do mesmo, permitindo que o usuário carregue e modifique composições que eles criaram anteriormente. Toda essa funcionalidade é fornecida em uma interface de Windows Runtime fácil de usar que reduz significativamente a quantidade e a complexidade do código necessário para executar essas tarefas quando comparado com a API de nível inferior da Microsoft Media Foundation .

Crie uma nova composição de mídia

A classe MediaComposition é o contêiner para todos os clipes de mídia presentes na composição e é responsável pela renderização final da composição, carregando e salvando composições no disco e fornecendo um fluxo de visualização da composição para que o usuário possa visualizá-la na interface do usuário. Para usar MediaComposition em seu aplicativo, inclua o namespace Windows.Media.Editing, bem como o namespace Windows.Media.Core que fornece APIs relacionadas que você precisará.

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

O objeto MediaComposition será acessado em vários pontos no seu código, então normalmente você irá declarar uma variável do membro no qual armazená-lo.

private MediaComposition composition;

O construtor para MediaComposition não tem argumentos.

composition = new MediaComposition();

Adicione clipes de mídia a uma composição

Composições de mídia geralmente contêm um ou mais clipes de vídeo. Você pode usar um FileOpenPicker para permitir que o usuário selecione um arquivo de vídeo. Depois que o arquivo foi selecionado, crie um novo objeto MediaClip para conter o videoclipe, chamando MediaClip.CreateFromFileAsync. Em seguida, você adiciona o clipe à lista MediaComposition do objeto Clips.

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

}
  • Clipes de mídia aparecem no MediaComposition na mesma ordem em que aparecem na lista Clips.

  • Um MediaClip só pode ser incluído em uma composição uma vez. Tentar adicionar um MediaClip que já está sendo usado pela composição resultará em erro. Para reutilizar um videoclipe várias vezes em uma composição, chame Clone para criar novos objetos MediaClip, que podem então ser adicionados à composição.

  • Aplicativos universais do Windows não têm permissão para acessar o sistema de arquivos inteiro. A propriedade FutureAccessList da classe StorageApplicationPermissions permite que seu aplicativo armazene um registro de um arquivo que foi selecionado pelo usuário para que você possa manter as permissões para acessar o arquivo. O FutureAccessList tem um máximo de 1.000 entradas, assim seu aplicativo precisa gerenciar a lista para garantir que não fique cheia. Isso é especialmente importante se você planeja suportar o carregamento e modificação de composições criadas anteriormente.

  • Uma MediaComposition suporta videoclipes em formato MP4.

  • Se um arquivo de vídeo contém várias faixas de áudio incorporadas, você pode selecionar qual faixa de áudio será usada na composição, definindo a propriedade SelectedEmbeddedAudioTrackIndex.

  • Crie um MediaClip com uma única cor preenchendo todo o quadro, chamando CreateFromColor e especificando uma cor e uma duração para o clipe.

  • Crie um MediaClip a partir de um arquivo de imagem, chamando CreateFromImageFileAsync e especificando um arquivo de imagem e uma duração para o clipe.

  • Crie um MediaClip a partir de um IDirect3DSurface chamando CreateFromSurface e especificando uma superfície e uma duração do clipe.

Visualize a composição em um MediaElement

Para permitir que o usuário exiba a composição de mídia, adicione um MediaPlayerElement no arquivo XAML que define a interface do usuário.

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

Declare uma variável de membro do tipo MediaStreamSource.

private MediaStreamSource mediaStreamSource;

Chame o método MediaComposition do objeto GeneratePreviewMediaStreamSource para criar um MediaStreamSource para a composição. Crie um objeto MediaSource chamando o método de fábrica CreateFromMediaStreamSource e atribua-o à propriedade Origem do MediaPlayerElement. Agora a composição pode ser exibida na interface do usuário.

public void UpdateMediaElementSource()
{

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

    mediaPlayerElement.Source = MediaSource.CreateFromMediaStreamSource(mediaStreamSource);

}
  • O MediaComposition deve conter pelo menos um clipe de mídia antes de chamar GeneratePreviewMediaStreamSource, ou o objeto retornado será nulo.

  • A linha do tempo MediaElement não é atualizada automaticamente para refletir as alterações na composição. É recomendável que você chame GeneratePreviewMediaStreamSource e defina a propriedade do MediaPlayerElement, Origem, sempre que fizer um conjunto de alterações na composição e desejar atualizar a interface do usuário.

É recomendável que você defina o objeto MediaStreamSource e a propriedade Source do MediaPlayerElement como nula quando o usuário navegar fora da página para liberar os recursos associados.

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

}

Renderize a composição de um arquivo de vídeo

Para renderizar uma composição de mídia para um arquivo de vídeo simples para que possa ser compartilhado e exibido em outros dispositivos, você precisará usar APIs do namespace Windows.Media.Transcoding. Para atualizar a interface do usuário sobre o progresso da operação assíncrona, você também precisará de APIs do namespace Windows.UI.Core.

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

Depois de permitir que o usuário selecione um arquivo de saída com um FileSavePicker, renderize a composição para o arquivo selecionado chamando o objeto MediaComposition RenderToFileAsync. O restante do código no exemplo a seguir simplesmente segue o padrão de manipulação de um 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");
    }
}
  • O MediaTrimmingPreference permite a você priorizar a velocidade da operação de transcodificação contra a precisão de corte de clipes de mídia adjacentes. Rápido faz com que a transcodificação seja mais rápida com os recursos de precisão mais baixos, Preciso faz com que a transcodificação seja mais lenta, mas com os recursos mais precisos.

Cortar um videoclipe

Corte a duração de um videoclipe em uma composição, definindo a propriedade TrimTimeFromStart dos objetos MediaClip, a propriedade TrimTimeFromEnd, ou 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;

}
  • Você pode usar qualquer interface do usuário em que desejar permitir que o usuário especifique os valores de corte inicial e final. O exemplo acima usa a propriedade Position do MediaPlaybackSession associada ao MediaPlayerElement para determinar primeiro qual MediaClip está reproduzindo na posição atual na composição verificando o StartTimeInComposition e EndTimeInComposition. Em seguida, as propriedades Position e StartTimeInComposition são usadas novamente para calcular o tempo de corte desde o início do clipe. o método FirstOrDefault é um método de extensão do namespace System.Linq que simplifica o código para selecionar itens de uma lista.
  • A propriedade OriginalDuration do objeto MediaClip permite que você saiba a duração do clipe de mídia sem qualquer recorte aplicado.
  • A propriedade TrimmedDuration permite saber a duração do clip de mídia depois da aplicação de corte.
  • Especificar um valor de corte maior do que a duração original do clipe não gera um erro. No entanto, se uma composição contém apenas um único clipe e está cortado para tamanho zero, especificando um valor de corte grande, uma chamada subsequente para GeneratePreviewMediaStreamSource retornará nulo, como se a composição não tivesse nenhum clipe.

Adicione uma faixa de áudio em segundo plano em uma composição

Para adicionar uma faixa em segundo plano para uma composição, carregue um arquivo de áudio e, em seguida, crie um objeto BackgroundAudioTrack chamando o método de fábrica BackgroundAudioTrack.CreateFromFileAsync. Em seguida, adicione o BackgroundAudioTrack à propriedade BackgroundAudioTracks da composição.

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

}
  • Um MediaComposition dá suporte a faixas de áudio em segundo plano nos seguintes formatos: MP3, WAV e FLAC

  • Uma faixa de áudio em segundo plano

  • Como com arquivos de vídeo, você deve usar a classe StorageApplicationPermissions para preservar o acesso a arquivos na composição.

  • Como com MediaClip, um BackgroundAudioTrack só pode ser incluído em uma composição uma vez. Tentar adicionar um BackgroundAudioTrack que já está sendo usado pela composição resultará em erro. Para reutilizar uma faixa de áudio várias vezes em uma composição, chame Clone para criar novos objetos MediaClip que podem então ser adicionados à composição.

  • Por padrão, faixas de áudio começam a reproduzir em segundo plano no início da composição. Se houver várias faixas em segundo plano, todas as faixas começarão a tocar no início da composição. Para fazer com que uma faixa de áudio comece a reproduzir em segundo plano em um outro momento, defina a propriedade Delay para o intervalo de tempo desejado.

Adicionar uma sobreposição de uma composição

Sobreposições permitem colocar várias camadas de vídeo umas sobre as outras como uma pilha. Uma composição pode conter várias camadas de sobreposição, cada uma delas pode incluir várias sobreposições. Crie um objeto MediaOverlay passando um MediaClip em seu construtor. Defina a posição e a opacidade da sobreposição e, em seguida, crie um novo MediaOverlayLayer e adicione o MediaOverlay para sua lista Overlays. Por fim, adicione o MediaOverlayLayer à lista OverlayLayers da composição.

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);
}
  • Sobreposições dentro de uma camada são ordenadas de Z a A com base em sua ordem na sua lista Overlays contendo a camada. Índices superiores dentro da lista são renderizados em cima de índices inferiores. O mesmo é verdadeiro para camadas de sobreposição dentro de uma composição. Uma camada com índice superior na lista OverlayLayers da composição será renderizada sobre índices inferiores.

  • Como sobreposições são empilhadas umas sobre as outras ao invés de serem reproduzidas sequencialmente, todas as sobreposições começarão a se reproduzir no início da composição por padrão. Para fazer com que uma sobreposição seja reproduzida em um outro momento, defina a propriedade Delay para o intervalo de tempo desejado.

Adicione efeitos a um clipe de mídia

Cada MediaClip em uma composição tem uma lista de efeitos de áudio e vídeos ao qual vários efeitos podem ser adicionados. Os efeitos devem implementar IAudioEffectDefinition e IVideoEffectDefinition respectivamente. O exemplo a seguir usa a posição atual do MediaPlayerElement para escolher o MediaClip visualizado no momento e, em seguida, cria uma nova instância do VideoStabilizationEffectDefinition e acrescenta-a à lista VideoEffectDefinitions do clipe de mídia.

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

Salvar uma composição em um arquivo

Composições de mídia podem ser serializadas para um arquivo para ser modificada em um momento posterior. Selecione um arquivo de saída e, em seguida, chame o método SaveAsync no MediaComposition para salvar a composição.

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

        };
    }
}

Carregar uma composição de um arquivo

Composições de mídia podem ser desserializadas de um arquivo para permitir que o usuário exiba e modifique a composição. Selecione um arquivo de composição e, em seguida, chame o método LoadAsync no MediaComposition para carregar a composição.

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");
        }
    }
}
  • Se o arquivo de mídia na composição não estiver em um local que possa ser acessado por seu aplicativo e não estiver na propriedade FutureAccessList da classe StorageApplicationPermissions para seu aplicativo, será gerado um erro ao carregar a composição.