Effets vidéo personnalisés

Cet article explique comment créer un composant Windows Runtime implémentant l’interface IBasicVideoEffect pour créer des effets personnalisés de flux vidéo. Vous pouvez utiliser les effets personnalisés avec plusieurs API Windows Runtime différentes, notamment MediaCapture, qui fournit un accès à la caméra d’un appareil, et MediaComposition, qui vous permet de créer des compositions complexes à partir de clips multimédias.

Ajouter un effet personnalisé à votre application

Un effet vidéo personnalisé est défini dans une classe qui implémente l’interface IBasicVideoEffect. Cette classe ne peut pas être incluse directement dans le projet de votre application. À la place, vous devez utiliser un composant Windows Runtime pour héberger votre classe d’effet vidéo.

Ajouter un composant Windows Runtime pour votre effet vidéo

  1. Dans Microsoft Visual Studio, quand votre solution est ouverte, accédez au menu Fichier, sélectionnez Ajouter->Nouveau projet.
  2. Sélectionnez le type de projet Composant Windows Runtime (Windows universel).
  3. Pour cet exemple, nommez le projet VideoEffectComponent. Ce nom sera référencé dans le code ultérieurement.
  4. Cliquez sur OK.
  5. Le modèle de projet crée une classe appelée Class1.cs. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur l’icône de Class1.cs et sélectionnez Renommer.
  6. Renommez le fichier ExampleVideoEffect.cs. Visual Studio affiche une invite vous demandant si vous voulez mettre à jour toutes les références sous le nouveau nom. Cliquez sur Oui.
  7. Ouvrez ExampleVideoEffect.cs et mettez à jour la définition de classe pour implémenter l’interface IBasicVideoEffect.
public sealed class ExampleVideoEffect : IBasicVideoEffect

Vous devez inclure les espaces de noms suivants dans votre fichier de classe effet afin d’accéder à tous les types utilisés dans les exemples de cet article.

using Windows.Media.Effects;
using Windows.Media.MediaProperties;
using Windows.Foundation.Collections;
using Windows.Graphics.DirectX.Direct3D11;
using Windows.Graphics.Imaging;

Implémentez l’interface IBasicVideoEffect à l’aide du traitement logiciel

Votre effet vidéo doit implémenter toutes les méthodes et propriétés de l’interface IBasicVideoEffect . Cette section vous explique la procédure d’implémentation simple de cette interface à l’aide d’un traitement logiciel.

Close, méthode

Le système appelle la méthode Fermer sur votre classe lorsque l’effet doit être arrêté. Vous devez utiliser cette méthode pour supprimer les ressources que vous avez créées. L’argument de la méthode est un MediaEffectClosedReason qui vous permet de savoir si l’effet a été fermé normalement, si une erreur s’est produite ou si l’effet ne prend pas en charge le format d’encodage requis.

public void Close(MediaEffectClosedReason reason)
{
    // Dispose of effect resources
}

Méthode DiscardQueuedFrames

La méthode DiscardQueuedFrames est appelée lorsque votre effet doit être réinitialisé. Dans ce cas, le scénario courant est que l’effet stocke les trames précédemment traitées pour les utiliser dans le traitement de la trame active. Quand cette méthode est appelée, vous devez supprimer l’ensemble des trames précédentes que vous avez enregistrées. Cette méthode peut être utilisée pour réinitialiser un état associé aux trames précédentes, pas seulement les trames vidéo cumulées.

private int frameCount;
public void DiscardQueuedFrames()
{
    frameCount = 0;
}

IsReadOnly, propriété

La propriété IsReadOnly permet au système de savoir si votre effet doit écrire dans la sortie de l’effet. Si votre application ne modifie pas les trames vidéo (par exemple, un effet qui effectue seulement une analyse des trames vidéo), vous devez définir cette propriété sur true. Ainsi, le système copie efficacement à votre place l’entrée de trame dans la sortie de trame.

Conseil

Quand la propriété IsReadOnly est définie sur true, le système copie la trame d’entrée sur la trame de sortie avant l’appel de ProcessFrame. Le fait de définir la propriété IsReadOnly sur true ne vous empêche pas d’écrire sur les trames de sortie de l’effet dans ProcessFrame.

public bool IsReadOnly { get { return false; } }

Méthode SetEncodingProperties

Le système appelle SetEncodingProperties sur votre effet pour vous indiquer les propriétés d’encodage du flux vidéo sur lequel l’effet fonctionne. Cette méthode fournit également une référence à l’appareil Direct3D utilisé pour le rendu matériel. L’utilisation de cet appareil est expliquée dans l’exemple de traitement matériel plus loin dans cet article.

private VideoEncodingProperties encodingProperties;
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
    this.encodingProperties = encodingProperties;
}

Propriété SupportedEncodingProperties

Le système vérifie la propriété SupportedEncodingProperties pour déterminer quelles propriétés d’encodage sont prises en charge par votre effet. Notez que si le consommateur de votre effet ne peut pas encoder une vidéo en utilisant les propriétés que vous spécifiez, il appelle Close sur votre effet et supprime votre effet du pipeline vidéo.

public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties
{            
    get
    {
        var encodingProperties = new VideoEncodingProperties();
        encodingProperties.Subtype = "ARGB32";
        return new List<VideoEncodingProperties>() { encodingProperties };

        // If the list is empty, the encoding type will be ARGB32.
        // return new List<VideoEncodingProperties>();
    }
}

Notes

Si vous renvoyez une liste vide d’objets VideoEncodingProperties à partir de SupportedEncodingProperties, le système utilise par défaut le codage ARGB32.

 

Propriété SupportedMemoryTypes

Le système vérifie que la propriété SupportedMemoryTypes pour déterminer si l’effet va accéder aux trames vidéo dans la mémoire du logiciel ou dans la mémoire du matériel (GPU). Si vous retournez MediaMemoryTypes.Cpu, votre effet sera transmis aux trames d’entrée et de sortie qui contiennent des données d’image dans des objets SoftwareBitmap . Si vous renvoyez MediaMemoryTypes.Gpu, l’effet sera transmis aux trames en entrée et en sortie qui contiennent des données d’image dans les objets IDirect3DSurface.

public MediaMemoryTypes SupportedMemoryTypes { get { return MediaMemoryTypes.Cpu; } }

Notes

Si vous spécifiez MediaMemoryTypes.GpuAndCpu, le système utilise la mémoire du GPU ou du système, selon laquelle est la plus performante pour le pipeline. Quand vous utilisez cette valeur, vous devez vérifier la méthode ProcessFrame pour voir si le SoftwareBitmap ou le IDirect3DSurface transmis dans la méthode contient des données, puis traiter la trame en fonction.

 

Propriété TimeIndependent

La propriété TimeIndependent permet au système de savoir si votre effet n’exige pas un minutage uniforme. Lorsqu’elle est définie sur true, le système peut utiliser les optimisations qui améliorent la performance de l’effet.

public bool TimeIndependent { get { return true; } }

Méthode SetProperties

La méthode SetProperties permet à l’application qui utilise votre effet d’ajuster les paramètres d’effet. Les propriétés sont transmises sous la forme d’une carte IPropertySet de noms de propriétés et de valeurs.

private IPropertySet configuration;
public void SetProperties(IPropertySet configuration)
{
    this.configuration = configuration;
}

Cet exemple simple estompe les pixels dans chaque trame vidéo en fonction d’une valeur spécifiée. Une propriété est déclarée et TryGetValue est utilisé pour obtenir la valeur définie par l’application d’appel. Si aucune valeur n’a été définie, la valeur par défaut .5 est utilisée.

public double FadeValue
{
    get
    {
        object val;
        if (configuration != null && configuration.TryGetValue("FadeValue", out val))
        {
            return (double)val;
        }
        return .5;
    }
}

Méthode ProcessFrame

C’est dans la méthode ProcessFrame que l’effet modifie les données d’image de la vidéo. La méthode est appelée une fois par trame et un objet ProcessVideoFrameContext lui est transmis. Cet objet contient un objet VideoFrame en entrée qui contient la trame entrante à traiter et un objet VideoFrame en sortie sur lequel vous écrivez des données d’image qui seront transmises dans le reste du pipeline vidéo. Chacun de ces objets VideoFrame comporte une propriété SoftwareBitmap et une propriété Direct3DSurface, mais lesquels d’entre eux peuvent être utilisés est déterminé par la valeur renvoyée à partir de la propriété SupportedMemoryTypes.

Cet exemple montre une implémentation simple de la méthode ProcessFrame à l’aide du traitement logiciel. Pour plus d’informations sur l’utilisation des objets SoftwareBitmap, voir Acquisition d’images. Un exemple d’implémentation de ProcessFrame à l’aide du traitement logiciel est illustré plus loin dans cet article.

L’accès à la mémoire tampon de données d’un SoftwareBitmap nécessite l’interopérabilité COM. Vous devez donc inclure l’espace de noms System.Runtime.InteropServices dans votre fichier de classe effet.

using System.Runtime.InteropServices;

Ajoutez le code suivant à l’intérieur de l’espace de noms de l’effet pour importer l’interface et accéder à la mémoire tampon d’image.

[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
    void GetBuffer(out byte* buffer, out uint capacity);
}

Notes

Dans la mesure où cette technique accède à une mémoire tampon d’image native non gérée, vous devez configurer votre projet pour autoriser du code unsafe.

  1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet VideoEffectComponent et sélectionnez Propriétés.
  2. Sélectionnez l’onglet Build .
  3. Sélectionnez la zone Autoriser le code non sécurisé case activée.

 

Vous pouvez maintenant ajouter l’implémentation de la méthode ProcessFrame. Tout d’abord, cette méthode obtient un objet BitmapBuffer à partir des bitmaps logiciels en entrée et en sortie. Notez que la trame en sortie est ouverte pour l’écriture, et que la trame en entrée l’est pour la lecture. Ensuite, un IMemoryBufferReference est obtenu pour chaque tampon en appelant CreateReference. Ensuite, le tampon de données réel est obtenu en transtypant les objets IMemoryBufferReference en tant qu’interface d’interopérabilité COM définie ci-dessus, IMemoryByteAccess, puis en appelant GetBuffer.

Maintenant que les tampons de données ont été obtenus, vous pouvez lire à partir du tampon en entrée et écrire sur le tampon en sortie. La disposition de la mémoire tampon est obtenue en appelant GetPlaneDescription, qui fournit des informations sur la largeur, la foulée et le décalage initial de la mémoire tampon. Les bits par pixel sont déterminés par les propriétés d’encodage définies précédemment avec la méthode SetEncodingProperties . Les informations sur le format du tampon sont utilisées pour trouver l’index dans le tampon pour chaque pixel. La valeur du pixel du tampon source est copiée dans le tampon cible. Les valeurs de couleur sont multipliées par la propriété FadeValue définie pour cet effet afin de les diminuer du montant spécifié.

public unsafe void ProcessFrame(ProcessVideoFrameContext context)
{
    using (BitmapBuffer buffer = context.InputFrame.SoftwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
    using (BitmapBuffer targetBuffer = context.OutputFrame.SoftwareBitmap.LockBuffer(BitmapBufferAccessMode.Write))
    {
        using (var reference = buffer.CreateReference())
        using (var targetReference = targetBuffer.CreateReference())
        {
            byte* dataInBytes;
            uint capacity;
            ((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacity);

            byte* targetDataInBytes;
            uint targetCapacity;
            ((IMemoryBufferByteAccess)targetReference).GetBuffer(out targetDataInBytes, out targetCapacity);

            var fadeValue = FadeValue;

            // Fill-in the BGRA plane
            BitmapPlaneDescription bufferLayout = buffer.GetPlaneDescription(0);
            for (int i = 0; i < bufferLayout.Height; i++)
            {
                for (int j = 0; j < bufferLayout.Width; j++)
                {

                    byte value = (byte)((float)j / bufferLayout.Width * 255);

                    int bytesPerPixel = 4; 
                    if (encodingProperties.Subtype != "ARGB32")
                    {
                        // If you support other encodings, adjust index into the buffer accordingly
                    }
                    

                    int idx = bufferLayout.StartIndex + bufferLayout.Stride * i + bytesPerPixel * j;

                    targetDataInBytes[idx + 0] = (byte)(fadeValue * (float)dataInBytes[idx + 0]);
                    targetDataInBytes[idx + 1] = (byte)(fadeValue * (float)dataInBytes[idx + 1]);
                    targetDataInBytes[idx + 2] = (byte)(fadeValue * (float)dataInBytes[idx + 2]);
                    targetDataInBytes[idx + 3] = dataInBytes[idx + 3];
                }
            }
        }
    }
}

Implémenter l’interface IBasicVideoEffect à l’aide du traitement matériel

La création d’un effet vidéo personnalisé à l’aide du traitement matériel (GPU) est presque identique à l’utilisation du traitement logiciel tel que décrit ci-dessus. Cette section vous explique les différences dans un effet qui utilise le traitement matériel. Cet exemple utilise l’API Windows Runtime Win2D. Pour plus d’informations sur l’utilisation de Win2D, voir la documentation Win2D.

Utilisez les étapes suivantes pour ajouter le package NuGet Win2D au projet que vous avez créé comme décrit dans la section Ajouter un effet personnalisé à votre application au début de cet article.

Pour ajouter le package NuGet Win2D à votre projet d’effet

  1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet VideoEffectComponent et sélectionnez Gérer les packages NuGet.
  2. En haut de la fenêtre, sélectionnez l’onglet Explorer.
  3. Dans la zone de recherche, entrez Win2D.
  4. Sélectionnez Win2D.uwp, puis Installer dans le volet droit.
  5. La boîte de dialogue Examiner les modifications vous indique le package à installer. Cliquez sur OK.
  6. Acceptez la licence de package.

Outre les espaces de noms inclus dans l’installation de base du projet, vous devez inclure les espaces de noms suivants fournis par Win2D.

using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas;

Dans la mesure où cet effet utilisera la mémoire GPU pour les opérations sur les données d’image, vous devez renvoyer MediaMemoryTypes.Gpu à partir de la propriété SupportedMemoryTypes.

public MediaMemoryTypes SupportedMemoryTypes { get { return MediaMemoryTypes.Gpu; } }

Définissez les propriétés de codage que votre effet prend en charge avec la propriété SupportedEncodingProperties. Quand vous utilisez Win2D, vous devez utiliser le codage ARGB32.

public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties {
    get
    {
        var encodingProperties = new VideoEncodingProperties();
        encodingProperties.Subtype = "ARGB32";
        return new List<VideoEncodingProperties>() { encodingProperties };
    }
}

Utilisez la méthode SetEncodingProperties pour créer un nouvel objet CanvasDevice Win2D à partir du IDirect3DDevice transmis dans la méthode.

private CanvasDevice canvasDevice;
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
    canvasDevice = CanvasDevice.CreateFromDirect3D11Device(device);
}

L’implémentation de SetProperties est identique à l’exemple de traitement logiciel précédent. Cet exemple utilise une propriété BlurAmount pour configurer un effet de flou Win2D.

private IPropertySet configuration;
public void SetProperties(IPropertySet configuration)
{
    this.configuration = configuration;
}
public double BlurAmount
{
    get
    {
        object val;
        if (configuration != null && configuration.TryGetValue("BlurAmount", out val))
        {
            return (double)val;
        }
        return 3;
    }
}

La dernière étape consiste à implémenter la méthode ProcessFrame qui traite réellement les données d’image.

À l’aide d’API Win2D, un CanvasBitmap est créé à partir de la propriété Direct3DSurface de la trame en entrée. Un CanvasRenderTarget est créé à partir du Direct3DSurface de la trame en sortie et un CanvasDrawingSession est créé à partir de cette cible de rendu. Un nouveau GaussianBlurEffect Win2D est initialisé à l’aide de la propriété BlurAmount que l’effet expose via SetProperties. Enfin, la méthode CanvasDrawingSession.DrawImage est appelée pour dessiner le bitmap en entrée sur la cible de rendu à l’aide de l’effet de flou.

public void ProcessFrame(ProcessVideoFrameContext context)
{

    using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromDirect3D11Surface(canvasDevice, context.InputFrame.Direct3DSurface))
    using (CanvasRenderTarget renderTarget = CanvasRenderTarget.CreateFromDirect3D11Surface(canvasDevice, context.OutputFrame.Direct3DSurface))
    using (CanvasDrawingSession ds = renderTarget.CreateDrawingSession())
    {


        var gaussianBlurEffect = new GaussianBlurEffect
        {
            Source = inputBitmap,
            BlurAmount = (float)BlurAmount,
            Optimization = EffectOptimization.Speed
        };

        ds.DrawImage(gaussianBlurEffect);

    }
}

Ajout d’un effet personnalisé à votre application

Pour utiliser votre effet vidéo dans votre application, vous devez ajouter une référence au projet d’effet à votre application.

  1. Dans l’Explorateur de solutions, sous votre projet d’application, cliquez avec le bouton droit sur Références, puis sélectionnez Ajouter une référence.
  2. Développez l’onglet Projets, sélectionnez Solution, puis cochez la case du nom de votre projet d’effet. Dans cet exemple, le nom est VideoEffectComponent.
  3. Cliquez sur OK.

Ajouter un effet personnalisé à un flux vidéo de caméra

Vous pouvez configurer un flux d’aperçu simple à partir de la caméra en suivant les étapes décrites dans l’article Accès à l’aperçu simple de l’appareil photo. En suivant ces étapes, vous obtiendrez un objet MediaCapture initialisé qui est utilisé pour accéder au flux vidéo de l’appareil photo.

Pour ajouter votre effet vidéo personnalisé à un flux de caméra, créez d’abord un objet VideoEffectDefinition, en transmettant l’espace de noms et le nom de classe de votre effet. Appelez ensuite la méthode AddVideoEffect de l’objet MediaCapture pour ajouter l’effet au flux spécifié. Cet exemple utilise la valeur MediaStreamType.VideoPreview pour indiquer que l’effet doit être ajouté au flux d’aperçu. Si votre application prend en charge la capture vidéo, vous pouvez également utiliser MediaStreamType.VideoRecord pour ajouter l’effet au flux de capture. AddVideoEffect renvoie un objet IMediaExtension représentant votre effet personnalisé. Vous pouvez utiliser la méthode SetProperties pour définir la configuration de votre effet.

Une fois l’effet ajouté, StartPreviewAsync est appelé pour démarrer le flux d’aperçu.

var videoEffectDefinition = new VideoEffectDefinition("VideoEffectComponent.ExampleVideoEffect");

IMediaExtension videoEffect =
   await mediaCapture.AddVideoEffectAsync(videoEffectDefinition, MediaStreamType.VideoPreview);

videoEffect.SetProperties(new PropertySet() { { "FadeValue", .25 } });

await mediaCapture.StartPreviewAsync();

Ajouter un effet personnalisé à un clip dans une composition multimédia

Pour obtenir des instructions générales sur la création des compositions multimédias à partir de clips vidéo, voir Compositions multimédias et modification. L’extrait de code suivant illustre la création d’une composition multimédia simple qui utilise un effet vidéo personnalisé. Un objet MediaClip est créé en appelant CreateFromFileAsync, en transmettant un fichier vidéo sélectionné par l’utilisateur avec un FileOpenPicker et le clip est ajouté à un nouveau MediaComposition. Ensuite, un objet VideoEffectDefinition est créé, en transmettant l’espace de noms et le nom de classe de votre effet au constructeur. Enfin, la définition de l’effet est ajoutée à la collection VideoEffectDefinitions de l’objet MediaClip.

MediaComposition composition = new MediaComposition();
var clip = await MediaClip.CreateFromFileAsync(pickedFile);
composition.Clips.Add(clip);

var videoEffectDefinition = new VideoEffectDefinition("VideoEffectComponent.ExampleVideoEffect", new PropertySet() { { "FadeValue", .5 } });

clip.VideoEffectDefinitions.Add(videoEffectDefinition);