Grafici audio

Questo articolo illustra come usare le API nello spazio dei nomi Windows.Media.Audio per creare grafici audio per scenari di routing, mixaggio ed elaborazione audio.

Un grafico audio è un set di nodi audio interconnessi attraverso i quali i dati audio vengono trasmessi.

  • I nodi di input audio forniscono dati audio al grafico da dispositivi di input audio, file audio o da codice personalizzato. lat

  • I nodi di output audio sono la destinazione per l'audio elaborato dal grafico. L'audio può essere instradato dal grafico a dispositivi di output audio, file audio o codice personalizzato.

  • I nodi submix accettano l'audio da uno o più nodi e li combinano in un singolo output che può essere instradato ad altri nodi nel grafico.

Dopo che tutti i nodi sono stati creati e le connessioni tra di essi configurate, è sufficiente avviare il grafico audio e i dati audio passano dai nodi di input, attraverso qualsiasi nodo submix, ai nodi di output. Questo modello crea scenari come la registrazione dal microfono di un dispositivo a un file audio, la riproduzione di audio da un file all'altoparlante di un dispositivo o la combinazione di audio da più origini in modo rapido e facile da implementare.

Altri scenari sono abilitati con l'aggiunta di effetti audio al grafico audio. Ogni nodo in un grafico audio può essere popolato con zero o più effetti audio che eseguono l'elaborazione audio sul passaggio dell'audio attraverso il nodo. Esistono diversi effetti predefiniti, ad esempio echo, equalizer, limitazione e riverbero, che possono essere collegati a un nodo audio con poche righe di codice. È anche possibile creare effetti audio personalizzati che funzionano esattamente come gli effetti predefiniti.

Nota

L'esempio L'esempio UWP AudioGraph implementa il codice descritto in questa panoramica. È possibile scaricare l'esempio per visualizzare il codice nel contesto o da usare come punto di partenza per la propria app.

Scelta di AudioGraph di Windows Runtime o XAudio2

Le API del grafico audio di Windows Runtime offrono funzionalità che possono essere implementate anche usando le API XAudio2 basate su COM. Di seguito sono riportate le funzionalità del framework del grafico audio di Windows Runtime che differiscono da XAudio2.

Api del grafico audio di Windows Runtime:

  • Sono notevolmente più facili da usare rispetto a XAudio2.
  • Può essere usato da C# oltre a essere supportato per C++.
  • Può usare file audio, inclusi i formati di file compressi, direttamente. XAudio2 funziona solo su buffer audio e non fornisce funzionalità di I/O di file.
  • Può usare la pipeline audio a bassa latenza in Windows 10.
  • Supportare il cambio automatico di endpoint quando vengono usati i parametri endpoint predefiniti. Ad esempio, se l'utente passa dall'altoparlante di un dispositivo a un visore VR, l'audio viene reindirizzato automaticamente al nuovo input.

Classe AudioGraph

La classe AudioGraph è l'elemento padre di tutti i nodi che costituiscono il grafico. Utilizzare questo oggetto per creare istanze di tutti i tipi di nodo audio. Creare un'istanza della classe AudioGraph inizializzando un oggetto AudioGraphSettings contenente le impostazioni di configurazione per il grafico e quindi chiamando AudioGraph.CreateAsync. L'oggetto CreateAudioGraphResult restituito consente di accedere al grafico audio creato o fornisce un valore di errore se la creazione del grafico audio ha esito negativo.

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;

}
  • Tutti i tipi di nodi audio vengono creati usando i metodi Create* della classe AudioGraph.

  • Il metodo AudioGraph.Start determina l'avvio dell'elaborazione dei dati audio da parte del grafico audio. Il metodo AudioGraph.Stop arresta l'elaborazione audio. Ogni nodo del grafico può essere avviato e arrestato in modo indipendente mentre il grafico è in esecuzione, ma non sono attivi nodi quando il grafico viene arrestato. ResetAllNodes fa sì che tutti i nodi del grafico eliminino tutti i dati attualmente presenti nei buffer audio.

  • L'evento QuantumStarted si verifica quando il grafico avvia l'elaborazione di un nuovo quantum di dati audio. L'evento QuantumProcessed si verifica quando viene completata l'elaborazione di un quantum.

  • L'unica proprietà AudioGraphSettings obbligatoria è AudioRenderCategory. Se si specifica questo valore, il sistema può ottimizzare la pipeline audio per la categoria specificata.

  • La dimensione quantistica del grafico audio determina il numero di campioni elaborati contemporaneamente. Per impostazione predefinita, la dimensione quantistica è 10 ms in base alla frequenza di campionamento predefinita. Se si specifica una dimensione quantistica personalizzata impostando la proprietà DesiredSamplesPerQuantum, è necessario impostare anche la proprietà QuantumSizeSelectionMode a ClosestToDesired oppure il valore fornito viene ignorato. Se viene usato questo valore, il sistema sceglierà una dimensione quantistica il più vicina possibile a quella specificata. Per determinare la dimensione quantistica effettiva, controllare SamplesPerQuantum di AudioGraph dopo la creazione.

  • Se si prevede di usare solo il grafico audio con file e non si prevede di eseguire l'output in un dispositivo audio, è consigliabile usare le dimensioni quantistiche predefinite non impostando la proprietà DesiredSamplesPerQuantum.

  • La proprietà DesiredRenderDeviceAudioProcessing determina la quantità di elaborazione eseguita dal dispositivo di rendering primario nell'output del grafico audio. L'impostazione Default consente al sistema di usare l'elaborazione audio predefinita per la categoria di rendering audio specificata. Questa elaborazione può migliorare significativamente il suono dell'audio in alcuni dispositivi, in particolare i dispositivi mobili con altoparlanti di piccole dimensioni. L'impostazione Raw può migliorare le prestazioni riducendo al minimo la quantità di elaborazione dei segnali eseguita, ma può comportare una qualità audio inferiore in alcuni dispositivi.

  • Se QuantumSizeSelectionMode è impostato su LowestLatency, il grafico audio userà automaticamente Raw per DesiredRenderDeviceAudioProcessing.

  • A partire da Windows 10, versione 1803, si può impostare la proprietà AudioGraphSettings.MaxPlaybackSpeedFactor per impostare un valore massimo usato per le proprietà AudioFileInputNode.PlaybackSpeedFactor, AudioFrameInputNode.PlaybackSpeedFactor e MediaSourceInputNode.PlaybackSpeedFactor. Quando un grafico audio supporta un fattore di velocità di riproduzione maggiore di 1, il sistema deve allocare memoria aggiuntiva per mantenere un buffer sufficiente di dati audio. Per questo motivo, l'impostazione di MaxPlaybackSpeedFactor sul valore più basso richiesto dall'app ridurrà il consumo di memoria dell'app. Se l'app riproduce il contenuto solo alla velocità normale, è consigliabile impostare MaxPlaybackSpeedFactor a 1.

  • EncodingProperties determina il formato audio usato dal grafico. Sono supportati solo i formati float a 32 bit.

  • PrimaryRenderDevice imposta il dispositivo di rendering primario per il grafico audio. Se non si imposta questa impostazione, viene usato il dispositivo di sistema predefinito. Il dispositivo di rendering primario viene usato per calcolare le dimensioni quantistiche per altri nodi nel grafico. Se nel sistema non sono presenti dispositivi di rendering audio, la creazione del grafico audio avrà esito negativo.

Si può consentire al grafico audio di usare il dispositivo di rendering audio predefinito o usare la classe Windows.Devices.Enumeration.DeviceInformation per ottenere un elenco dei dispositivi di rendering audio disponibili del sistema chiamando FindAllAsync e passando il selettore del dispositivo di rendering audio restituito da Windows.Media.Devices.MediaDevice.GetAudioRenderSelector. Puoi scegliere uno degli oggetti DeviceInformation restituiti a livello di codice o mostrare l'interfaccia utente per consentire all'utente di selezionare un dispositivo e quindi usarlo per impostare la proprietà 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;

Nodo di input del dispositivo

Un nodo di input del dispositivo inserisce audio nel grafico da un dispositivo di acquisizione audio connesso al sistema, ad esempio un microfono. Creare un oggetto DeviceInputNode che usa il dispositivo di acquisizione audio predefinito del sistema chiamando CreateDeviceInputNodeAsync. Fornire un oggetto AudioRenderCategory per consentire al sistema di ottimizzare la pipeline audio per la categoria specificata.

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;
}

Se si vuole specificare un dispositivo di acquisizione audio specifico per il nodo di input del dispositivo, si può usare la classe Windows.Devices.Enumeration.DeviceInformation per ottenere un elenco dei dispositivi di acquisizione audio disponibili del sistema chiamando FindAllAsync e passando il selettore del dispositivo di rendering audio restituito da Windows.Media.Devices.MediaDevice.GetAudioCaptureSelector. Si può scegliere uno degli oggetti DeviceInformation restituiti a livello di codice o mostrare l'interfaccia utente per consentire all'utente di selezionare un dispositivo e quindi passarlo a 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);

Nodo di output del dispositivo

Un nodo di output del dispositivo esegue il push dell'audio dal grafico a un dispositivo di rendering audio, ad esempio altoparlanti o visori VR. Creare un DeviceOutputNode chiamando CreateDeviceOutputNodeAsync. Il nodo di output usa PrimaryRenderDevice del grafico 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;
}

Nodo di input file

Un nodo di input di file consente di inserire dati da un file audio nel grafico. Creare un AudioFileInputNode chiamando 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;
}
  • I nodi di input dei file supportano i formati di file seguenti: mp3, wav, wma, m4a.
  • Impostare la proprietà StartTime per specificare l'offset temporale nel file in cui deve iniziare la riproduzione. Se questa proprietà è null, viene utilizzato l'inizio del file. Impostare la proprietà EndTime per specificare l'offset temporale nel file in cui deve terminare la riproduzione. Se questa proprietà è null, viene utilizzata la fine del file. Il valore dell'ora di inizio deve essere inferiore al valore dell'ora di fine e il valore dell'ora di fine deve essere minore o uguale alla durata del file audio, che può essere determinato controllando il valore della proprietà Duration.
  • Cercare una posizione nel file audio chiamando Seek e specificando l'offset di tempo nel file in cui deve essere spostata la posizione di riproduzione. Il valore specificato deve essere compreso nell'intervallo StartTime e EndTime. Ottiene la posizione di riproduzione corrente del nodo con la proprietà di sola lettura Position.
  • Abilitare il ciclo del file audio impostando la proprietà LoopCount. Se non null, questo valore indica il numero di volte in cui il file verrà riprodotto dopo la riproduzione iniziale. Ad esempio, impostando LoopCount su 1, il file verrà riprodotto 2 volte in totale e impostandolo su 5 il file verrà riprodotto 6 volte in totale. Se si imposta LoopCount su null, il file viene eseguito a ciclo continuo per un periodo illimitato. Per arrestare il ciclo, impostare il valore su 0.
  • Regolare la velocità di riproduzione del file audio impostando PlaybackSpeedFactor. Il valore 1 indica la velocità originale del file, .5 è a metà velocità e 2 è doppia velocità.

Nodo di input MediaSource

La classe MediaSource offre un modo comune per fare riferimento a supporti provenienti da origini diverse ed espone un modello comune per l'accesso ai dati multimediali indipendentemente dal formato multimediale sottostante, che potrebbe essere un file su disco, un flusso o un'origine di rete di streaming adattiva. Un nodo **MediaSourceAudioInputNode consente di indirizzare i dati audio da un oggetto MediaSource al grafico audio. Creare un oggetto MediaSourceAudioInputNode chiamando CreateMediaSourceAudioInputNodeAsync, passando un oggetto MediaSource che rappresenta il contenuto che si desidera riprodurre. Viene restituito un **CreateMediaSourceAudioInputNodeResult che è possibile usare per determinare lo stato dell'operazione controllando la proprietà Status. Se lo stato è Success, è possibile ottenere l'oggetto MediaSourceAudioInputNode creato accedendo alla proprietà Node. Nell'esempio seguente viene illustrata la creazione di un nodo da un oggetto AdaptiveMediaSource che rappresenta lo streaming del contenuto in rete. Per altre informazioni sull'uso di MediaSource, vedere Elementi multimediali, playlist e tracce. Per altre informazioni sullo streaming di contenuti multimediali su Internet, vedere Streaming adattivo.

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;
}

Per ricevere una notifica quando la riproduzione ha raggiunto la fine del contenuto MediaSource, registrare un gestore per l'evento MediaSourceCompleted.

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

Durante la riproduzione di un file da diskis è probabile che venga sempre completato correttamente, i contenuti multimediali trasmessi da un'origine di rete potrebbero non riuscire durante la riproduzione a causa di una modifica della connessione di rete o di altri problemi esterni al controllo del grafico audio. Se un MediaSource diventa non riproducibile durante la riproduzione, il grafico audio genererà l'evento UnrecoverableErrorOccurred. È possibile usare il gestore per questo evento per arrestare e eliminare il grafico audio e quindi reinizializzare il grafico.

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();
    }
}

Nodo di output del file

Un nodo di output di file consente di indirizzare i dati audio dal grafico in un file audio. Creare un AudioFileOutputNode chiamando 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;
}

Nodo di input dei frame audio

Un nodo di input dei frame audio consente di eseguire il push dei dati audio generati nel proprio codice nel grafico audio. Ciò consente scenari come la creazione di un sintetizzatore software personalizzato. Creare un oggetto AudioFrameInputNode chiamando 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'evento FrameInputNode.QuantumStarted viene generato quando il grafico audio è pronto per iniziare a elaborare il successivo quantum di dati audio. Fornire i dati audio generati personalizzati dall'interno del gestore a questo evento.

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'oggetto FrameInputNodeQuantumStartedEventArgs passato nel gestore evento QuantumStarted espone RequiredSamples che indica quanti campioni il grafico audio deve riempire il quantum da elaborare.
  • Chiamare AudioFrameInputNode.AddFrame per passare a un oggetto AudioFrame pieno di dati audio nel grafico.
  • In Windows 10 versione 1803 è stato introdotto un nuovo set di API per l'uso di MediaFrameReader con dati audio. Questi API consentono di ottenere oggetti AudioFrame da un'origine di fotogrammi multimediali, che possono essere passati a un FrameInputNode usando il metodo AddFrame. Per altre informazioni, vedere Elaborare frame audio con MediaFrameReader.
  • Di seguito è illustrata un'implementazione di esempio del metodo helper GenerateAudioData.

Per popolare un AudioFrame con dati audio, è necessario ottenere l'accesso al buffer di memoria sottostante del frame audio. A tale scopo, è necessario inizializzare l'interfaccia COM IMemoryBufferByteAccess aggiungendo il codice seguente all'interno dello spazio dei nomi.

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

Il codice seguente illustra un'implementazione di esempio di un metodo helper GenerateAudioData che crea un AudioFrame e lo popola con dati 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;
}
  • Poiché questo metodo accede al buffer non elaborato sottostante ai tipi di Windows Runtime, deve essere dichiarato usando la parola chiave unsafe. È inoltre necessario configurare il progetto in Microsoft Visual Studio per consentire la compilazione di codice non sicuro aprendo la pagina Proprietà del progetto, facendo clic sulla pagina proprietà Build e selezionando la casella di spunta Allow Unsafe Code.
  • Inizializzare una nuova istanza di AudioFrame nello spazio dei nomi Windows.Media passando le dimensioni del buffer desiderate al costruttore. La dimensione del buffer è il numero di campioni moltiplicato per le dimensioni di ogni campione.
  • Ottenere l'AudioBuffer del fotogramma audio chiamando LockBuffer.
  • Ottenere un'istanza dell'interfaccia COM IMemoryBufferByteAccess dal buffer audio chiamando CreateReference.
  • Ottenere un puntatore ai dati del buffer audio non elaborato chiamando IMemoryBufferByteAccess.GetBuffer ed eseguirne il cast al tipo di dati di esempio dei dati audio.
  • Riempire il buffer con i dati e restituire l'AudioFrame per l'invio nel grafico audio.

Nodo di output del frame audio

Un nodo di output dei frame audio consente di ricevere ed elaborare l'output dei dati audio dal grafico audio con codice personalizzato creato. Uno scenario di esempio è l'esecuzione dell'analisi dei segnali sull'output audio. Creare un oggetto AudioFrameOutputNode chiamando CreateFrameOutputNode.

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

L'evento AudioGraph.QuantumStarted viene generato quando il grafico audio inizia a elaborare un quantum di dati audio. È possibile accedere ai dati audio dall'interno del gestore per questo evento.

Nota

Se si desidera recuperare frame audio a cadenza regolare, sincronizzati con il grafico audio, chiamare AudioFrameOutputNode.GetFrame dall'interno del gestore eventi QuantumStarted sincrono. L'evento QuantumProcessed viene generato in modo asincrono dopo che il motore audio ha completato l'elaborazione audio, il che significa che la frequenza potrebbe essere irregolare. Pertanto, non è consigliabile usare l'evento QuantumProcessed per l'elaborazione sincronizzata dei dati dei frame audio.

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

}
  • Chiamare GetFrame per ottenere un oggetto AudioFrame pieno di dati audio dal grafico.
  • Di seguito è illustrata un'implementazione di esempio del metodo helper ProcessFrameOutput.
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;
    }
}
  • Come nell'esempio precedente del nodo di input dei fotogrammi audio, dovrai dichiarare l'interfaccia COM IMemoryBufferByteAccess e configurare il progetto per consentire il codice non sicuro per accedere al buffer audio sottostante.
  • Ottenere l'AudioBuffer del fotogramma audio chiamando LockBuffer.
  • Ottenere un'istanza dell'interfaccia COM IMemoryBufferByteAccess dal buffer audio chiamando CreateReference.
  • Ottenere un puntatore ai dati del buffer audio non elaborato chiamando IMemoryBufferByteAccess.GetBuffer ed eseguirne il cast al tipo di dati di esempio dei dati audio.

Connessioni nodo e nodi submix

Tutti i tipi di nodi di input espongono il metodo AddOutgoingConnection che instrada l'audio prodotto dal nodo al nodo passato al metodo. L'esempio seguente collega un AudioFileInputNode a un AudioDeviceOutputNode, che è una semplice configurazione per la riproduzione di un file audio nell'altoparlante del dispositivo.

fileInputNode.AddOutgoingConnection(deviceOutputNode);

È possibile creare più connessioni da un nodo di input ad altri nodi. Nell'esempio seguente viene aggiunta un'altra connessione da AudioFileInputNode a un AudioFileOutputNode. Ora, l'audio del file audio viene riprodotto nell'altoparlante del dispositivo e viene scritto anche in un file audio.

fileInputNode.AddOutgoingConnection(fileOutputNode);

I nodi di output possono anche ricevere più connessioni da altri nodi. Nell'esempio seguente viene stabilita una connessione da un oggetto AudioDeviceInputNode al nodo AudioDeviceOutput. Poiché il nodo di output ha connessioni dal nodo di input del file e dal nodo di input del dispositivo, l'output conterrà una combinazione di audio da entrambe le origini. AddOutgoingConnection fornisce un overload che consente di specificare un valore di guadagno per il segnale che passa attraverso la connessione.

deviceInputNode.AddOutgoingConnection(deviceOutputNode, .5);

Anche se i nodi di output possono accettare connessioni da più nodi, è possibile creare una combinazione intermedia di segnali da uno o più nodi prima di passare la combinazione a un output. Ad esempio, è possibile impostare il livello o applicare effetti a un subset dei segnali audio in un grafico. A tale scopo, usare AudioSubmixNode. È possibile connettersi a un nodo submix da uno o più nodi di input o da altri nodi submix. Nell'esempio seguente viene creato un nuovo nodo submix con AudioGraph.CreateSubmixNode. Le connessioni vengono quindi aggiunte da un nodo di input di file e da un nodo di output frame al nodo submix. Infine, il nodo submix è connesso a un nodo di output del file.

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

Avvio e arresto dei nodi del grafico audio

Quando viene chiamato AudioGraph.Start, il grafico audio inizia a elaborare i dati audio. Ogni tipo di nodo fornisce metodi Start e Stop che causano l'avvio o l'interruzione dell'elaborazione dei dati da parte del singolo nodo. Quando viene chiamato AudioGraph.Stop, tutte le elaborazioni audio in tutti i nodi vengono arrestate indipendentemente dallo stato dei singoli nodi, ma lo stato di ogni nodo può essere impostato mentre il grafico audio viene arrestato. Ad esempio, è possibile chiamare Stop su un singolo nodo mentre il grafico viene arrestato e quindi chiamare AudioGraph.Start e il singolo nodo rimarrà nello stato arrestato.

Tutti i tipi di nodo espongono la proprietà ConsumeInput che, se impostata su false, consente al nodo di continuare l'elaborazione audio, ma impedisce l'utilizzo di dati audio provenienti da altri nodi.

Tutti i tipi di nodo espongono il metodo Reset che fa sì che il nodo elimini tutti i dati audio attualmente presenti nel buffer.

Aggiunta di effetti audio

L'API grafico audio consente di aggiungere effetti audio a ogni tipo di nodo in un grafico. I nodi di output, i nodi di input e i nodi submix possono avere un numero illimitato di effetti audio, limitati solo dalle funzionalità dell'hardware. Nell'esempio seguente viene illustrata l'aggiunta dell'effetto echo predefinito a un nodo submix.

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

submixNode.EffectDefinitions.Add(echoEffect);
  • Tutti gli effetti audio implementano IAudioEffectDefinition. Ogni nodo espone una proprietà EffectDefinitions che rappresenta l'elenco di effetti applicati a tale nodo. Aggiungere un effetto aggiungendo l'oggetto definizione all'elenco.
  • Esistono diverse classi di definizione degli effetti disponibili nello spazio dei nomi Windows.Media.Audio . Queste includono:
  • È possibile creare effetti audio personalizzati che implementano IAudioEffectDefinition e applicarli a qualsiasi nodo in un grafico audio.
  • Ogni tipo di nodo espone un metodo DisableEffectsByDefinition che disabilita tutti gli effetti nell'elenco EffectDefinitions del nodo aggiunto usando la definizione specificata. EnableEffectsByDefinition abilita gli effetti con la definizione specificata.

Audio spaziale

A partire da Windows 10, versione 1607, AudioGraph supporta l'audio spaziale, che consente di specificare la posizione nello spazio 3D da cui viene generato l'audio da qualsiasi nodo di input o submix. È anche possibile specificare una forma e una direzione in cui viene generato l'audio, una velocità che verrà usata per spostare l'audio del nodo e definire un modello di decadimento che descrive come l'audio viene attenuato con la distanza.

Per creare un emettitore, è possibile creare prima una forma in cui il suono viene proiettato dall'emettitore, che può essere un cono o omnidirectionale. La classe AudioNodeEmitterShape fornisce metodi statici per la creazione di ognuna di queste forme. Creare quindi un modello di decadimento. Questo definisce il modo in cui il volume dell'audio dell'emettitore diminuisce man mano che aumenta la distanza dal listener. Il metodo CreateNatural crea un modello di decadimento che emula il decadimento naturale del suono usando un modello di falloff quadratico a distanza. Infine, creare un oggetto AudioNodeEmitterSettings. Attualmente, questo oggetto viene usato solo per abilitare e disabilitare l'attenuazione doppler basata sulla velocità dell'audio dell'emettitore. Chiamare il costruttore AudioNodeEmitter, passando gli oggetti di inizializzazione appena creati. Per impostazione predefinita, l'emettitore viene posizionato all'origine, ma è possibile impostare la posizione dell'emettitore con la proprietà Position.

Nota

Gli emettitori di nodi audio possono elaborare solo l'audio formattato in mono con una frequenza di campionamento di 48 kHz. Il tentativo di usare audio o audio stereo con una frequenza di campionamento diversa genererà un'eccezione.

L'emettitore viene assegnato a un nodo audio quando lo si crea usando il metodo di creazione di overload per il tipo di nodo desiderato. In questo esempio, CreateFileInputNodeAsync viene usato per creare un nodo di input di file da un file specificato e l'oggetto AudioNodeEmitter da associare al nodo.

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;

AudioDeviceOutputNode che restituisce l'audio dal grafico all'utente ha un oggetto listener, accessibile con la proprietà Listener che rappresenta la posizione, l'orientamento e la velocità dell'utente nello spazio 3D. Le posizioni di tutti gli emettitori nel grafico sono relative alla posizione e all'orientamento dell'oggetto listener. Per impostazione predefinita, il listener si trova in corrispondenza dell'origine (0,0,0) rivolta verso l'avanti lungo l'asse Z, ma è possibile impostare la posizione e l'orientamento con le proprietà Position e Orientation.

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

È possibile aggiornare la posizione, la velocità e la direzione degli emettitori in fase di esecuzione per simulare lo spostamento di una sorgente audio attraverso lo spazio 3D.

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

È anche possibile aggiornare la posizione, la velocità e l'orientamento dell'oggetto listener in fase di esecuzione per simulare lo spostamento dell'utente attraverso lo spazio 3D.

deviceOutputNode.Listener.Position = newUserPosition;

Per impostazione predefinita, l'audio spaziale viene calcolato usando l'algoritmo HRTF (Head-Relative Transfer Function) di Microsoft per attenuare l'audio in base alla forma, alla velocità e alla posizione rispetto al listener. Si può impostare la proprietà ppatialAudioModel su FoldDown per usare un semplice metodo di combinazione stereo per simulare l'audio spaziale meno accurato, ma richiede meno risorse di CPU e memoria.

Vedi anche