Graphiques audio

Cet article explique comment utiliser les API dans la Windows. Media.Audio espace de noms pour créer des graphiques audio pour le routage audio, le mixage et les scénarios de traitement.

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

  • Les nœuds d’entrée audio fournissent des données audio au graphe à partir d’appareils d’entrée audio, de fichiers audio ou de code personnalisé.

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

  • Les nœuds de sous-mélange prennent de l’audio à partir d’un ou plusieurs nœuds et les combinent en une seule sortie qui peut être acheminée vers d’autres nœuds du graphique.

Une fois que tous les nœuds ont été créés et que les connexions entre eux sont configurées, vous démarrez simplement le graphe audio et les flux de données audio des nœuds d’entrée, via tous les nœuds de sous-mélange, vers les nœuds de sortie. Ce modèle rend les scénarios tels que l’enregistrement à partir du microphone d’un appareil vers un fichier audio, la lecture de l’audio d’un fichier vers le haut-parleur d’un appareil ou le mixage audio à partir de plusieurs sources rapides et faciles à implémenter.

Des scénarios supplémentaires sont activés avec l’ajout d’effets audio au graphe audio. Chaque nœud d’un graphe audio peut contenir un ou plusieurs effets audio qui traitent le signal audio transitant 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 seulement quelques lignes de code. Vous pouvez également créer vos propres effets audio personnalisés qui fonctionnent exactement de la même façon que les effets intégrés.

Choisir Windows Runtime AudioGraph ou XAudio2

Les API de graphique audio Windows Runtime offrent des fonctionnalités qui peuvent également être implémentées à l’aide des API com XAudio2. Voici les fonctionnalités de l’infrastructure de graphe audio Windows Runtime qui diffèrent de XAudio2.

Les API de graphe audio de Windows Runtime :

  • Sont considérablement plus faciles à utiliser que XAudio2.
  • Peut également être utilisé avec C#, en plus d’être pris en charge en C++.
  • Peut utiliser des fichiers audio, y compris des formats de fichiers compressés, directement. XAudio2 fonctionne uniquement sur les mémoires tampons audio et ne fournit aucune fonctionnalité d’E/S de fichier.
  • Peut utiliser le pipeline audio à faible latence dans Windows.
  • Prendre en charge le basculement 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 du haut-parleur d’un appareil à un casque, l’audio est automatiquement redirigé vers la nouvelle sortie.

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 des paramètres de configuration pour le graphe, puis en appelant AudioGraph.CreateAsync. Le CreateAudioGraphResult donne accès au graphe audio créé ou fournit une valeur d’erreur si la création de 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);
        return;
    }

    audioGraph = result.Graph;
}
  • Tous les types de nœuds audio sont créés à l’aide des méthodes Create* de la classe AudioGraph .

  • La méthode AudioGraph.Start entraîne le début du traitement des données audio. La méthode AudioGraph.Stop arrête le traitement audio. Chaque nœud du graphique peut être démarré et arrêté indépendamment pendant l’exécution du graphique, mais aucun nœud n’est actif lorsque le graphique est arrêté. ResetAllNodes entraîne la suppression, par tous les nœuds du graphe, de toutes les données actuellement présentes dans leurs tampons audio.

  • L’événement QuantumStarted se produit lorsque le graphique démarre 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. La spécification de cette valeur permet au système d’optimiser le pipeline audio pour la catégorie spécifiée.

  • La taille quantique 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 quantique personnalisée en définissant la propriété DesiredSamplesPerQuantum , vous devez également définir la propriété QuantumSizeSelectionMode sur ClosestToDesired ou la valeur fournie est ignorée. Si cette valeur est utilisée, le système choisit une taille quantique aussi proche que possible de celle que vous spécifiez. Pour déterminer la taille quantique réelle, vérifiez le SamplesPerQuantum de l’AudioGraph une fois qu’il a été créé.

  • Si vous envisagez uniquement d’utiliser le graphique audio avec des fichiers et de ne pas planifier la sortie sur un périphérique audio, il est recommandé d’utiliser la taille quantique par défaut en ne définissant pas la propriété DesiredSamplesPerQuantum .

  • La propriété DesiredRenderDeviceAudioProcessing détermine la quantité de traitement effectuée par l’appareil de rendu principal sur la sortie du graphique audio. Le paramètre Par défaut 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 minimisant la quantité de traitement du signal effectuée, mais peut entraîner une qualité sonore inférieure sur certains appareils.

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

  • 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 une mémoire tampon suffisante de données audio. Pour cette raison, la définition de MaxPlaybackSpeedFactor sur la valeur la plus faible requise par votre application réduit la consommation de mémoire de votre application. Si votre application ne lirea que du contenu à la vitesse normale, il est recommandé de définir MaxPlaybackSpeedFactor sur 1.

  • EncodingProperties détermine le format audio utilisé par le graphique. Seuls les formats float 32 bits sont pris en charge.

  • PrimaryRenderDevice définit l’appareil de rendu principal pour le graphe audio. Si vous ne définissez pas cela, l’appareil système par défaut est utilisé. L’appareil de rendu principal est utilisé pour calculer les tailles quantiques pour les autres nœuds du graphique. S’il n’existe aucun périphérique de rendu audio présent sur le système, la création de graphiques audio échoue.

Vous pouvez laisser le graphique audio utiliser l’appareil de rendu audio par défaut ou utiliser le Windows. Devices.Enumeration.DeviceInformation classe pour obtenir la liste des appareils de rendu audio disponibles du système en appelant FindAllAsync et en passant le sélecteur d'appareil de rendu audio retourné par Windows. Media.Devices.MediaDevice.GetAudioRenderSelector. Vous pouvez choisir l’un des objets DeviceInformation retournés par programmation ou afficher l’interface utilisateur pour permettre à l’utilisateur de sélectionner un appareil, 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 d’appareil

Un nœud d’entrée d’appareil alimente l’audio dans le graphe à partir d’un appareil de capture audio connecté au système, tel qu’un microphone. Créez un objet DeviceInputNode qui utilise l'appareil de capture audio par défaut du système en appelant CreateDeviceInputNodeAsync. Fournissez un MediaCategory 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 l’appareil, vous pouvez utiliser le Windows. Devices.Enumeration.DeviceInformation classe pour obtenir la liste des appareils de capture audio disponibles du système en appelant FindAllAsync et en passant le sélecteur d'appareil de capture audio retourné par Windows. Media.Devices.MediaDevice.GetAudioCaptureSelector. Vous pouvez choisir l’un des objets DeviceInformation retournés par programmation ou afficher l’interface utilisateur pour permettre à l’utilisateur de sélectionner un appareil, puis de le transmettre à 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 l’appareil

Un nœud de sortie d’appareil envoie l’audio du graphe à un appareil 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 graphe 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 alimenter les données 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;
    WinRT.Interop.InitializeWithWindow.Initialize(filePicker, _hwnd);
    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());
        return;
    }

    fileInputNode = result.FileInputNode;
}
  • Les nœuds d’entrée de fichier prennent en charge les formats de fichiers suivants : mp3, wav, wma, m4a.
  • Définissez la propriété StartTime pour spécifier le décalage horaire dans le fichier où la lecture doit commencer. Si cette propriété a la valeur Null, le début du fichier est utilisé. Définissez la propriété EndTime pour spécifier le décalage horaire dans le fichier où la lecture doit se terminer. Si cette propriété a la valeur Null, la fin du fichier est utilisée. La valeur d’heure de début doit être inférieure à la valeur de l’heure de fin, et la valeur d’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 propriété Duration .
  • Recherchez une position dans le fichier audio en appelant Seek et en spécifiant le décalage horaire dans le fichier vers lequel la position de lecture doit être déplacée. La valeur spécifiée doit se trouver dans la plage StartTime et EndTime . Pour obtenir la position de lecture actuelle du nœud, utilisez la propriété Position en lecture seule.
  • Activez la boucle du fichier audio en définissant la propriété LoopCount . Lorsque la valeur n’est pas null, cette valeur indique le nombre de fois que le fichier est lu après la lecture initiale. Par exemple, la définition de LoopCount sur 1 entraîne la lecture du fichier 2 fois au total et la définition de ce paramètre sur 5 entraîne la lecture du fichier 6 fois au total. La définition de LoopCount sur null entraîne la boucle du fichier 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 PlaybackSpeedFactor. La valeur 1 indique la vitesse d’origine du fichier, .5 est demi-vitesse, et 2 est double vitesse.

Nœud d’entrée MediaSource

La classe MediaSource offre un moyen courant de référencer les médias provenant de différentes sources et expose un modèle commun pour accéder aux données multimédias, quel que soit le format multimédia sous-jacent, qui peut être un fichier sur disque, un flux ou une source réseau de diffusion adaptative. Un nœud MediaSourceAudioInputNode vous permet de diriger les données audio d’un MediaSource vers le graphe 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 l’état de l’opération en vérifiant la propriété Status . Si l’état est Success, vous pouvez obtenir le MediaSourceAudioInputNode créé en accédant à la propriété Node . 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.

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

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

    MediaSource 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 a atteint la fin du contenu MediaSource , inscrivez un gestionnaire pour l’événement MediaSourceCompleted .

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

Si la lecture d’un fichier depuis le disque se termine presque toujours correctement, la lecture d’un média diffusé en continu à partir d’une source réseau peut échouer en cours de lecture en raison d’un changement de connexion réseau ou d’autres problèmes échappant au contrôle du graphe audio. Si un MediaSource devient inplayable pendant la lecture, le graphique audio déclenche 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 graphe.

if (audioGraph != null)
{
    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 graphe dans 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 System.Collections.Generic.List<string>() { ".wav" });
    saveFilePicker.FileTypeChoices.Add("Windows Media Audio", new System.Collections.Generic.List<string>() { ".wma" });
    saveFilePicker.FileTypeChoices.Add("MPEG Audio Layer-3", new System.Collections.Generic.List<string>() { ".mp3" });
    saveFilePicker.SuggestedFileName = "New Audio Track";
    WinRT.Interop.InitializeWithWindow.Initialize(saveFilePicker, _hwnd);
    StorageFile file = await saveFilePicker.PickSaveFileAsync();

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

    MediaEncodingProfile mediaEncodingProfile;
    switch (file.FileType.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 d’injecter dans le graphe audio des données audio que vous générez dans votre propre code. Cela permet des scénarios tels que 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 à traiter le prochain quantum de données audio. Vous transmettez à cet événement vos données audio personnalisées que vous générez depuis le gestionnaire.

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é au gestionnaire de l’événement QuantumStarted expose la propriété RequiredSamples, qui indique combien d’échantillons le graphe audio doit fournir pour compléter le quantum à traiter.
  • Appelez AudioFrameInputNode.AddFrame pour passer un objet AudioFrame rempli de données audio dans le graphe.
  • Vous pouvez utiliser MediaFrameReader avec des données audio pour obtenir des objets AudioFrame à partir d’une source d’images multimédias, qui peuvent être passés dans un FrameInputNode à l’aide de la méthode AddFrame .
  • Vous trouverez ci-dessous un exemple d’implémentation de la méthode d’assistance GenerateAudioData .

Pour remplir un AudioFrame avec des données audio, vous devez accéder à la mémoire tampon sous-jacente de l’image audio. Pour ce faire, initialisez l’interface COM IMemoryBufferByteAccess , comme indiqué ci-dessous.

[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 un exemple d’implémentation d’une méthode d’assistance GenerateAudioData qui crée une méthode d’assistance AudioFrame et la 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 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 permettre la compilation de code non sécurisé en ouvrant la page de propriétés Properties, en cliquant sur la page de propriétés Build et en sélectionnant la case à cocher Allow Unsafe Code.
  • Initialisez une nouvelle instance de AudioFrame, dans la Windows. Média espace de noms, en passant la taille de mémoire tampon souhaitée au constructeur. La taille de la mémoire tampon est le nombre d’échantillons multiplié par la taille de chaque échantillon.
  • Récupérez le AudioBuffer de la trame audio en appelant LockBuffer.
  • Obtenez une instance de l’interface COM IMemoryBufferByteAccess à partir de la mémoire tampon audio en appelant CreateReference.
  • Obtenez un pointeur vers des données de mémoire tampon audio brutes en appelant IMemoryBufferByteAccess.GetBuffer et en le castant dans l’exemple de type de données audio.
  • Remplissez la mémoire tampon avec des données et renvoyez le AudioFrame pour l’envoi dans le graphique audio.

Nœud de sortie d’image audio

Un nœud de sortie de trame audio vous permet de recevoir et de traiter les données audio de sortie issues du graphe audio à l’aide d’un code personnalisé que vous créez. Un exemple de scénario pour cela consiste à effectuer une analyse des signaux sur la 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 à partir du gestionnaire pour cet événement.

Note

Si vous souhaitez récupérer des images audio à une cadence régulière, synchronisées avec le graphe audio, appelez AudioFrameOutputNode.GetFrame à partir du gestionnaire d’événements QuantumStarted synchrone . L’événement QuantumProcessed est déclenché de façon asynchrone une fois que le moteur audio a terminé le traitement audio, ce qui signifie que sa cadence peut être irrégulière. Par conséquent, vous ne devez 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 de données audio à partir du graphique.
  • Un exemple d’implémentation de la méthode d’assistance ProcessFrameOutput est illustré 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 l’exemple de nœud d’entrée d’image audio ci-dessus, vous devez déclarer l’interface COM IMemoryBufferByteAccess et configurer votre projet pour autoriser le code non sécurisé afin d’accéder à la mémoire tampon audio sous-jacente.
  • Récupérez le AudioBuffer de la trame audio en appelant LockBuffer.
  • Obtenez une instance de l’interface COM IMemoryBufferByteAccess à partir de la mémoire tampon audio en appelant CreateReference.
  • Obtenez un pointeur vers des données de mémoire tampon audio brutes en appelant IMemoryBufferByteAccess.GetBuffer et en le castant dans l’exemple de type de données audio.

Connexions de nœuds et nœuds de sous-mélange

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, qui est une configuration simple pour lire un fichier audio sur le haut-parleur de l'appareil.

fileInputNode.AddOutgoingConnection(deviceOutputNode);

Vous pouvez créer plusieurs connexions à partir d’un nœud d’entrée vers d’autres nœuds. L’exemple suivant ajoute une autre connexion du AudioFileInputNode à un AudioFileOutputNode. À présent, le contenu du fichier audio est diffusé par le haut-parleur de l’appareil et est également enregistré dans un fichier audio.

fileInputNode.AddOutgoingConnection(fileOutputNode);

Les nœuds de sortie peuvent également recevoir plusieurs connexions d’autres nœuds. Dans l’exemple suivant, une connexion est établie à partir d’un AudioDeviceInputNode au nœud AudioDeviceOutput. Étant donné que le nœud de sortie dispose de connexions à partir du nœud d’entrée de fichier et du nœud d’entrée de l’appareil, la sortie contient un mélange d’audio provenant 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 à partir de plusieurs nœuds, vous pouvez créer une combinaison intermédiaire de signaux d’un ou de plusieurs nœuds avant de passer la combinaison à une sortie. Par exemple, vous pouvez définir 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-mélange à partir d’un ou plusieurs nœuds d’entrée ou d’autres nœuds de sous-mélange. Dans l’exemple suivant, un nouveau nœud de sous-mélange est créé avec AudioGraph.CreateSubmixNode. Ensuite, les connexions sont ajoutées à partir d’un nœud d’entrée de fichier et d’un nœud d’entrée frame au nœud de sous-mélange. Enfin, le nœud de sous-mélange 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émarrage et arrêt des nœuds de graphe audio

Lorsque AudioGraph.Start est appelé, le graphe audio commence à traiter les données audio. Chaque type de nœud fournit des méthodes Start et Stop qui provoquent le démarrage ou l’arrêt du 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 pendant l’arrêt du graphique audio. Par exemple, vous pouvez appeler Arrêter sur un nœud individuel pendant que le graphique est arrêté, puis appeler AudioGraph.Start, et le nœud individuel reste 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 les données audio entrantes à partir d’autres nœuds.

Tous les types de nœuds exposent la méthode Reset qui entraîne l’abandon de toutes les données audio actuellement dans sa mémoire tampon.

Ajout d’effets audio

L’API 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-mélange peuvent chacun avoir un nombre illimité d’effets audio, limités uniquement par les fonctionnalités du matériel. L’exemple suivant illustre l’ajout de l’effet d’écho intégré à un nœud de sous-mélange.

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 qui sont fournies dans l’espace de noms Windows.Media.Audio. Voici quelques-uns des éléments suivants :
  • Vous pouvez créer vos propres effets audio qui implémentent IAudioEffectDefinition et les appliquer à n’importe quel nœud d’un graphique audio.
  • Chaque type de nœud expose une méthode DisableEffectsByDefinition qui désactive tous les effets de la liste EffectDefinitions du nœud qui ont été ajoutés à l’aide de la définition spécifiée. EnableEffectsByDefinition active les effets avec la définition spécifiée.

Audio spatial

AudioGraph prend en charge l’audio spatial, ce qui vous permet de spécifier l’emplacement dans l’espace 3D à partir duquel l’audio de n’importe quel nœud d’entrée ou de sous-mélange est émis. Vous pouvez également spécifier une forme et une direction selon lesquelles l’audio est émis, une vitesse qui sera utilisée pour appliquer un décalage Doppler à l’audio du nœud, et définir un modèle d’atténuation qui décrit comment l’audio est atténué en fonction de 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ésintégration. Cela définit la façon dont le volume de l’audio de l’émetteur diminue à mesure que la distance de l’écouteur augmente. La méthode CreateNatural crée un modèle de désintégration qui émule la dégradation naturelle du son à l’aide d’un modèle de chute carré à distance. Enfin, créez un objet AudioNodeEmitterSettings. Actuellement, cet objet est utilisé uniquement 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 lui 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 .

Note

Les émetteurs de nœuds audio peuvent uniquement traiter l’audio mis en forme en mono avec un taux d’échantillonnage de 48 000 Hz. Toute tentative d’utilisation de l’audio stéréo ou audio avec un taux d’échantillonnage différent entraîne une exception.

Vous affectez l’émetteur à un nœud audio lorsque vous le créez à l’aide de la méthode de création surchargée pour le type de nœud souhaité. 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 Vector3(10, 0, 5);

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

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

fileInputNode = result.FileInputNode;

Le AudioDeviceOutputNode qui génère l’audio du graphique à l’utilisateur a un objet d’écouteur, 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 du graphe sont relatives à la position et à l’orientation de l’objet écouteur. Par défaut, l’écouteur se trouve à l’origine (0,0,0) 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 Vector3(100, 0, 0);
deviceOutputNode.Listener.Orientation = Quaternion.CreateFromYawPitchRoll(0, (float)Math.PI, 0);

Vous pouvez mettre à jour l’emplacement, la vitesse et la direction des émetteurs au moment de l’exécution pour simuler le déplacement d’une source audio via l’espace 3D.

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

Vous pouvez également mettre à jour l’emplacement, la vitesse et l’orientation de l’objet écouteur au moment de l’exécution pour simuler le déplacement de l’utilisateur via l’espace 3D.

deviceOutputNode.Listener.Position = newUserPosition;

Par défaut, l'audio spatial est calculé à l'aide de l'algorithme HRTF (Head-Relative Transfer Function) de Microsoft pour atténuer l'audio en fonction de sa forme, de sa vélocité et de sa position par rapport à l'écouteur. Vous pouvez définir la propriété SpatialAudioModel sur FoldDown pour utiliser une méthode de combinaison stéréo simple de simulation de l’audio spatial moins précis, mais qui nécessite moins de ressources processeur et mémoire.

Voir aussi