Graphiques audio

Cet article montre comment utiliser les API de l’espace de noms Windows.Media.Audio pour créer des graphiques audio pour des scénarios de routage, de mixage et de traitement audio.

Un graphique audio est un ensemble de nœuds audio interconnectés à travers lesquels les données audio circulent.

  • Les nœuds d’entrée audio fournissent des données audio au graphique à partir des périphériques d’entrée audio, des fichiers audio ou du code personnalisé. lat

  • Les nœuds de sortie audio sont la destination pour l’audio traité par le graphique. L’audio peut être routé hors du graphique vers des périphériques de sortie audio, des fichiers audio ou du code personnalisé.

  • Les nœuds de sous-mixage prennent l’audio de un ou plusieurs nœuds et les combinent en une sortie unique qui peut être routée vers d’autres nœuds dans le graphique.

Une fois que tous les nœuds ont été créés et que les connexions entre eux sont configurées, il suffit de démarrer le graphique audio et les données audio circulent des nœuds d’entrée, à travers les éventuels nœuds de sous-mixage, vers les nœuds de sortie. Ce modèle rend des scénarios tels que l’enregistrement à partir du microphone d’un appareil vers un fichier audio, la lecture audio à partir d’un fichier vers le haut-parleur d’un appareil, ou le mélange audio de sources multiples rapides et faciles à mettre en œuvre.

Des scénarios supplémentaires sont activés avec l’ajout d’effets audio au graphique audio. Chaque nœud dans un graphique audio peut être peuplé de zéro ou plusieurs effets audio qui effectuent un traitement audio sur l’audio passant par le nœud. Il existe plusieurs effets intégrés tels que l’écho, l’égaliseur, la limitation et la réverbération qui peuvent être attachés à un nœud audio avec juste quelques lignes de code. Vous pouvez également créer vos propres effets audio personnalisés qui fonctionnent exactement de la même manière que les effets intégrés.

Remarque

L’exemple AudioGraph UWP implémente le code discuté dans cette vue d’ensemble. Vous pouvez télécharger l’exemple pour voir le code dans son contexte ou l’utiliser comme point de départ pour votre propre application.

Choisir entre Windows Runtime AudioGraph ou XAudio2

Les API de graphique audio Windows Runtime offrent des fonctionnalités qui peuvent également être implémentées en utilisant les API XAudio2 basées sur COM. Voici les caractéristiques du framework de graphique audio Windows Runtime qui diffèrent de XAudio2 :

Les API de graphique audio Windows Runtime :

  • Sont considérablement plus faciles à utiliser que XAudio2.
  • Peuvent être utilisées à partir de C# en plus d’être prises en charge pour C++
  • Peuvent utiliser des fichiers audio, y compris des formats de fichier compressés, directement. XAudio2 opère uniquement sur des tampons audio et ne fournit aucune fonctionnalité d’E/S de fichier.
  • Peuvent utiliser le pipeline audio à faible latence dans Windows 10.
  • Prennent en charge le changement automatique de point de terminaison lorsque les paramètres de point de terminaison par défaut sont utilisés. Par exemple, si l’utilisateur passe des haut-parleurs d’un périphérique à un casque, l’audio est automatiquement redirigé vers la nouvelle entrée.

Classe AudioGraph

La classe AudioGraph est le parent de tous les nœuds qui composent le graphique. Utilisez cet objet pour créer des instances de tous les types de nœuds audio. Créez une instance de la classe AudioGraph en initialisant un objet AudioGraphSettings contenant les paramètres de configuration du graphique, puis en appelant AudioGraph.CreateAsync. Le résultat CreateAudioGraphResult donne accès au graphique audio créé ou fournit une valeur d’erreur si la création du graphique audio échoue.

AudioGraph audioGraph;
private async Task InitAudioGraph()
{

    AudioGraphSettings settings = new AudioGraphSettings(Windows.Media.Render.AudioRenderCategory.Media);

    CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
    if (result.Status != AudioGraphCreationStatus.Success)
    {
        ShowErrorMessage("AudioGraph creation error: " + result.Status.ToString());
    }

    audioGraph = result.Graph;

}
  • Tous les types de nœuds audio sont créés en utilisant les méthodes Create* de la classe AudioGraph.

  • La méthode AudioGraph.Start lance le traitement des données audio dans le graphique. La méthode AudioGraph.Stop arrête le traitement audio. Chaque nœud dans le graphique peut être démarré et arrêté indépendamment pendant que le graphique est en cours d’exécution, mais aucun nœud n’est actif lorsque le graphique est arrêté. La méthode ResetAllNodes provoque la suppression de toutes les données actuellement dans les tampons audio de tous les nœuds du graphique.

  • L’événement QuantumStarted se produit lorsque le graphique commence le traitement d’un nouveau quantum de données audio. L’événement QuantumProcessed se produit lorsque le traitement d’un quantum est terminé.

  • La seule propriété AudioGraphSettings requise est AudioRenderCategory. Spécifier cette valeur permet au système d’optimiser le pipeline audio pour le catégorie spécifiée.

  • La taille du quantum du graphique audio détermine le nombre d’échantillons traités à la fois. Par défaut, la taille quantique est de 10 ms en fonction du taux d’échantillonnage par défaut. Si vous spécifiez une taille de quantum personnalisée en définissant la propriété DesiredSamplesPerQuantum, vous devez également définir la propriété QuantumSizeSelectionMode sur ClosestToDesired, sinon la valeur fournie est ignorée. Si cette valeur est utilisée, le système choisira une taille de quantum aussi proche que possible de celle que vous spécifiez. Pour déterminer la taille de quantum réelle, vérifiez la propriété SamplesPerQuantum de AudioGraph après sa création.

  • Si vous prévoyez d’utiliser le graphique audio uniquement avec des fichiers et que vous ne prévoyez pas de sortie vers un périphérique audio, il est recommandé de n’utiliser que la taille de quantum par défaut en ne définissant pas la propriété DesiredSamplesPerQuantum.

  • La propriété DesiredRenderDeviceAudioProcessing détermine le niveau de traitement effectué par le périphérique de rendu principal sur la sortie du graphique audio. Le paramètre Default permet au système d’utiliser le traitement audio par défaut pour la catégorie de rendu audio spécifiée. Ce traitement peut améliorer considérablement le son de l’audio sur certains appareils, en particulier les appareils mobiles avec de petits haut-parleurs. Le paramètre Raw peut améliorer les performances en réduisant au minimum le traitement du signal, mais peut entraîner une qualité audio inférieure sur certains appareils.

  • Si QuantumSizeSelectionMode est défini sur LowestLatency, le graphique audio utilisera automatiquement Raw pour DesiredRenderDeviceAudioProcessing.

  • À partir de Windows 10, version 1803, vous pouvez définir la propriété AudioGraphSettings.MaxPlaybackSpeedFactor pour définir une valeur maximale utilisée pour les propriétés AudioFileInputNode.PlaybackSpeedFactor, AudioFrameInputNode.PlaybackSpeedFactor et MediaSourceInputNode.PlaybackSpeedFactor. Lorsqu’un graphique audio prend en charge un facteur de vitesse de lecture supérieur à 1, le système doit allouer de la mémoire supplémentaire pour maintenir un tampon audio suffisant. Pour cette raison, définir MaxPlaybackSpeedFactor sur la valeur la plus basse requise par votre application réduira la consommation de mémoire de votre application. Si votre application ne lit que du contenu à la vitesse normale, il est recommandé de définir MaxPlaybackSpeedFactor sur 1.

  • La propriété EncodingProperties détermine le format audio utilisé par le graphique. Seuls les formats de 32 bits en virgule flottante sont pris en charge.

  • PrimaryRenderDevice définit le périphérique de rendu principal pour le graphique audio. Si vous ne définissez pas cela, le périphérique système par défaut est utilisé. Le périphérique de rendu principal est utilisé pour calculer les tailles de quantum pour les autres nœuds du graphique. Si aucun périphérique de rendu audio n’est présent sur le système, la création du graphique audio échouera.

Vous pouvez laisser le graphique audio utiliser le périphérique de rendu audio par défaut ou utiliser la classe Windows.Devices.Enumeration.DeviceInformation pour obtenir une liste des périphériques de rendu audio disponibles sur le système en appelant FindAllAsync et en passant le sélecteur de périphérique de rendu audio retourné par Windows.Media.Devices.MediaDevice.GetAudioRenderSelector. Vous pouvez choisir un des objets DeviceInformation de manière programmatique ou afficher une interface utilisateur pour permettre à l’utilisateur de sélectionner un périphérique, puis l’utiliser pour définir la propriété PrimaryRenderDevice.

Windows.Devices.Enumeration.DeviceInformationCollection devices =
 await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(Windows.Media.Devices.MediaDevice.GetAudioRenderSelector());

// Show UI to allow the user to select a device
Windows.Devices.Enumeration.DeviceInformation selectedDevice = ShowMyDeviceSelectionUI(devices);


settings.PrimaryRenderDevice = selectedDevice;

Nœud d’entrée de périphérique

Un nœud d’entrée de périphérique alimente l’audio dans le graphique à partir d’un périphérique de capture audio connecté au système, tel qu’un microphone. Créez un objet DeviceInputNode qui utilise le périphérique de capture audio par défaut du système en appelant CreateDeviceInputNodeAsync. Fournissez une AudioRenderCategory pour permettre au système d’optimiser le pipeline audio pour la catégorie spécifiée.

AudioDeviceInputNode deviceInputNode;
private async Task CreateDeviceInputNode()
{
    // Create a device output node
    CreateAudioDeviceInputNodeResult result = await audioGraph.CreateDeviceInputNodeAsync(Windows.Media.Capture.MediaCategory.Media);

    if (result.Status != AudioDeviceNodeCreationStatus.Success)
    {
        // Cannot create device output node
        ShowErrorMessage(result.Status.ToString());
        return;
    }

    deviceInputNode = result.DeviceInputNode;
}

Si vous souhaitez spécifier un périphérique de capture audio spécifique pour le nœud d’entrée de périphérique, vous pouvez utiliser la classe Windows.Devices.Enumeration.DeviceInformation pour obtenir une liste des périphériques de capture audio disponibles sur le système en appelant FindAllAsync et en passant le sélecteur de périphérique de capture audio retourné par Windows.Media.Devices.MediaDevice.GetAudioCaptureSelector. Vous pouvez choisir un des objets DeviceInformation retournés de manière programmée ou afficher une interface utilisateur pour permettre à l’utilisateur de sélectionner un périphérique, puis le passer à CreateDeviceInputNodeAsync.

Windows.Devices.Enumeration.DeviceInformationCollection devices =
 await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(Windows.Media.Devices.MediaDevice.GetAudioCaptureSelector());

// Show UI to allow the user to select a device
Windows.Devices.Enumeration.DeviceInformation selectedDevice = ShowMyDeviceSelectionUI(devices);

CreateAudioDeviceInputNodeResult result =
    await audioGraph.CreateDeviceInputNodeAsync(Windows.Media.Capture.MediaCategory.Media, audioGraph.EncodingProperties, selectedDevice);

Nœud de sortie de périphérique

Un nœud de sortie de périphérique envoie l’audio du graphique vers un périphérique de rendu audio, tel que des haut-parleurs ou un casque. Créez un DeviceOutputNode en appelant CreateDeviceOutputNodeAsync. Le nœud de sortie utilise le PrimaryRenderDevice du graphique audio.

AudioDeviceOutputNode deviceOutputNode;
private async Task CreateDeviceOutputNode()
{
    // Create a device output node
    CreateAudioDeviceOutputNodeResult result = await audioGraph.CreateDeviceOutputNodeAsync();

    if (result.Status != AudioDeviceNodeCreationStatus.Success)
    {
        // Cannot create device output node
        ShowErrorMessage(result.Status.ToString());
        return;
    }

    deviceOutputNode = result.DeviceOutputNode;
}

Nœud d’entrée de fichier

Un nœud d’entrée de fichier vous permet de fournir des données à partir d’un fichier audio dans le graphique. Créez un AudioFileInputNode en appelant CreateFileInputNodeAsync.

AudioFileInputNode fileInputNode;
private async Task CreateFileInputNode()
{
    if (audioGraph == null)
        return;

    FileOpenPicker filePicker = new FileOpenPicker();
    filePicker.SuggestedStartLocation = PickerLocationId.MusicLibrary;
    filePicker.FileTypeFilter.Add(".mp3");
    filePicker.FileTypeFilter.Add(".wav");
    filePicker.FileTypeFilter.Add(".wma");
    filePicker.FileTypeFilter.Add(".m4a");
    filePicker.ViewMode = PickerViewMode.Thumbnail;
    StorageFile file = await filePicker.PickSingleFileAsync();

    // File can be null if cancel is hit in the file picker
    if (file == null)
    {
        return;
    }
    CreateAudioFileInputNodeResult result = await audioGraph.CreateFileInputNodeAsync(file);

    if (result.Status != AudioFileNodeCreationStatus.Success)
    {
        ShowErrorMessage(result.Status.ToString());
    }

    fileInputNode = result.FileInputNode;
}
  • Les nœuds d’entrée de fichier prennent en charge les formats de fichier suivants : mp3, wav, wma, m4a.
  • Définissez la propriété StartTime pour spécifier le décalage temporel dans le fichier où la lecture doit commencer. Si cette propriété est null, le début du fichier est utilisé. Définissez la propriété EndTime pour spécifier le décalage temporel dans le fichier où la lecture doit se terminer. Si cette propriété est null, la fin du fichier est utilisée. La valeur de l’heure de début doit être inférieure à la valeur de l’heure de fin, et la valeur de l’heure de fin doit être inférieure ou égale à la durée du fichier audio, qui peut être déterminée en vérifiant la valeur de la propriété Duration.
  • Déplacez-vous à une position dans le fichier audio en appelant Seek et en spécifiant le décalage temporel dans le fichier vers lequel la position de lecture doit être déplacée. La valeur spécifiée doit être dans la plage de StartTime et EndTime. Obtenez la position de lecture actuelle du nœud avec la propriété en lecture seule Position.
  • Activez la boucle du fichier audio en définissant la propriété LoopCount. Lorsque cette valeur n’est pas null, cette valeur indique le nombre de fois que le fichier sera lu après la lecture initiale. Ainsi, en définissant par exemple LoopCount sur 1, le fichier sera lu 2 fois au total, et en le définissant sur 5, le fichier sera lu 6 fois au total. Définir LoopCount sur null fait en sorte que le fichier soit lu en boucle indéfiniment. Pour arrêter la boucle, définissez la valeur sur 0.
  • Ajustez la vitesse à laquelle le fichier audio est lu en définissant le PlaybackSpeedFactor. Une valeur de 1 indique la vitesse originale du fichier, .5 est la moitié de la vitesse, et 2 est le double de la vitesse.

Nœud d’entrée de MediaSource

La classe MediaSource fournit un moyen commun de référencer des médias à partir de différentes sources et expose un modèle commun pour accéder aux données des médias quel que soit le format de média sous-jacent, qui peut être un fichier sur disque, un flux, ou une source réseau de diffusion en continu adaptative. Un nœud MediaSourceAudioInputNode vous permet de diriger les données audio à partir d’une MediaSource dans le graphique audio. Créez un MediaSourceAudioInputNode en appelant CreateMediaSourceAudioInputNodeAsync, en passant un objet MediaSource représentant le contenu que vous souhaitez lire. Un CreateMediaSourceAudioInputNodeResult est retourné, que vous pouvez utiliser pour déterminer le statut de l’opération en vérifiant la propriété Status. Si le statut est Succès, vous pouvez obtenir le MediaSourceAudioInputNode créé en accédant à la propriété Nœud. L’exemple suivant montre la création d’un nœud à partir d’un objet AdaptiveMediaSource représentant le streaming de contenu sur le réseau. Pour plus d’informations sur le travail avec MediaSource, veuillez consulter la rubrique Éléments multimédias, listes de lecture et pistes. Pour plus d’informations sur le streaming de contenu multimédia sur Internet, veuillez consulter la rubrique Streaming adaptatif.

MediaSourceAudioInputNode mediaSourceInputNode;
private async Task CreateMediaSourceInputNode(System.Uri contentUri)
{
    if (audioGraph == null)
        return;

    var adaptiveMediaSourceResult = await AdaptiveMediaSource.CreateFromUriAsync(contentUri);
    if(adaptiveMediaSourceResult.Status != AdaptiveMediaSourceCreationStatus.Success)
    {
        Debug.WriteLine("Failed to create AdaptiveMediaSource");
        return;
    }

    var mediaSource = MediaSource.CreateFromAdaptiveMediaSource(adaptiveMediaSourceResult.MediaSource);
    CreateMediaSourceAudioInputNodeResult mediaSourceAudioInputNodeResult =
        await audioGraph.CreateMediaSourceAudioInputNodeAsync(mediaSource);

    if (mediaSourceAudioInputNodeResult.Status != MediaSourceAudioInputNodeCreationStatus.Success)
    {
        switch (mediaSourceAudioInputNodeResult.Status)
        {
            case MediaSourceAudioInputNodeCreationStatus.FormatNotSupported:
                Debug.WriteLine("The MediaSource uses an unsupported format");
                break;
            case MediaSourceAudioInputNodeCreationStatus.NetworkError:
                Debug.WriteLine("The MediaSource requires a network connection and a network-related error occurred");
                break;
            case MediaSourceAudioInputNodeCreationStatus.UnknownFailure:
            default:
                Debug.WriteLine("An unknown error occurred while opening the MediaSource");
                break;
        }
        return;
    }

    mediaSourceInputNode = mediaSourceAudioInputNodeResult.Node;
}

Pour recevoir une notification lorsque la lecture atteint la fin du contenu de MediaSource, enregistrez un gestionnaire pour l’événement MediaSourceCompleted.

mediaSourceInputNode.MediaSourceCompleted += MediaSourceInputNode_MediaSourceCompleted;
private void MediaSourceInputNode_MediaSourceCompleted(MediaSourceAudioInputNode sender, object args)
{
    audioGraph.Stop();
}

Alors que la lecture d’un fichier depuis un disque est susceptible de toujours se terminer avec succès, les médias diffusés à partir d’une source réseau peuvent échouer pendant la lecture en raison d’un changement de connexion réseau ou d’autres problèmes hors du contrôle du graphique audio. Si un MediaSource devient injouable pendant la lecture, le graphique audio lèvera l’événement UnrecoverableErrorOccurred. Vous pouvez utiliser le gestionnaire pour cet événement pour arrêter et supprimer le graphique audio, puis réinitialiser votre graphique.

audioGraph.UnrecoverableErrorOccurred += AudioGraph_UnrecoverableErrorOccurred;
private void AudioGraph_UnrecoverableErrorOccurred(AudioGraph sender, AudioGraphUnrecoverableErrorOccurredEventArgs args)
{
    if (sender == audioGraph && args.Error != AudioGraphUnrecoverableError.None)
    {
        Debug.WriteLine("The audio graph encountered and unrecoverable error.");
        audioGraph.Stop();
        audioGraph.Dispose();
        InitAudioGraph();
    }
}

Nœud de sortie de fichier

Un nœud de sortie de fichier vous permet de diriger les données audio du graphique vers un fichier audio. Créez un AudioFileOutputNode en appelant CreateFileOutputNodeAsync.

AudioFileOutputNode fileOutputNode;
private async Task CreateFileOutputNode()
{
    FileSavePicker saveFilePicker = new FileSavePicker();
    saveFilePicker.FileTypeChoices.Add("Pulse Code Modulation", new List<string>() { ".wav" });
    saveFilePicker.FileTypeChoices.Add("Windows Media Audio", new List<string>() { ".wma" });
    saveFilePicker.FileTypeChoices.Add("MPEG Audio Layer-3", new List<string>() { ".mp3" });
    saveFilePicker.SuggestedFileName = "New Audio Track";
    StorageFile file = await saveFilePicker.PickSaveFileAsync();

    // File can be null if cancel is hit in the file picker
    if (file == null)
    {
        return;
    }

    Windows.Media.MediaProperties.MediaEncodingProfile mediaEncodingProfile;
    switch (file.FileType.ToString().ToLowerInvariant())
    {
        case ".wma":
            mediaEncodingProfile = MediaEncodingProfile.CreateWma(AudioEncodingQuality.High);
            break;
        case ".mp3":
            mediaEncodingProfile = MediaEncodingProfile.CreateMp3(AudioEncodingQuality.High);
            break;
        case ".wav":
            mediaEncodingProfile = MediaEncodingProfile.CreateWav(AudioEncodingQuality.High);
            break;
        default:
            throw new ArgumentException();
    }


    // Operate node at the graph format, but save file at the specified format
    CreateAudioFileOutputNodeResult result = await audioGraph.CreateFileOutputNodeAsync(file, mediaEncodingProfile);

    if (result.Status != AudioFileNodeCreationStatus.Success)
    {
        // FileOutputNode creation failed
        ShowErrorMessage(result.Status.ToString());
        return;
    }

    fileOutputNode = result.FileOutputNode;
}

Nœud d’entrée de trame audio

Un nœud d’entrée de trame audio vous permet de pousser des données audio que vous générez dans votre propre code dans le graphique audio. Cela permet des scénarios comme la création d’un synthétiseur logiciel personnalisé. Créez un AudioFrameInputNode en appelant CreateFrameInputNode.

AudioFrameInputNode frameInputNode;
private void CreateFrameInputNode()
{
    // Create the FrameInputNode at the same format as the graph, except explicitly set mono.
    AudioEncodingProperties nodeEncodingProperties = audioGraph.EncodingProperties;
    nodeEncodingProperties.ChannelCount = 1;
    frameInputNode = audioGraph.CreateFrameInputNode(nodeEncodingProperties);

    // Initialize the Frame Input Node in the stopped state
    frameInputNode.Stop();

    // Hook up an event handler so we can start generating samples when needed
    // This event is triggered when the node is required to provide data
    frameInputNode.QuantumStarted += node_QuantumStarted;
}

L’événement FrameInputNode.QuantumStarted est déclenché lorsque le graphique audio est prêt à commencer le traitement du prochain quantum de données audio. Vous fournissez vos données audio générées personnalisées depuis le gestionnaire de cet événement.

private void node_QuantumStarted(AudioFrameInputNode sender, FrameInputNodeQuantumStartedEventArgs args)
{
    // GenerateAudioData can provide PCM audio data by directly synthesizing it or reading from a file.
    // Need to know how many samples are required. In this case, the node is running at the same rate as the rest of the graph
    // For minimum latency, only provide the required amount of samples. Extra samples will introduce additional latency.
    uint numSamplesNeeded = (uint)args.RequiredSamples;

    if (numSamplesNeeded != 0)
    {
        AudioFrame audioData = GenerateAudioData(numSamplesNeeded);
        frameInputNode.AddFrame(audioData);
    }
}
  • L’objet FrameInputNodeQuantumStartedEventArgs passé dans le gestionnaire d’événement QuantumStarted expose la propriété RequiredSamples qui indique combien d’échantillons le graphique audio doit remplir le quantum à traiter.
  • Appelez AudioFrameInputNode.AddFrame pour passer un objet AudioFrame rempli de données audio dans le graphique.
  • Un nouvel ensemble d’API pour utiliser MediaFrameReader avec des données audio a été introduit dans Windows 10, version 1803. Ces API vous permettent d’obtenir des objets AudioFrame à partir d’une source de trames multimédia, qui peuvent être passés dans un FrameInputNode en utilisant la méthode AddFrame. Pour plus d’informations, veuillez consulter la rubrique Traiter les trames audio avec MediaFrameReader.
  • Une implémentation d’exemple de la méthode d’aide GenerateAudioData est présentée ci-dessous.

Pour remplir un AudioFrame avec des données audio, vous devez accéder au tampon de mémoire sous-jacent du frame audio. Pour cela, vous devez initialiser l’interface COM IMemoryBufferByteAccess en ajoutant le code suivant dans votre espace de noms.

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

Le code suivant montre une implémentation exemple d’une méthode d’aide GenerateAudioData qui crée un AudioFrame et le remplit avec des données audio.

private double audioWaveTheta = 0;

unsafe private AudioFrame GenerateAudioData(uint samples)
{
    // Buffer size is (number of samples) * (size of each sample)
    // We choose to generate single channel (mono) audio. For multi-channel, multiply by number of channels
    uint bufferSize = samples * sizeof(float);
    AudioFrame frame = new Windows.Media.AudioFrame(bufferSize);

    using (AudioBuffer buffer = frame.LockBuffer(AudioBufferAccessMode.Write))
    using (IMemoryBufferReference reference = buffer.CreateReference())
    {
        byte* dataInBytes;
        uint capacityInBytes;
        float* dataInFloat;

        // Get the buffer from the AudioFrame
        ((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacityInBytes);

        // Cast to float since the data we are generating is float
        dataInFloat = (float*)dataInBytes;

        float freq = 1000; // choosing to generate frequency of 1kHz
        float amplitude = 0.3f;
        int sampleRate = (int)audioGraph.EncodingProperties.SampleRate;
        double sampleIncrement = (freq * (Math.PI * 2)) / sampleRate;

        // Generate a 1kHz sine wave and populate the values in the memory buffer
        for (int i = 0; i < samples; i++)
        {
            double sinValue = amplitude * Math.Sin(audioWaveTheta);
            dataInFloat[i] = (float)sinValue;
            audioWaveTheta += sampleIncrement;
        }
    }

    return frame;
}
  • Étant donné que cette méthode accède à la mémoire tampon brute sous-jacente aux types Windows Runtime, elle doit être déclarée à l’aide du mot clé unsafe. Vous devez également configurer votre projet dans Microsoft Visual Studio pour autoriser la compilation de code unsafe en ouvrant la page Propriétés du projet, en cliquant sur la page de propriétés Générer, puis en cochant la case Autoriser le code unsafe.
  • Initialisez une nouvelle instance de AudioFrame, dans l’espace de noms Windows.Media, en passant la taille de tampon désirée au constructeur. La taille du tampon est le nombre d’échantillons multiplié par la taille de chaque échantillon.
  • Obtenez le AudioBuffer du frame audio en appelant LockBuffer.
  • Obtenez une instance de l’interface COM IMemoryBufferByteAccess à partir du tampon audio en appelant CreateReference.
  • Obtenez un pointeur vers les données brutes du tampon audio en appelant IMemoryBufferByteAccess.GetBuffer et convertissez-le au type de données d’échantillon des données audio.
  • Remplissez le tampon avec des données et retournez le AudioFrame pour soumission dans le graphique audio.

Nœud de sortie de trame audio

Un nœud de sortie de trame audio vous permet de recevoir et de traiter les données audio produites par le graphique audio avec un code personnalisé que vous créez. Un exemple de scénario pour cela est l’analyse du signal de sortie audio. Créez un AudioFrameOutputNode en appelant CreateFrameOutputNode.

AudioFrameOutputNode frameOutputNode;
private void CreateFrameOutputNode()
{
    frameOutputNode = audioGraph.CreateFrameOutputNode();
    audioGraph.QuantumStarted += AudioGraph_QuantumStarted;
}

L’événement AudioGraph.QuantumStarted est déclenché lorsque le graphique audio commence à traiter un quantum de données audio. Vous pouvez accéder aux données audio depuis le gestionnaire de cet événement.

Remarque

Si vous voulez récupérer les trames audio de manière régulière, synchronisées avec le graphique audio, appelez AudioFrameOutputNode.GetFrame depuis le gestionnaire d’événement QuantumStarted synchrone. L’événement QuantumProcessed est déclenché de manière asynchrone après que le moteur audio ait terminé le traitement audio, ce qui signifie que sa cadence peut être irrégulière. Par conséquent, vous ne devriez pas utiliser l’événement QuantumProcessed pour le traitement synchronisé des données de trame audio.

private void AudioGraph_QuantumStarted(AudioGraph sender, object args)
{
    AudioFrame frame = frameOutputNode.GetFrame();
    ProcessFrameOutput(frame);

}
  • Appelez GetFrame pour obtenir un objet AudioFrame rempli avec des données audio du graphique.
  • Un exemple d’implémentation de la méthode d’aide ProcessFrameOutput est montrée ci-dessous.
unsafe private void ProcessFrameOutput(AudioFrame frame)
{
    using (AudioBuffer buffer = frame.LockBuffer(AudioBufferAccessMode.Write))
    using (IMemoryBufferReference reference = buffer.CreateReference())
    {
        byte* dataInBytes;
        uint capacityInBytes;
        float* dataInFloat;

        // Get the buffer from the AudioFrame
        ((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacityInBytes);

        dataInFloat = (float*)dataInBytes;
    }
}
  • Comme dans l’exemple de nœud d’entrée de trame audio ci-dessus, vous devrez déclarer l’interface COM IMemoryBufferByteAccess et configurer votre projet pour autoriser le code non sécurisé afin d’accéder au tampon audio sous-jacent.
  • Obtenez le AudioBuffer du frame audio en appelant LockBuffer.
  • Obtenez une instance de l’interface COM IMemoryBufferByteAccess à partir du tampon audio en appelant CreateReference.
  • Obtenez un pointeur vers les données brutes du tampon audio en appelant IMemoryBufferByteAccess.GetBuffer et convertissez-le au type de données d’échantillon des données audio.

Connexions des nœuds et nœuds de sous-mixage

Tous les types de nœuds d’entrée exposent la méthode AddOutgoingConnection qui route l’audio produit par le nœud vers le nœud passé dans la méthode. L’exemple suivant connecte un AudioFileInputNode à un AudioDeviceOutputNode, ce qui est une configuration simple pour lire un fichier audio sur le haut-parleur du périphérique.

fileInputNode.AddOutgoingConnection(deviceOutputNode);

Vous pouvez créer plus d’une connexion à partir d’un nœud d’entrée vers d’autres nœuds. L’exemple suivant ajoute une autre connexion du AudioFileInputNode à un AudioFileOutputNode. Maintenant, l’audio du fichier audio est lu sur le haut-parleur du périphérique et est également écrit dans un fichier audio.

fileInputNode.AddOutgoingConnection(fileOutputNode);

Les nœuds de sortie peuvent également recevoir plus d’une connexion à partir d’autres nœuds. Dans l’exemple suivant, une connexion est établie depuis un AudioDeviceInputNode vers le nœud AudioDeviceOutput. Comme le nœud de sortie a des connexions à partir du nœud d’entrée de fichier et du nœud d’entrée de périphérique, la sortie contiendra un mélange audio des deux sources. AddOutgoingConnection fournit une surcharge qui vous permet de spécifier une valeur de gain pour le signal passant par la connexion.

deviceInputNode.AddOutgoingConnection(deviceOutputNode, .5);

Bien que les nœuds de sortie puissent accepter des connexions de plusieurs nœuds, vous voudrez peut-être créer un mélange intermédiaire de signaux à partir d’un ou de plusieurs nœuds avant de passer le mélange à une sortie. Par exemple, vous pouvez vouloir régler le niveau ou appliquer des effets à un sous-ensemble des signaux audio dans un graphique. Pour ce faire, utilisez le AudioSubmixNode. Vous pouvez vous connecter à un nœud de sous-mixage à partir d’un ou de plusieurs nœuds d’entrée ou d’autres nœuds de sous-mixage. Dans l’exemple suivant, un nouveau nœud de sous-mixage est créé avec AudioGraph.CreateSubmixNode. Ensuite, des connexions sont ajoutées à partir d’un nœud d’entrée de fichier et d’un nœud de sortie de trame vers le nœud de sous-mixage. Enfin, le nœud de sous-mixage est connecté à un nœud de sortie de fichier.

private void CreateSubmixNode()
{
    AudioSubmixNode submixNode = audioGraph.CreateSubmixNode();
    fileInputNode.AddOutgoingConnection(submixNode);
    frameInputNode.AddOutgoingConnection(submixNode);
    submixNode.AddOutgoingConnection(fileOutputNode);
}

Démarrer et arrêter des nœuds de graphique audio

Lorsque AudioGraph.Start est appelé, le graphique audio commence à traiter les données audio. Chaque type de nœud fournit des méthodes Start et Stop qui font démarrer ou arrêter le traitement des données par le nœud individuel. Lorsque AudioGraph.Stop est appelé, tout le traitement audio dans tous les nœuds est arrêté, quel que soit l’état des nœuds individuels, mais l’état de chaque nœud peut être défini lorsque le graphique audio est arrêté. Par exemple, vous pourriez appeler Stop sur un nœud individuel pendant que le graphique est arrêté, puis appeler AudioGraph.Start, et le nœud individuel restera dans l’état arrêté.

Tous les types de nœuds exposent la propriété ConsumeInput qui, lorsqu’elle est définie sur false, permet au nœud de continuer le traitement audio mais l’empêche de consommer des données audio entrantes en provenance d’autres nœuds.

Tous les types de nœuds exposent la méthode Reset qui provoque le vidage de toutes les données audio actuellement dans son tampon.

Ajout d’effets audio

L’API du graphique audio vous permet d’ajouter des effets audio à chaque type de nœud dans un graphique. Les nœuds de sortie, les nœuds d’entrée et les nœuds de sous-mixage peuvent chacun avoir un nombre illimité d’effets audio, limité uniquement par les capacités du matériel. L’exemple suivant montre comment ajouter l’effet d’écho intégré à un nœud de sous-mixage.

EchoEffectDefinition echoEffect = new EchoEffectDefinition(audioGraph);
echoEffect.Delay = 1000.0;
echoEffect.Feedback = .2;
echoEffect.WetDryMix = .5;

submixNode.EffectDefinitions.Add(echoEffect);
  • Tous les effets audio implémentent IAudioEffectDefinition. Chaque nœud expose une propriété EffectDefinitions représentant la liste des effets appliqués à ce nœud. Ajoutez un effet en ajoutant son objet de définition à la liste.
  • Il existe plusieurs classes de définition d’effets fournies dans l’espace de noms Windows.Media.Audio. notamment :
  • Vous pouvez créer vos propres effets audio qui implémentent IAudioEffectDefinition et les appliquer à n’importe quel nœud dans un graphique audio.
  • Chaque type de nœud expose une méthode DisableEffectsByDefinition qui désactive tous les effets dans la liste EffectDefinitions du nœud qui ont été ajoutés en utilisant la définition spécifiée. EnableEffectsByDefinition active les effets avec la définition spécifiée.

Audio spatial

À partir de Windows 10, version 1607, AudioGraph prend en charge l’audio spatial, qui vous permet de spécifier l’emplacement dans l’espace 3D à partir duquel l’audio de tout nœud d’entrée ou de sous-mixage est émis. Vous pouvez également spécifier une forme et une direction dans lesquelles l’audio est émis, une vitesse qui sera utilisée pour le décalage Doppler de l’audio du nœud et définir un modèle de décroissance qui décrit comment l’audio est atténué avec la distance.

Pour créer un émetteur, vous pouvez d’abord créer une forme dans laquelle le son est projeté à partir de l’émetteur, qui peut être un cône ou omnidirectionnel. La classe AudioNodeEmitterShape fournit des méthodes statiques pour créer chacune de ces formes. Ensuite, créez un modèle de décroissance. Cela définit comment le volume de l’audio de l’émetteur diminue à mesure que la distance par rapport à l’auditeur augmente. La méthode CreateNatural crée un modèle de décroissance qui émule la décroissance naturelle du son en utilisant un modèle de diminution quadratique de la distance. Enfin, créez un objet AudioNodeEmitterSettings. Actuellement, cet objet est uniquement utilisé pour activer et désactiver l’atténuation Doppler basée sur la vitesse de l’audio de l’émetteur. Appelez le constructeur AudioNodeEmitter, en passant les objets d’initialisation que vous venez de créer. Par défaut, l’émetteur est placé à l’origine, mais vous pouvez définir la position de l’émetteur avec la propriété Position.

Remarque

Les émetteurs de nœuds audio ne peuvent traiter que l’audio formaté en mono avec un taux d’échantillonnage de 48 kHz. Tenter d’utiliser de l’audio stéréo ou de l’audio avec un taux d’échantillonnage différent entraînera une exception.

Vous attribuez l’émetteur à un nœud audio lors de sa création en utilisant la méthode de création surchargée pour le type de nœud que vous souhaitez. Dans cet exemple, CreateFileInputNodeAsync est utilisé pour créer un nœud d’entrée de fichier à partir d’un fichier spécifié et de l’objet AudioNodeEmitter que vous souhaitez associer au nœud.

var emitterShape = AudioNodeEmitterShape.CreateOmnidirectional();
var decayModel = AudioNodeEmitterDecayModel.CreateNatural(.1, 1, 10, 100);
var settings = AudioNodeEmitterSettings.None;

var emitter = new AudioNodeEmitter(emitterShape, decayModel, settings);
emitter.Position = new System.Numerics.Vector3(10, 0, 5);

CreateAudioFileInputNodeResult result = await audioGraph.CreateFileInputNodeAsync(file, emitter);

if (result.Status != AudioFileNodeCreationStatus.Success)
{
    ShowErrorMessage(result.Status.ToString());
}

fileInputNode = result.FileInputNode;

Le AudioDeviceOutputNode qui émet l’audio du graphique à l’utilisateur a un objet d’auditeur, accessible avec la propriété Listener, qui représente l’emplacement, l’orientation et la vitesse de l’utilisateur dans l’espace 3D. Les positions de tous les émetteurs dans le graphique sont relatives à la position et à l’orientation de l’objet d’auditeur. Par défaut, l’auditeur est situé à l’origine (0,0,0) et fait face vers l’avant le long de l’axe Z, mais vous pouvez définir sa position et son orientation avec les propriétés Position et Orientation.

deviceOutputNode.Listener.Position = new System.Numerics.Vector3(100, 0, 0);
deviceOutputNode.Listener.Orientation = System.Numerics.Quaternion.CreateFromYawPitchRoll(0, (float)Math.PI, 0);

Vous pouvez mettre à jour la position, la vitesse et la direction des émetteurs à l’exécution pour simuler le mouvement d’une source audio à travers l’espace 3D.

var emitter = fileInputNode.Emitter;
emitter.Position = newObjectPosition;
emitter.DopplerVelocity = newObjectPosition - oldObjectPosition;

Vous pouvez également mettre à jour la position, la vitesse et l’orientation de l’objet d’auditeur à l’exécution pour simuler le mouvement de l’utilisateur à travers l’espace 3D.

deviceOutputNode.Listener.Position = newUserPosition;

Par défaut, l’audio spatial est calculé en utilisant l’algorithme de fonction de transfert relative à la tête (HRTF) de Microsoft pour atténuer l’audio en fonction de sa forme, de sa vitesse et de sa position par rapport à l’auditeur. Vous pouvez définir la propriété SpatialAudioModel sur FoldDown pour utiliser une méthode de mixage stéréo simple pour simuler l’audio spatial, qui est moins précise mais nécessite moins de ressources CPU et mémoire.

Voir aussi