Composizioni multimediali e modifica

Questo articolo illustra come usare le API del namespace Windows.Media.Editing per sviluppare rapidamente app che consentano agli utenti di creare composizioni multimediali da file audio e video di origine. 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à vengono fornite in un'interfaccia Windows Runtime facile da usare che riduce notevolmente la quantità e la complessità del codice necessarie per eseguire queste attività rispetto all'API di basso livello Microsoft Media Foundation.

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 nella tua app, includi lo spazio dei nomi Windows.Media.Editing e lo spazio dei nomi Windows.Media.Core, che fornisce le 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 = null!;

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. È possibile 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. Aggiungi quindi il clip all'elenco Clip dell'oggetto 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);
}
  • I clip multimediali vengono visualizzati in MediaComposition nello stesso ordine in cui vengono visualizzati nell'elenco Clip .

  • 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, chiama Clone per creare nuovi oggetti MediaClip che possono quindi essere aggiunti alla composizione.

  • 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 di immagine chiamando CreateFromImageFileAsync e specificando un file di immagine e una durata per il clip.

  • Creare un MediaClip da un IDirect3DSurface chiamando CreateFromSurface e specificando una superficie e la durata del clip.

Visualizzare in anteprima la composizione in mediaPlayerElement

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"
    AreTransportControlsEnabled="True"
    HorizontalAlignment="Stretch"
    MinHeight="360" />

Dichiarare una variabile membro di tipo MediaStreamSource.

private MediaStreamSource mediaStreamSource = null!;

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

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 deve contenere almeno una clip multimediale prima di chiamare GeneratePreviewMediaStreamSource oppure l'oggetto restituito sarà Null.

  • La sequenza temporale di MediaPlayerElement 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 la finestra viene chiusa per rilasciare le risorse associate.

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

Eseguire il rendering della composizione in un file video

Per eseguire il rendering di una composizione multimediale in un singolo file video, in modo che possa essere condiviso e visualizzato su altri dispositivi, è necessario utilizzare le API del namespace Windows.Media.Transcoding.

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

Dopo aver consentito all'utente di selezionare un file di output con un FileSavePicker, eseguire il rendering della composizione nel file selezionato chiamando il MediaCompositionRenderToFileAsync. Il resto del codice nell'esempio seguente segue semplicemente il modello di gestione di 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");
    }
}
  • Il 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 clip video

Ridurre la durata di un clip video in una composizione impostando la proprietà MediaClip oggetti TrimTimeFromStart, proprietà TrimTimeFromEnd, o entrambi.

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;
}
  • È possibile usare qualsiasi interfaccia utente che si vuole consentire all'utente di specificare i valori di taglio iniziale e finale. Nell'esempio precedente viene utilizzata la proprietà Position della proprietà MediaPlaybackSession associata al MediaPlayerElement per determinare prima quale MediaClip viene riprodotto nella posizione corrente della composizione controllando il 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 ti 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 una sola clip e questa viene ridotta a una lunghezza pari a zero specificando un valore di ritaglio elevato, una chiamata successiva a GeneratePreviewMediaStreamSource restituirà null, come se la composizione non contenesse 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 factory BackgroundAudioTrack.CreateFromFileAsync. Quindi, aggiungere BackgroundAudioTrack alla proprietà BackgroundAudioTracks della composizione.

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

  • Una traccia audio di sottofondo

  • Come per MediaClip, un BackgroundAudioTrack può essere incluso una sola volta in una composizione. Se si tenta di aggiungere un oggetto BackgroundAudioTrack già usato dalla composizione, verrà generato un errore. Per riutilizzare più volte una traccia audio in una composizione, chiama Clone per creare nuovi oggetti BackgroundAudioTrack 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 nel relativo costruttore. Impostare la posizione e l'opacità della sovrimpressione, creare quindi un nuovo MediaOverlayLayer e aggiungere l'elenco MediaOverlay al relativo Overlays. Infine, aggiungere MediaOverlayLayer all'elenco OverlayLayers della composizione.

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);
}
  • Le sovrimpressioni all'interno di un livello sono ordinate sull'asse z in base al loro ordine nell'elenco Overlays del livello che le contiene. Gli indici superiori nell'elenco vengono visualizzati sopra quelli 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 un elemento in sovrimpressione avvii la riproduzione in un altro momento, impostare la proprietà Delay sul valore di 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 IAudioEffectDefinition e IVideoEffectDefinition rispettivamente. L'esempio seguente usa la posizione corrente di MediaPlayerElement per selezionare il MediaClip attualmente visualizzato, quindi crea una nuova istanza di VideoStabilizationEffectDefinition e la aggiunge alla lista VideoEffectDefinitions del clip multimediale.

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

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 MediaComposition metodo SaveAsync per salvare la composizione.

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

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 MediaComposition metodo LoadAsync per caricare la composizione.

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");
        }
    }
}
  • Se un file multimediale nella composizione non si trova in una posizione accessibile dall'app, viene generato un errore durante il caricamento della composizione.

Vedere anche