Partager via


Effets vidéo personnalisés

Cet article explique comment créer un composant Windows Runtime qui implémente l’interface IBasicVideoEffect pour créer des effets personnalisés pour les flux vidéo. Les effets personnalisés peuvent être utilisés avec plusieurs API Windows Runtime différentes, notamment MediaCapture, qui permet d’accéder à la caméra d’un appareil et à MediaComposition, ce 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. Au lieu de cela, 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, votre solution étant ouverte, accédez au menu Fichier et sélectionnez Ajouter> un nouveau projet.
  2. Sélectionnez le type de projet Composant d’exécution Windows (Windows universel).
  3. Pour cet exemple, nommez le projet VideoEffectComponent. Ce nom sera référencé dans le code par la suite.
  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 de la souris sur l’icône de Class1.cs et sélectionnez Renommer.
  6. Renommez le fichier en ExampleVideoEffect.cs. Visual Studio affiche une invite vous demandant si vous souhaitez mettre à jour toutes les références avec le nouveau nom. Sélectionnez 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 noms suivants dans votre fichier de classe d’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émenter 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 guide tout au long d’une implémentation simple de cette interface qui utilise le traitement logiciel.

Close, méthode

Le système appelle la méthode Close sur votre classe lorsque l’effet doit s’arrêter. Vous devez utiliser cette méthode pour vous débarrasser de toutes les ressources que vous avez créées. L’argument de la méthode est un motif 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é. Un scénario typique est celui où votre effet stocke les trames traitées précédemment pour les utiliser dans le traitement de la trame actuelle. Lorsque cette méthode est appelée, vous devez vous débarrasser de l’ensemble des trames précédentes que vous avez enregistrées. Cette méthode peut être utilisée pour réinitialiser n’importe quel état lié aux images précédentes, et non seulement les images vidéo accumulées.

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

IsReadOnly, propriété

La propriété IsReadOnly indique au système si votre effet écrit dans la sortie de l’effet. Si votre application ne modifie pas les images vidéo (par exemple, un effet qui effectue uniquement l’analyse des images vidéo), vous devez définir cette propriété sur true, ce qui entraîne la copie efficace de l’entrée d’image dans la sortie de l’image.

Conseil

Lorsque la propriété IsReadOnly a la valeur true, le système copie le frame d’entrée dans le cadre de sortie avant l’appel de ProcessFrame. La définition de la propriété IsReadOnly sur true ne vous empêche pas d’écrire dans 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 informer des 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 illustré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 les propriétés d’encodage prises en charge par votre effet. Notez que si le consommateur de votre effet ne peut pas encoder la vidéo à l’aide des 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>();
    }
}

Remarque

Si vous retournez une liste vide d’objets VideoEncodingProperties de SupportedEncodingProperties, le système est défini par défaut sur l’encodage ARGB32.

 

Propriété SupportedMemoryTypes

Le système vérifie la propriété SupportedMemoryTypes pour déterminer si votre effet accède aux images vidéo en mémoire logicielle ou en mémoire matérielle (GPU). Si vous retournez MediaMemoryTypes.Cpu, votre effet est transmis aux images d’entrée et de sortie qui contiennent des données d’image dans des objets SoftwareBitmap. Si vous retournez MediaMemoryTypes.Gpu, votre effet est transmis aux images d’entrée et de sortie qui contiennent des données d’image dans des objets IDirect3DSurface.

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

Remarque

Si vous spécifiez MediaMemoryTypes.GpuAndCpu, le système utilise la mémoire gpu ou système, selon ce qui est plus efficace pour le pipeline. Lorsque vous utilisez cette valeur, vous devez vérifier dans la méthode ProcessFrame pour déterminer si SoftwareBitmap ou IDirect3DSurface passé dans la méthode contient des données, puis traiter l’image en conséquence.

 

Propriété TimeIndependent

La propriété TimeIndependent indique au système si votre effet ne nécessite pas de minutage uniforme. Lorsque cette propriété est définie sur « true », le système peut utiliser des optimisations qui améliorent les performances 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 de l’effet. Les propriétés sont transmises sous la forme d’une carte IPropertySet contenant les noms et les valeurs des propriétés.

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

Cet exemple simple permet deimer les pixels dans chaque image 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 appelante. Si aucune valeur n’a été définie, une valeur par défaut de 0,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

La méthode ProcessFrame est l’endroit où votre effet modifie les données d’image de la vidéo. La méthode est appelée une fois par image et est passée à un objet ProcessVideoFrameContext. Cet objet contient un objet VideoFrame d’entrée qui contient l’image entrante à traiter et un objet VideoFrame de sortie dans lequel vous écrivez des données d’image qui seront transmises au reste du pipeline vidéo. Chacun de ces objets VideoFrame a une propriété SoftwareBitmap et une propriété Direct3DSurface, mais ceux-ci peuvent être utilisés sont déterminés par la valeur retournée par 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 d’objets SoftwareBitmap , consultez Imaging. Un exemple d’implémentation ProcessFrame à l’aide du traitement matériel est présenté plus loin dans cet article.

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

using System.Runtime.InteropServices;

Ajoutez le code suivant à l’intérieur de l’espace de noms pour que votre effet importe l’interface pour 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);
}

Remarque

Comme cette technique accède à une mémoire tampon d’image native non gérée, vous devrez configurer votre projet pour autoriser le code non sécurisé.

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

 

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 logicielles d’entrée et de sortie. Notez que la trame de sortie est ouverte en écriture et la trame d’entrée en lecture. Ensuite, une référence IMemoryBufferReference est obtenue pour chaque tampon en appelant CreateReference. Ensuite, le tampon de données réel est obtenu en coulant les objets IMemoryBufferReference en tant qu’interface interop COM définie ci-dessus, IMemoryByteAccess, puis en appelant GetBuffer.

Maintenant que les tampons de données ont été obtenus, vous pouvez lire dans le tampon d’entrée et écrire dans le tampon de sortie. La disposition de la mémoire tampon est obtenue en appelant GetPlaneDescription, qui fournit des informations sur la largeur, la progression 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 de format de mémoire tampon sont utilisées pour rechercher l’index dans la mémoire tampon pour chaque pixel. La valeur de pixel de la mémoire tampon source est copiée dans la mémoire tampon cible, avec les valeurs de couleur multipliées par la propriété FadeValue définie pour cet effet afin de les faire disparaître par la quantité spécifiée.

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, comme décrit ci-dessus. Cette section présente les quelques 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, consultez 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 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 Parcourir .
  3. Dans la zone de recherche, entrez Win2D.
  4. Sélectionnez Win2D.uwp, puis sélectionnez Installer dans le volet droit.
  5. La boîte de dialogue Révision des modifications affiche le package à installer. Cliquez sur OK.
  6. Acceptez la licence de package.

Outre les espaces de noms inclus dans la configuration du projet de base, vous devez inclure les espaces de noms suivants fournis par Win2D.

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

Étant donné que cet effet utilise la mémoire GPU pour fonctionner 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 d’encodage prises en charge par votre effet avec la propriété SupportedEncodingProperties. Lorsque vous utilisez Win2D, vous devez utiliser l’encodage 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 objet CanvasDevice Win2D à partir de l’IDirect3DDevice passé dans la méthode.

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

L’implémentation setProperties est identique à l’exemple précédent de traitement logiciel. 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 des API Win2D, un CanvasBitmap est créé à partir de la propriété Direct3DSurface de l’image d’entrée. Un CanvasRenderTarget est créé à partir de direct3DSurface du frame de sortie et une canvasDrawingSession est créée à partir de cette cible de rendu. Un nouveau Win2D GaussianBlurEffect est initialisé, à l’aide de la propriété BlurAmount que notre effet expose via SetProperties. Enfin, la méthode CanvasDrawingSession.DrawImage est appelée pour dessiner la bitmap d’entrée vers la cible de rendu à l’aide de l’effet 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);

    }
}

Ajouter votre effet personnalisé à votre application

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

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

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

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

Pour ajouter votre effet vidéo personnalisé à un flux de caméra, commencez par créer un objet VideoEffectDefinition , en passant l’espace de noms et le nom de la classe pour votre effet. Ensuite, appelez la méthode AddVideoEffect de l’objet MediaCapture pour ajouter votre effet au flux spécifié. Cet exemple utilise la valeur MediaStreamType.VideoPreview pour spécifier 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 retourne 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 votre effet personnalisé à un clip dans une MediaComposition

Pour obtenir des conseils généraux sur la création de compositions multimédias à partir de clips vidéo, consultez Compositions multimédias et édition. L’extrait de code suivant montre 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 passant un fichier vidéo sélectionné par l’utilisateur avec un FileOpenPicker, et le clip est ajouté à un nouveau MediaComposition. Ensuite, un nouvel objet VideoEffectDefinition est créé, en passant l’espace de noms et le nom de la classe pour votre effet au constructeur. Enfin, la définition d’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);