Compositions multimédias et modification
Cet article vous montre comment utiliser les API de l’espace noms Windows.Media.Editing pour développer rapidement des applications permettant aux utilisateurs de créer des compositions multimédias à partir de fichiers sources audio et vidéo. Les fonctionnalités du framework incluent la possibilité d’ajouter par programme plusieurs clips vidéo ensemble, d’ajouter des superpositions d’images et de vidéos, d’ajouter un fond sonore et d’appliquer des effets audio et vidéo. Une fois créées, les compositions médiatiques peuvent être rendues dans un fichier multimédia plat pour la lecture ou le partage, mais les compositions peuvent également être sérialisées et désérialisées à partir du disque, ce qui permet à l’utilisateur de charger et de modifier les compositions qu’il a créées précédemment. Toutes ces fonctionnalités sont fournies dans une interface Windows Runtime facile à utiliser qui réduit considérablement la quantité et la complexité du code nécessaire pour effectuer ces tâches par rapport à l’API de bas niveau de Microsoft Media Foundation.
La classe MediaComposition est le conteneur de tous les clips multimédias qui composent la composition et est responsable du rendu de la composition finale, du chargement et de l’enregistrement des compositions sur disque et de la fourniture d’un flux de préversion de la composition afin que l’utilisateur puisse la visualiser dans l’interface utilisateur. Pour utiliser MediaComposition dans votre application, incluez l’espace de noms Windows.Media.Editing ainsi que l’espace de noms Windows.Media.Core qui fournit les applications connexes dont vous aurez besoin.
using Windows.Media.Editing;
using Windows.Media.Core;
using Windows.Media.Playback;
using System.Threading.Tasks;
L’objet MediaComposition sera accessible à partir de plusieurs points de votre code, c’est pourquoi vous déclarerez généralement une variable membre dans laquelle vous le stockerez.
private MediaComposition composition;
Le constructeur de MediaComposition ne prend aucun argument.
composition = new MediaComposition();
Les compositions de médias contiennent généralement un ou plusieurs clips vidéo. Vous pouvez utiliser un FileOpenPicker pour permettre à l’utilisateur de sélectionner un fichier vidéo. Une fois le fichier sélectionné, créez un nouvel objet MediaClip contenant le clip vidéo en appelant MediaClip.CreateFromFileAsync. Vous ajoutez ensuite le clip à la liste Clips de l’objet 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);
}
Les clips vidéo apparaissent dans MediaComposition dans le même ordre que dans la liste des clips.
Un MediaClip ne peut être inclus qu’une seule fois dans une composition. Si vous tentez d’ajouter un MediaClip déjà utilisé par la composition, vous obtiendrez une erreur. Pour réutiliser un clip vidéo plusieurs fois dans une composition, appelez Clone pour créer de nouveaux objets MediaClip qui peuvent ensuite être ajoutés à la composition.
Les applications Windows universelles n’ont pas la permission d’accéder à l’ensemble du système de fichiers. La propriété FutureAccessList de la classe StorageApplicationPermissions permet à votre application de stocker un enregistrement d’un fichier qui a été sélectionné par l’utilisateur afin que vous puissiez conserver les permissions d’accès au fichier. FutureAccessList a un maximum de 1 000 entrées. Votre application doit donc gérer la liste pour s’assurer qu’elle ne devient pas complète. Cela est particulièrement important si vous envisagez de prendre en charge le chargement et la modification de compositions créées précédemment.
Une MediaComposition prend en charge les clips vidéo au format MP4.
Si un fichier vidéo contient plusieurs pistes audio intégrées, vous pouvez sélectionner la piste audio utilisée dans la composition en définissant la propriété SelectedEmbeddedAudioTrackIndex.
Créez un MediaClip avec une seule couleur remplissant toute la trame en appelant CreateFromColor et en spécifiant une couleur et une durée pour le clip.
Créez un MediaClip à partir d’un fichier image en appelant CreateFromImageFileAsync et en spécifiant un fichier image et une durée pour le clip.
Créez un MediaClip à partir d’un IDirect3DSurface en appelant CreateFromSurface et en spécifiant une surface et une durée à partir du clip.
Pour permettre à l’utilisateur de visualiser la composition multimédia, ajoutez un MediaPlayerElement au fichier XAML qui définit votre interface utilisateur.
<MediaPlayerElement x:Name="mediaPlayerElement" AutoPlay="False" Margin="5" HorizontalAlignment="Stretch" AreTransportControlsEnabled="True" />
Déclarez une variable membre de type MediaStreamSource.
private MediaStreamSource mediaStreamSource;
Appelez la méthode GeneratePreviewMediaStreamSource de l’objet MediaComposition pour créer une MediaStreamSource pour la composition. Créez un objet MediaSource en appelant la méthode d’usine CreateFromMediaStreamSource et affectez-le à la propriété Source de l’élément MediaPlayerElement. La composition peut maintenant être visualisée dans l’interface utilisateur.
public void UpdateMediaElementSource()
{
mediaStreamSource = composition.GeneratePreviewMediaStreamSource(
(int)mediaPlayerElement.ActualWidth,
(int)mediaPlayerElement.ActualHeight);
mediaPlayerElement.Source = MediaSource.CreateFromMediaStreamSource(mediaStreamSource);
}
La MediaComposition doit contenir au moins un clip multimédia avant d’appeler GeneratePreviewMediaStreamSource, sinon l’objet renvoyé sera nul.
La chronologie du MediaElement n’est pas automatiquement mise à jour pour refléter les modifications apportées à la composition. Nous vous recommandons d’appeler GeneratePreviewMediaStreamSource et de définir la propriété Source MediaPlayerElement chaque fois que vous apportez un ensemble de modifications à la composition et que vous souhaitez mettre à jour l’interface utilisateur.
Il est recommandé de définir l’objet MediaStreamSource et la propriété Source du MediaPlayerElement sur null lorsque l’utilisateur quitte la page afin de libérer les ressources associées.
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
mediaPlayerElement.Source = null;
mediaStreamSource = null;
base.OnNavigatedFrom(e);
}
Pour convertir une composition multimédia en un fichier vidéo plat afin qu’il puisse être partagé et visualisé sur d’autres appareils, vous devrez utiliser les API de l’espace de noms Windows.Media.Transcoding. Pour mettre à jour l’interface utilisateur sur la progression de l’opération asynchrone, vous aurez également besoin des API de l’espace de noms Windows.UI.Core.
using Windows.Media.Transcoding;
using Windows.UI.Core;
Après avoir permis à l’utilisateur de sélectionner un fichier de sortie à l’aide d’un FileSavePicker, effectuez le rendu de la composition dans le fichier sélectionné en appelant la fonction RenderToFileAsync de l’objet MediaComposition. Le reste du code de l’exemple suivant suit simplement le modèle de gestion d’une 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");
}
}
- La préférence MediaTrimmingPreference vous permet de donner la priorité à la vitesse de l’opération de transcodage par rapport à la précision du découpage des clips multimédias adjacents. Fastpermet un transcodage plus rapide avec un découpage moins précis, Precise permet un transcodage plus lent mais avec un découpage plus précis.
Découpez la durée d’un clip vidéo dans une composition en définissant la propriété TrimTimeFromStart des objets MediaClip, la propriété TrimTimeFromEnd, ou les deux.
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;
}
- Vous pouvez utiliser n’importe quelle interface utilisateur pour permettre à l’utilisateur de spécifier les valeurs de début et de fin du découpage. L’exemple ci-dessus utilise la propriété Position de la MediaPlaybackSession associée à l’élément MediaPlayerElement pour déterminer d’abord quel MediaClip est lu à la position actuelle dans la composition en vérifiant les propriétés StartTimeInComposition et EndTimeInComposition. Ensuite, les propriétés Position et StartTimeInComposition sont à nouveau utilisées pour calculer la durée à découper à partir du début du clip. La méthode FirstOrDefault est une méthode d’extension de l’espace de noms System.Linq qui simplifie le code de sélection des éléments d’une liste.
- La propriété OriginalDuration de l’objet MediaClip vous permet de connaître la durée du clip multimédia sans aucun découpage.
- La propriété TrimmedDuration vous permet de connaître la durée du clip multimédia après l’application d’un découpage.
- La spécification d’une valeur de découpage supérieure à la durée originale du clip n’entraîne pas d’erreur. Toutefois, si une composition ne contient qu’un seul clip et que celui-ci est réduit à zéro en spécifiant une valeur de découpage élevée, un appel ultérieur à GeneratePreviewMediaStreamSource renverra null, comme si la composition n’avait pas de clips.
Pour ajouter une piste d’arrière-plan à une composition, chargez un fichier audio, puis créez un objet BackgroundAudioTrack en appelant la méthode d’usine BackgroundAudioTrack.CreateFromFileAsync. Ajoutez ensuite la piste BackgroundAudioTrack à la propriété BackgroundAudioTracks de la composition.
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);
}
Une MediaComposition prend en charge les pistes audio d’arrière-plan dans les formats suivants : MP3, WAV, FLAC
Une piste audio d’arrière-plan
Comme pour les fichiers vidéo, vous devez utiliser la classe StorageApplicationPermissions pour préserver l’accès aux fichiers de la composition.
Comme pour les MediaClip, une BackgroundAudioTrack ne peut être incluse qu’une seule fois dans une composition. Si vous tentez d’ajouter une BackgroundAudioTrack déjà utilisée par la composition, vous obtiendrez une erreur. Pour réutiliser une piste audio plusieurs fois dans une composition, appelez Clone pour créer de nouveaux objets MediaClip qui peuvent ensuite être ajoutés à la composition.
Par défaut, les pistes audio d’arrière-plan commencent à être jouées au début de la composition. Si plusieurs pistes d’arrière-plan sont présentes, toutes les pistes commenceront à être lues au début de la composition. Pour qu’une piste audio d’arrière-plan commence à être lue à un autre moment, définissez la propriété Délai sur le décalage temporel souhaité.
Les incrustations vous permettent de superposer plusieurs couches de vidéo dans une composition. Une composition peut contenir plusieurs couches d’incrustation, chacune d’entre elles pouvant inclure plusieurs incrustations. Créez un objet MediaOverlay en passant un MediaClip dans son constructeur. Définissez la position et l’opacité de la superposition, puis créez un nouveau MediaOverlayLayer et ajoutez le MediaOverlay à sa liste de superpositions. Enfin, ajoutez le MediaOverlayLayer à la liste OverlayLayers de la composition.
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);
}
Les superpositions d’un calque sont classées par ordre z en fonction de leur ordre dans la liste des superpositions du calque qui les contient. Les indices supérieurs de la liste sont rendus au-dessus des indices inférieurs. Il en va de même pour les calques de superposition au sein d’une composition. Une couche ayant un indice supérieur dans la liste OverlayLayers de la composition sera rendue au-dessus des indices inférieurs.
Étant donné que les superpositions sont empilées les unes sur les autres au lieu d’être jouées de manière séquentielle, toutes les superpositions commencent la lecture au début de la composition par défaut. Pour qu’une superposition commence à être lue à un autre moment, définissez la propriété Délai sur le décalage temporel souhaité.
Chaque MediaClip d’une composition possède une liste d’effets audio et vidéo à laquelle il est possible d’ajouter plusieurs effets. Les effets doivent implémenter respectivement IAudioEffectDefinition et IVideoEffectDefinition. L’exemple suivant utilise la position actuelle de l’élément MediaPlayerElement pour choisir le MediaClip actuellement visualisé, puis crée une nouvelle instance de VideoStabilizationEffectDefinition et l’ajoute à la liste VideoEffectDefinitions du clip multimé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);
}
Les compositions de médias peuvent être sérialisées dans un fichier afin d’être modifiées ultérieurement. Choisissez un fichier de sortie, puis appelez la méthode SaveAsync de MediaComposition pour enregistrer la composition.
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");
}
};
}
}
Les compositions de médias peuvent être désérialisées à partir d’un fichier pour permettre à l’utilisateur de visualiser et de modifier la composition. Choisissez un fichier de composition, puis appelez la méthode LoadAsync de MediaComposition pour charger la composition.
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 fichier média de la composition ne se trouve pas dans un emplacement auquel votre application peut accéder et ne figure pas dans la propriété FutureAccessList de la classe StorageApplicationPermissions de votre application, une erreur sera générée lors du chargement de la composition.