Composizioni multimediali e modifica

Questo articolo illustra come le API nello spazio dei nomi Windows.Media.Editing consentono di sviluppare rapidamente app che consentono agli utenti di creare composizioni multimediali da file di origine audio e video. Le funzionalità del framework includono la possibilità di aggiungere più clip video a livello di codice, aggiungere sovrimpressioni video e immagini, aggiungere audio in background e applicare effetti audio e video. Dopo la creazione, è possibile eseguire il rendering delle composizioni multimediali in un file multimediale flat per la riproduzione o la condivisione, ma le composizioni possono anche essere serializzate e deserializzate dal disco, consentendo all'utente di caricare e modificare le composizioni create in precedenza. Tutte queste funzionalità sono disponibili in un'interfaccia di Windows Runtime facile da usare che riduce notevolmente la quantità e la complessità del codice necessario per eseguire queste attività rispetto all'API Microsoft Media Foundation di basso livello.

Creare una nuova composizione multimediale

La classe MediaComposition è il contenitore per tutte le clip multimediali che costituiscono la composizione ed è responsabile del rendering della composizione finale, del caricamento e del salvataggio delle composizioni su disco e della fornitura di un flusso di anteprima della composizione in modo che l'utente possa visualizzarlo nell'interfaccia utente. Per usare MediaComposition nell'app, includere lo spazio dei nomi Windows.Media.Editing e lospazio dei nomi Windows.Media.Core che fornisce API correlate necessarie.

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

L'oggetto MediaComposition sarà accessibile da più punti nel codice, quindi in genere dichiarerai una variabile membro in cui archiviarla.

private MediaComposition composition;

Il costruttore per MediaComposition non accetta argomenti.

composition = new MediaComposition();

Aggiungere clip multimediali a una composizione

Le composizioni multimediali in genere contengono una o più clip video. Si può usare un FileOpenPicker per consentire all'utente di selezionare un file video. Dopo aver selezionato il file, creare un nuovo oggetto MediaClip per contenere il clip video chiamando MediaClip.CreateFromFileAsync. Aggiungere quindi il clip all'elenco Clip dell'oggetto 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);

}
  • Le clip multimediali vengono visualizzate in MediaComposition nello stesso ordine in cui vengono visualizzate nell'elenco Clips list.

  • Un MediaClip può essere incluso una sola volta in una composizione. Il tentativo di aggiungere un MediaClip già usato dalla composizione genererà un errore. Per riutilizzare più volte un clip video in una composizione, chiamare Clone per creare nuovi oggetti MediaClip che possono quindi essere aggiunti alla composizione.

  • Le app di Windows universali non dispongono dell'autorizzazione per accedere all'intero file system. La proprietà FutureAccessList della classe StorageApplicationPermissions consente all'app di archiviare un record di un file selezionato dall'utente in modo da poter conservare le autorizzazioni per accedere al file. FutureAccessList ha un massimo di 1000 voci, quindi l'app deve gestire l'elenco per assicurarsi che non diventi completa. Ciò è particolarmente importante se si prevede di supportare il caricamento e la modifica delle composizioni create in precedenza.

  • MediaComposition supporta clip video in formato MP4.

  • Se un file video contiene più tracce audio incorporate, è possibile selezionare la traccia audio utilizzata nella composizione impostando la proprietà SelectedEmbeddedAudioTrackIndex.

  • Creare un MediaClip con un singolo colore che riempie l'intero fotogramma chiamando CreateFromColor e specificando un colore e una durata per la clip.

  • Creare un MediaClip da un file immagine chiamando CreateFromImageFileAsync e specificando un file di immagine e una durata per il clip.

  • Creare un MediaClip da un oggetto IDirect3DSurface chiamando CreateFromSurface e specificando una superficie e una durata dal clip.

Visualizzare in anteprima la composizione in un oggetto MediaElement

Per consentire all'utente di visualizzare la composizione multimediale, aggiungere MediaPlayerElement al file XAML che definisce l'interfaccia utente.

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

Dichiarare una variabile membro di tipo MediaStreamSource.

private MediaStreamSource mediaStreamSource;

Chiamare GeneratePreviewMediaStreamSource dell'oggetto MediaComposition per creare un oggetto MediaStreamSource per la composizione. Creare un oggetto MediaSource chiamando il metodo di fabbrica CreateFromMediaStreamSource e assegnarlo alla proprietà Source di MediaPlayerElement. Ora la composizione può essere visualizzata nell'interfaccia utente.

public void UpdateMediaElementSource()
{

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

    mediaPlayerElement.Source = MediaSource.CreateFromMediaStreamSource(mediaStreamSource);

}
  • MediaComposition deve contenere almeno una clip multimediale prima di chiamare GeneratePreviewMediaStreamSource, oppure l'oggetto restituito sarà Null.

  • La sequenza temporale di MediaElement non viene aggiornata automaticamente per riflettere le modifiche apportate alla composizione. È consigliabile chiamare sia GeneratePreviewMediaStreamSource che impostare la proprietà MediaPlayerElementSource ogni volta che si apporta un set di modifiche alla composizione e si vuole aggiornare l'interfaccia utente.

È consigliabile impostare l'oggetto MediaStreamSource e la proprietà Source di MediaPlayerElement su null quando l'utente si allontana dalla pagina per rilasciare le risorse associate.

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

}

Eseguire il rendering della composizione in un file video

Per eseguire il rendering di una composizione multimediale in un file video flat in modo che possa essere condiviso e visualizzato in altri dispositivi, è necessario usare le API dello spazio dei nomi Windows.Media.Transcoding. Per aggiornare l'interfaccia utente sullo stato di avanzamento dell'operazione asincrona, sono necessarie anche API dallo spazio dei nomi Windows.UI.Core.

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

Dopo aver consentito all'utente di selezionare un file di output con un oggetto FileSavePicker, eseguire il rendering della composizione nel file selezionato chiamando RenderToFileAsync dell'oggetto MediaComposition. Il resto del codice nell'esempio seguente segue semplicemente il modello di gestione di 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");
    }
}
  • The MediaTrimmingPreference consente di classificare in ordine di priorità la velocità dell'operazione di transcodifica rispetto alla precisione del taglio di clip multimediali adiacenti. Fast fa sì che la transcodifica sia più veloce con taglio con precisione inferiore, Precise fa sì che la transcodifica sia più lenta ma con taglio più preciso.

Tagliare un video clip

Tagliare la durata di un clip video in una composizione impostando la proprietà TrimTimeFromStart degli oggetti MediaClip, la proprietà TrimTimeFromEnd, o entrambe.

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;

}
  • Si può usare qualsiasi interfaccia utente che vuoi consentire all'utente di specificare i valori di taglio iniziale e finale. Nell'esempio precedente viene utilizzata la proprietà Position di MediaPlaybackSession associata con MediaPlayerElement per determinare prima di tutto quale MediaClip viene riprodotto nella posizione corrente nella composizione controllando StartTimeInComposition e EndTimeInComposition. Le proprietà Position e StartTimeInComposition vengono quindi usate di nuovo per calcolare la quantità di tempo da tagliare dall'inizio della clip. Il metodo FirstOrDefault è un metodo di estensione dello spazio dei nomi System.Linq che semplifica il codice per la selezione di elementi da un elenco.
  • La proprietà OriginalDuration dell'oggetto MediaClip consente di conoscere la durata della clip multimediale senza alcun ritaglio applicato.
  • La proprietà TrimmedDuration consente di conoscere la durata della clip multimediale dopo l'applicazione del taglio.
  • Se si specifica un valore di taglio maggiore della durata originale della clip, non viene generato un errore. Tuttavia, se una composizione contiene solo un singolo clip e che viene tagliato a lunghezza zero specificando un valore di taglio di grandi dimensioni, una chiamata successiva a GeneratePreviewMediaStreamSource restituirà null, come se la composizione non contenga clip.

Aggiungere una traccia audio in background a una composizione

Per aggiungere una traccia di sfondo a una composizione, caricare un file audio e quindi creare un oggetto BackgroundAudioTrack chiamando il metodo di fabbrica BackgroundAudioTrack.CreateFromFileAsync. Aggiungere quindi BackgroundAudioTrack alla proprietà BackgroundAudioTracks della composizione.

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 supporta tracce audio in background nei formati seguenti: MP3, WAV, FLAC

  • Traccia audio in background

  • Come per i file video, è consigliabile usare la classe StorageApplicationPermissions per mantenere l'accesso ai file nella composizione.

  • Come per MediaClip, un BackgroundAudioTrack può essere incluso una sola volta in una composizione. Il tentativo di aggiungere un BackgroundAudioTrack già usato dalla composizione genererà un errore. Per riutilizzare più volte una traccia audio in una composizione, chiamare Clone per creare nuovi oggetti MediaClip che possono quindi essere aggiunti alla composizione.

  • Per impostazione predefinita, le tracce audio in background iniziano a essere riprodotte all'inizio della composizione. Se sono presenti più tracce di sfondo, tutte le tracce inizieranno a suonare all'inizio della composizione. Per fare in modo che una traccia audio in background inizi la riproduzione in un altro momento, impostare la proprietà Delay sull'offset temporale desiderato.

Aggiungere una sovrimpressione a una composizione

Le sovrimpressioni consentono di impilare più livelli di video uno sopra l'altro in una composizione. Una composizione può contenere più livelli di sovrapposizione, ognuno dei quali può includere più sovrapposizioni. Creare un oggetto MediaOverlay passando un oggetto MediaClip al relativo costruttore. Impostare la posizione e l'opacità della sovrimpressione, quindi creare un nuovo MediaOverlayLayer e aggiungere MediaOverlay al suo elenco Overlays. Infine, aggiungere MediaOverlayLayer all'elenco OverlayLayers della composizione.

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);
}
  • Le sovrimpressioni all'interno di un livello vengono ordinate in base all'ordine nell'elenco Sovrimpressione del livello contenitore. Il rendering di indici più elevati all'interno dell'elenco viene eseguito sopra gli indici inferiori. Lo stesso vale per i livelli sovrapposti all'interno di una composizione. Verrà eseguito il rendering di un livello con un indice superiore nell'elenco OverlayLayers della composizione sopra gli indici inferiori.

  • Poiché le sovrimpressioni vengono sovrapposte tra loro anziché essere riprodotte in sequenza, tutte le sovrimpressioni iniziano la riproduzione all'inizio della composizione per impostazione predefinita. Per fare in modo che una sovrimpressione inizi la riproduzione in un altro momento, impostare la proprietà Delay sull'offset temporale desiderato.

Aggiungere effetti a un clip multimediale

Ogni MediaClip in una composizione include un elenco di effetti audio e video a cui è possibile aggiungere più effetti. Gli effetti devono implementare rispettivamente IAudioEffectDefinition e IVideoEffectDefinition. Nell'esempio seguente viene utilizzata la posizione MediaPlayerElement corrente per scegliere l'oggetto MediaPlayerElement attualmente visualizzato MediaClip e quindi viene creata una nuova istanza di VideoStabilizationEffectDefinition e la aggiunge all'elenco VideoEffectDefinitions della clip multimediale.

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

Salvare una composizione in un file

Le composizioni multimediali possono essere serializzate in un file da modificare in un secondo momento. Selezionare un file di output e quindi chiamare il metodo MediaCompositionSaveAsync per salvare la composizione.

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

        };
    }
}

Caricare una composizione da un file

Le composizioni multimediali possono essere deserializzate da un file per consentire all'utente di visualizzare e modificare la composizione. Selezionare un file di composizione e quindi chiamare il metodo MediaCompositionLoadAsync per caricare la composizione.

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 un file multimediale nella composizione non si trova in una posizione accessibile dalla tua app e non si trova nella proprietà FutureAccessList della classe StorageApplicationPermissions per l'app, viene generato un errore durante il caricamento della composizione.