Effets audio personnalisés

Cet article décrit comment créer un composant Windows Runtime qui implémente l’interface IBasicAudioEffect afin de créer des effets personnalisés pour les flux audio. Les effets personnalisés peuvent être utilisés avec plusieurs API d’exécution Windows, notamment MediaCapture, qui permet d’accéder à la caméra d’un appareil, MediaComposition, qui vous permet de créer des compositions complexes à partir de clips média, et AudioGraph, qui vous permet d’assembler rapidement un graphique de divers nœuds d’entrée, de sortie et de sous-mixage audio.

Ajouter un effet personnalisé à votre application

Un effet audio personnalisé est défini dans une classe qui met en œuvre l’interface IBasicAudioEffect. 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 audio.

Ajouter un composant Windows Runtime pour votre effet audio

  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 AudioEffectComponent. 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 ExampleAudioEffect.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 ExampleAudioEffect.cs et mettez à jour la définition de la classe pour implémenter l’interface IBasicAudioEffect.
public sealed class ExampleAudioEffect : IBasicAudioEffect

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 System.Runtime.InteropServices;
using Windows.Media;
using Windows.Foundation;

Implémenter l’interface IBasicAudioEffect

Votre effet audio doit implémenter toutes les méthodes et propriétés de l’interface IBasicAudioEffect. Cette section vous présente une implémentation simple de cette interface pour créer un effet d’écho basique.

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 l’audio à l’aide des propriétés que vous spécifiez, le système appellera Close sur votre effet et le supprimera du pipeline audio. Dans cet exemple, des objets AudioEncodingProperties sont créés et ajoutés à la liste renvoyée pour prendre en charge l’encodage 44,1 kHz et 48 kHz, 32-bit float, mono.

public IReadOnlyList<AudioEncodingProperties> SupportedEncodingProperties
{
    get
    {
        var supportedEncodingProperties = new List<AudioEncodingProperties>();
        AudioEncodingProperties encodingProps1 = AudioEncodingProperties.CreatePcm(44100, 1, 32);
        encodingProps1.Subtype = MediaEncodingSubtypes.Float;
        AudioEncodingProperties encodingProps2 = AudioEncodingProperties.CreatePcm(48000, 1, 32);
        encodingProps2.Subtype = MediaEncodingSubtypes.Float;

        supportedEncodingProperties.Add(encodingProps1);
        supportedEncodingProperties.Add(encodingProps2);

        return supportedEncodingProperties;
        
    }
}

Méthode SetEncodingProperties

Le système appelle SetEncodingProperties sur votre effet pour vous faire connaître les propriétés de codage du flux audio sur lequel l’effet fonctionne. Afin de mettre en œuvre un effet d’écho, cet exemple utilise une mémoire tampon pour stocker une seconde de données audio. Cette méthode permet d’initialiser la taille de la mémoire tampon au nombre d’échantillons contenus dans une seconde d’audio, en fonction de la fréquence d’échantillonnage dans laquelle l’audio est encodé. L’effet de retard utilise également un compteur d’entiers pour garder une trace de la position actuelle dans le tampon de retard. Puisque SetEncodingProperties est appelé chaque fois que l’effet est ajouté au pipeline audio, c’est le bon moment pour initialiser cette valeur à 0. Vous pouvez également capturer l’objet AudioEncodingProperties passé dans cette méthode pour l’utiliser ailleurs dans votre effet.

private float[] echoBuffer;
private int currentActiveSampleIndex;
private AudioEncodingProperties currentEncodingProperties;
public void SetEncodingProperties(AudioEncodingProperties encodingProperties)
{
    currentEncodingProperties = encodingProperties;
    echoBuffer = new float[encodingProperties.SampleRate]; // exactly one second delay
    currentActiveSampleIndex = 0;
}

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.

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

Cet exemple simple mélangera l’échantillon audio actuel avec une valeur du tampon de retard en fonction de la valeur de la propriété Mix. 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. Notez que cette propriété est en lecture seule. La valeur de la propriété doit être définie à l’aide de SetProperties.

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

Méthode ProcessFrame

La méthode ProcessFrame permet à votre effet de modifier les données audio du flux. Cette méthode est appelée une fois par trame et reçoit un objet ProcessAudioFrameContext. Cet objet contient un objet AudioFrame d’entrée qui contient la trame entrante à traiter et un objet AudioFrame de sortie dans lequel vous écrivez les données audio qui seront transmises au reste du pipeline audio. Une trame audio est une mémoire tampon d’échantillons audio représentant une courte tranche de données audio.

L’accès au tampon de données d’une AudioFrame nécessite une interopérabilité COM. Vous devez donc inclure l’espace de noms System.Runtime.InteropServices dans votre fichier de classe d’effet, puis ajouter le code suivant dans l’espace de noms de votre effet afin d’importer l’interface permettant d’accéder au tampon audio.

[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 l’explorateur de solutions, cliquez avec le bouton droit de la souris sur le projet AudioEffectComponent 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 à votre effet. Tout d’abord, cette méthode obtient un objet AudioBuffer à partir des trames audio 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. Pour chaque échantillon dans le tampon d’entrée, la valeur est obtenue et multipliée par 1 - Mix pour définir la valeur du signal sec de l’effet. Ensuite, un échantillon est récupéré à partir de la position actuelle dans la mémoire tampon de l’écho et multiplié par Mix pour définir la valeur du signal humide de l’effet. L’échantillon de sortie est réglé sur la somme des valeurs du signal sec et du signal humide. Enfin, chaque échantillon d’entrée est stocké dans la mémoire tampon de l’écho et l’index de l’échantillon actuel est incrémenté.

unsafe public void ProcessFrame(ProcessAudioFrameContext context)
{
    AudioFrame inputFrame = context.InputFrame;
    AudioFrame outputFrame = context.OutputFrame;

    using (AudioBuffer inputBuffer = inputFrame.LockBuffer(AudioBufferAccessMode.Read),
                        outputBuffer = outputFrame.LockBuffer(AudioBufferAccessMode.Write))
    using (IMemoryBufferReference inputReference = inputBuffer.CreateReference(),
                                    outputReference = outputBuffer.CreateReference())
    {
        byte* inputDataInBytes;
        byte* outputDataInBytes;
        uint inputCapacity;
        uint outputCapacity;

        ((IMemoryBufferByteAccess)inputReference).GetBuffer(out inputDataInBytes, out inputCapacity);
        ((IMemoryBufferByteAccess)outputReference).GetBuffer(out outputDataInBytes, out outputCapacity);

        float* inputDataInFloat = (float*)inputDataInBytes;
        float* outputDataInFloat = (float*)outputDataInBytes;

        float inputData;
        float echoData;

        // Process audio data
        int dataInFloatLength = (int)inputBuffer.Length / sizeof(float);

        for (int i = 0; i < dataInFloatLength; i++)
        {
            inputData = inputDataInFloat[i] * (1.0f - this.Mix);
            echoData = echoBuffer[currentActiveSampleIndex] * this.Mix;
            outputDataInFloat[i] = inputData + echoData;
            echoBuffer[currentActiveSampleIndex] = inputDataInFloat[i];
            currentActiveSampleIndex++;

            if (currentActiveSampleIndex == echoBuffer.Length)
            {
                // Wrap around (after one second of samples)
                currentActiveSampleIndex = 0;
            }
        }
    }
}

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
    echoBuffer = null;
}

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 tout état lié aux trames précédentes, et pas seulement les trames audio accumulées.

public void DiscardQueuedFrames()
{
    // Reset contents of the samples buffer
    Array.Clear(echoBuffer, 0, echoBuffer.Length - 1);
    currentActiveSampleIndex = 0;
}

Propriété TimeIndependent

La propriété TimeIndependent TimeIndependent permet au système de savoir si votre effet ne nécessite pas une synchronisation 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; } }

Propriété UseInputFrameForOutput

Définissez la propriété UseInputFrameForOutput sur true pour indiquer au système que votre effet écrira sa sortie dans la mémoire tampon audio de l’InputFrame du ProcessAudioFrameContext passé dans ProcessFrame au lieu d’écrire dans l’OutputFrame.

public bool UseInputFrameForOutput { get { return false; } }

Ajouter votre effet personnalisé à votre application

Pour utiliser votre effet audio à 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. Dans cet exemple, le nom est AudioEffectComponent.
  3. Cliquez sur OK.

Si votre classe d’effet audio est déclarée dans un espace de noms différent, veillez à inclure cet espace de noms dans votre fichier de code.

using AudioEffectComponent;

Ajoutez votre effet personnalisé à un nœud AudioGraph

Pour des informations générales sur l’utilisation des graphes audio, voir Graphes audio. L’extrait de code suivant vous montre comment ajouter l’exemple d’effet d’écho présenté dans cet article à un nœud de graphe audio. Tout d’abord, un PropertySet est créé et une valeur est attribuée à la propriété Mix, définie par l’effet. Ensuite, le constructeur AudioEffectDefinition est appelé, en transmettant le nom de classe complet du type d’effet personnalisé et l’ensemble de propriétés. Enfin, la définition de l’effet est ajoutée à la propriété EffectDefinitions d’un FileInputNode existant, ce qui entraîne le traitement de l’audio émis par l’effet personnalisé.

// Create a property set and add a property/value pair
PropertySet echoProperties = new PropertySet();
echoProperties.Add("Mix", 0.5f);

// Instantiate the custom effect defined in the 'AudioEffectComponent' project
AudioEffectDefinition echoEffectDefinition = new AudioEffectDefinition(typeof(ExampleAudioEffect).FullName, echoProperties);
fileInputNode.EffectDefinitions.Add(echoEffectDefinition);

Une fois ajouté à un nœud, l’effet personnalisé peut être désactivé en appelant DisableEffectsByDefinition et en transmettant l’objet AudioEffectDefinition. Pour plus d’informations sur l’utilisation des graphiques audio dans votre application, voir AudioGraph.

Ajouter votre effet personnalisé à un clip dans une MediaComposition

L’extrait de code suivant illustre l’ajout d’un effet audio personnalisé à un clip vidéo et à une piste audio d’arrière-plan dans une composition multimédia. Pour obtenir des conseils généraux sur la création de compositions multimédias à partir de clips vidéo et l’ajout de pistes audio d’arrière-plan, voir Compositions multimédias et édition.

// Create a property set and add a property/value pair
PropertySet echoProperties = new PropertySet();
echoProperties.Add("Mix", 0.5f);

// Instantiate the custom effect defined in the 'AudioEffectComponent' project
AudioEffectDefinition echoEffectDefinition = new AudioEffectDefinition(typeof(ExampleAudioEffect).FullName, echoProperties);

// Add custom audio effect to the current clip in the timeline
var currentClip = composition.Clips.FirstOrDefault(
    mc => mc.StartTimeInComposition <= mediaPlayerElement.MediaPlayer.PlaybackSession.Position &&
    mc.EndTimeInComposition >= mediaPlayerElement.MediaPlayer.PlaybackSession.Position);
currentClip.AudioEffectDefinitions.Add(echoEffectDefinition);

// Add custom audio effect to the first background audio track
if (composition.BackgroundAudioTracks.Count > 0)
{
    composition.BackgroundAudioTracks[0].AudioEffectDefinitions.Add(echoEffectDefinition);
}