Gráficos de áudio

Este artigo mostra como usar as APIs no namespace Windows.Media.Audio para criar gráficos de áudio para cenários de roteamento, mixagem e processamento de áudio.

Um gráfico de áudio é um conjunto de nós de áudio interconectados, por meio dos quais fluem os dados de áudio.

  • Os nós de entrada de áudio fornecem dados de áudio ao gráfico a partir de dispositivos de entrada de áudio, de arquivos de áudio ou de código personalizado. lat

  • Osnós de saída de áudio são o destino do áudio processado pelo gráfico. O áudio pode ser roteado para fora do gráfico para dispositivos de saída de áudio, arquivos de áudio ou código personalizado.

  • Os nós de submixagem pegam áudio de um ou mais nós e combina-os em uma única saída que pode ser roteada para outros nós no gráfico.

Depois que todos os nós forem criados e as conexões entre eles forem configuradas, basta iniciar o gráfico de áudio para que os dados de áudio fluam dos nós de entrada, por todos os nós de submixagem, para os nós de saída. Esse modelo torna cenários, como a gravação do microfone de um dispositivo para um arquivo de áudio, a reprodução de áudio de um arquivo do alto-falante do dispositivo ou a mixagem de áudio de várias fontes, rápidos e fáceis de implementar.

Cenários adicionais são habilitados com a adição de efeitos de áudio ao gráfico de áudio. Cada nó em um gráfico de áudio pode ser preenchido com zeros ou mais efeitos de áudio que executam o processamento de áudio no áudio que passa pelo nó. Há vários efeitos internos como eco, equalizador, limitação e reverberação que podem ser anexados a um nó de áudio com apenas algumas linhas de código. Você também pode criar seus próprios efeitos de áudio que funcionem exatamente como os efeitos internos.

Observação

A amostra AudioGraph UWP implementa o código abordado nesta visão geral. Você pode baixar a amostra para ver o código usado no contexto ou usá-lo como ponto de partida para seu próprio aplicativo.

Escolhendo AudioGraph ou XAudio2 do Windows Runtime

As APIs de gráfico de áudio do Windows Runtime oferecem a funcionalidade que também pode ser implementada usando-se as APIs do XAudio2 baseadas em COM. A seguir estão os recursos da estrutura de gráfico de áudio do Windows Runtime que diferem do XAudio2.

As APIs de gráfico de áudio do Windows Runtime:

  • São significativamente mais fáceis de usar que as do XAudio2.
  • Podem ser usadas em C#, além de oferecerem suporte a C++.
  • Podem usar arquivos de áudio, incluindo formatos de arquivo compactado, diretamente. O XAudio2 opera somente em buffers de áudio e não fornece nenhuma funcionalidade de E/S.
  • Podem usar o pipeline de áudio de baixa latência no Windows 10.
  • Oferecem suporte à troca automática de ponto de extremidade quando são usados parâmetros de ponto de extremidade padrão. Por exemplo, se o usuário alterna do alto-falante do dispositivo para um fone de ouvido, o áudio é automaticamente redirecionado para a nova entrada.

Classe AudioGraph

A classe AudioGraph é pai de todos os nós que compõem o gráfico. Use esse objeto para criar instâncias de todos os tipos de nós de áudio. Crie uma instância da classe AudioGraph inicializando um objeto AudioGraphSettings contendo definições de configuração para o gráfico e chame AudioGraph.CreateAsync. O CreateAudioGraphResult retornado dá acesso ao gráfico de áudio criado ou fornece um valor de erro em caso de falha na criação do gráfico de áudio.

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;

}
  • Todos os tipos de nó de áudio são criados usando os métodos Create* da classe AudioGraph .

  • O método AudioGraph.Start faz com que o gráfico de áudio comece a processar dados de áudio. O método AudioGraph.Stop para o processamento do áudio. Cada nó no gráfico pode ser iniciado e interrompido independentemente enquanto o gráfico está sendo executado, mas nenhum nó está ativo quando o gráfico é interrompido. ResetAllNodes faz com que todos os nós no gráfico descartem todos os dados em seus buffers de áudio no momento.

  • O evento QuantumStarted ocorre quando o gráfico está iniciando o processamento de um novo quantum de dados de áudio. O evento QuantumProcessed ocorre quando o processamento de um quantum é concluído.

  • A única propriedade AudioGraphSettings necessária é AudioRenderCategory. A especificação desse valor permite que o sistema otimize o pipeline de áudio para a categoria especificada.

  • O tamanho de quantum do gráfico de áudio determina o número de amostras que são processadas de uma só vez. Por padrão, o tamanho do quantum é de 10 ms com base na taxa de amostra padrão. Se você especificar um tamanho de quantum definido a propriedade DesiredSamplesPerQuantum, também deve definir a propriedade QuantumSizeSelectionMode como ClosestToDesired; caso contrário, o valor fornecido será ignorado. Se esse valor for usado, o sistema escolherá o tamanho de quantum mais próximo possível daquele que você especificou. Para determinar o tamanho real quantum, verifique o SamplesPerQuantum de AudioGraph depois que ele for criado.

  • Se você pretende usar o gráfico de áudio com arquivos apenas e não pretende enviar saída para um dispositivo de áudio, é recomendável que você use o tamanho de quantum padrão não definindo a propriedade DesiredSamplesPerQuantum.

  • A propriedade DesiredRenderDeviceAudioProcessing determina a quantidade de processamento que o dispositivo de renderização principal realiza na saída do gráfico de áudio. A configuração Default permite que o sistema use o processamento de áudio padrão na categoria de renderização de áudio especificada. Esse processamento pode melhorar significativamente o som do áudio em alguns dispositivos, especialmente dispositivos móveis com alto-falantes pequenos. A configuração Raw pode melhorar o desempenho, minimizando a quantidade de processamento de sinal realizada, mas pode resultar em som de qualidade inferior em alguns dispositivos.

  • Se o QuantumSizeSelectionMode é definido como LowestLatency, o gráfico de áudio usará automaticamente Raw para DesiredRenderDeviceAudioProcessing.

  • A partir do Windows 10, versão 1803, você pode definir a propriedade AudioGraphSettings.MaxPlaybackSpeedFactor a fim de definir um valor máximo usado pelas propriedades AudioFileInputNode.PlaybackSpeedFactor, AudioFrameInputNode.PlaybackSpeedFactor e MediaSourceInputNode.PlaybackSpeedFactor. Quando um gráfico de áudio oferece suporte a um fator de velocidade de reprodução maior que 1, o sistema deve alocar memória adicional para manter um buffer suficiente de dados de áudio. Por esse motivo, a configuração de MaxPlaybackSpeedFactor com o valor mais baixo exigido pelo seu aplicativo reduz o consumo de memória do aplicativo. Caso seu aplicativo reproduza somente conteúdo na velocidade normal, é recomendável que você defina MaxPlaybackSpeedFactor como 1.

  • EncodingProperties determina o formato de áudio usado pelo gráfico. Há suporte para formatos float de 32 bits somente.

  • O PrimaryRenderDevice define o dispositivo de renderização principal para o gráfico de áudio. Se você não definir isso, o dispositivo padrão do sistema será usado. O dispositivo de renderização principal é usado para calcular os tamanhos de quantum para outros nós do gráfico. Se não houver dispositivos de renderização de áudio presentes no sistema, a criação de gráfico de áudio falhará.

Você pode permitir que o gráfico de áudio use o dispositivo de renderização de áudio padrão ou usar a classe Windows.Devices.Enumeration.DeviceInformation para obter uma lista dos dispositivos de renderização de áudio disponíveis no sistema chamando FindAllAsync e passando o seletor de dispositivo de renderização de áudio retornado por Windows.Media.Devices.MediaDevice.GetAudioRenderSelector. É possível escolher um dos objetos DeviceInformation retornados programaticamente ou mostrar a interface do usuário para permitir que o usuário selecione um dispositivo e, em seguida, usá-lo para definir a propriedade 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ó de entrada de dispositivo

Um nó de entrada de dispositivo passa áudio para o gráfico a partir de um dispositivo de captura de áudio conectado ao sistema, como um microfone. Crie um objeto DeviceInputNode que usa o dispositivo de captura de áudio padrão do sistema chamando CreateDeviceInputNodeAsync. Forneça uma AudioRenderCategory para permitir que o sistema otimize o pipeline de áudio para a categoria especificada.

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 você quiser especificar um dispositivo de captura de áudio específico para o nó de entrada do dispositivo, use a classe Windows.Devices.Enumeration.DeviceInformation para obter uma lista dos dispositivos de captura de áudio disponíveis do sistema chamando FindAllAsync e passando o seletor de dispositivo de renderização de áudio retornado por Windows.Media.Devices.MediaDevice.GetAudioCaptureSelector. É possível escolher um dos objetos DeviceInformation retornados programaticamente ou mostrar a interface do usuário para permitir que o usuário selecione um dispositivo e, em seguida, passá-lo para 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ó de saída do dispositivo

Um nó de saída do dispositivo empurra áudio do gráfico para um dispositivo de renderização de áudio, como alto-falantes ou um fone de ouvido. Crie um DeviceOutputNode chamando CreateDeviceOutputNodeAsync. O nó de saída usa o PrimaryRenderDevice do gráfico de áudio.

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ó de entrada do arquivo

Um nó de entrada de arquivo permite que você alimente dados de um arquivo de áudio no gráfico. Crie um AudioFileInputNode chamando 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;
}
  • Nós de entrada do arquivo oferecem suporte aos seguintes formatos de arquivo: mp3, wav, wma, m4a.
  • Defina a propriedade StartTime para especificar a diferença de horário para o arquivo no qual a reprodução deve começar. Se essa propriedade for null, o início do arquivo será usado. Defina a propriedade EndTime para especificar o deslocamento de tempo para o arquivo no qual a reprodução deve terminar. Se essa propriedade for nula, será usado o fim do arquivo. O valor de hora inicial deve ser menor que o valor de hora final, e o valor de hora final deve ser menor ou igual à duração do arquivo de áudio, que pode ser determinada verificando o valor da propriedade Duration.
  • Busque uma posição no arquivo de áudio chamando Seek e especificando o deslocamento de tempo para o arquivo no qual a posição de reprodução deve ser movida. O valor especificado deve estar dentro do intervalo de StartTime e EndTime. Obtenha a posição de reprodução atual do nó com a propriedade somente leitura Position.
  • Habilite o loop do arquivo de áudio definindo a propriedade LoopCount. Quando não é nulo, esse valor indica o número de vezes em que o arquivo será reproduzido após a reprodução inicial. Assim, por exemplo, a definição de LoopCount como 1 fará com que o arquivo seja reproduzido 2 vezes no total e defini-lo como 5 fará com que o arquivo seja reproduzido 6 vezes no total. A definição de LoopCount como nulo fará com que o arquivo entre em um loop infinito. Para interromper o loop, defina o valor como 0.
  • Ajuste a velocidade em que o arquivo de áudio é reproduzido definindo PlaybackSpeedFactor. Um valor 1 indica a velocidade original do arquivo, 0,5 indica metade da velocidade e 2 é velocidade dupla.

Nó de entrada de MediaSource

A classe MediaSource fornece uma maneira comum de referenciar mídia de diferentes fontes e expõe um modelo comum para acessar dados de mídia, independentemente do formato de mídia subjacente, que pode ser um arquivo no disco, um streaming ou uma fonte de rede de streaming adaptável. Um nó **MediaSourceAudioInputNode permite que você direcione dados de áudio de uma MediaSource para o gráfico de áudio. Crie um MediaSourceAudioInputNode ao chamar CreateMediaSourceAudioInputNodeAsync, passando um objeto MediaSource representando o conteúdo que você deseja reproduzir. Um **CreateMediaSourceAudioInputNodeResult é retornado, o qual você pode usar para determinar o status da operação ao verificar a propriedade Status. Se o status for Sucesso, você pode obter o MediaSourceAudioInputNode criado ao acessar a propriedade Node. O exemplo a seguir mostra a criação de um nó de um objeto AdaptiveMediaSource representando o streaming de conteúdo pela rede. Para obter mais informações sobre como trabalhar com MediaSource, consulte Itens de mídia, playlists e faixas. Para obter mais informações sobre o conteúdo de mídia de streaming pela Internet, consulte Streaming adaptável.

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

Para receber uma notificação quando a reprodução chegar ao fim do conteúdo de MediaSource, registre um manipulador para o evento MediaSourceCompleted.

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

Ao reproduzir um arquivo do disco, é provável que seja concluído com sucesso, a mídia transmitida de uma fonte de rede pode falhar durante a reprodução devido a uma alteração na conexão de rede ou outros problemas fora do controle do gráfico de áudio. Se não for possível reproduzir uma MediaSource, o gráfico de áudio acionará o evento UnrecoverableErrorOccurred. Você pode usar o manipulador para esse evento a fim de parar e descartar o gráfico de áudio e, em seguida, reinicializar o gráfico.

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ó de saída de arquivo

Um nó de saída de arquivo permite que você direcione dados de áudio do gráfico para um arquivo de áudio. Crie um AudioFileOutputNode chamando 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ó de entrada de quadro de áudio

Um nó de entrada de quadro de áudio permite que você envie dados de áudio que você gera em seu próprio código para o gráfico de áudio. Isso habilita cenários como a criação de um sintetizador de software personalizado. Crie um AudioFrameInputNode chamando 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;
}

O evento FrameInputNode.QuantumStarted é disparado quando o gráfico de áudio está pronto para começar o processamento do próximo quantum de dados de áudio. Forneça dados de áudio personalizados gerados de dentro do manipulador para esse 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);
    }
}
  • O objeto FrameInputNodeQuantumStartedEventArgs passado para o manipulador de evento QuantumStarted expõe a propriedade RequiredSamples que indica quantas amostras são necessárias para que o gráfico de áudio preencha o quantum a ser processado.
  • Chame AudioFrameInputNode.AddFrame para passar um objeto AudioFrame preenchido com dados de áudio para o gráfico.
  • Um novo conjunto de APIs para usar MediaFrameReader com dados de áudio foi introduzido no Windows 10, versão 1803. Essas APIs permitem que você obtenha objetos AudioFrame a partir de uma fonte de quadro de mídia, que pode ser passada para um FrameInputNode usando o método AddFrame. Para obter mais informações, consulte Processar quadros de áudio com o MediaFrameReader.
  • Veja a seguir um exemplo de implementação do método auxiliar GenerateAudioData.

Para preencher um AudioFrame com dados de áudio, você deve obter acesso ao buffer de memória subjacente do quadro de áudio. Para fazer isso, você deve inicializar a interface COM IMemoryBufferByteAccess adicionando o seguinte código dentro de seu namespace.

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

O código a seguir mostra um exemplo de implementação de um método auxiliar GenerateAudioData que cria um AudioFrame e o preenche com dados de áudio.

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;
}
  • Como esse método acessa o buffer bruto subjacente aos tipos do Windows Runtime, ele deve ser declarado usando a palavra-chave unsafe. Você também deve configurar seu projeto no Microsoft Visual Studio para permitir a compilação de código não seguro. Para fazer isso, abra a página Propriedades do projeto, clique na página de propriedade Build e selecione a caixa de seleção Permitir Código Não Seguro.
  • Inicialize uma nova instância de AudioFrame, no namespace Windows.Media, passando o tamanho de buffer desejado para o construtor. O tamanho do buffer é o número de amostras multiplicado pelo tamanho de cada exemplo.
  • Obtenha o AudioBuffer do quadro de áudio chamando LockBuffer.
  • Obtenha uma instância da interface COM IMemoryBufferByteAccess do buffer de áudio chamando CreateReference.
  • Obtenha um ponteiro para dados de buffer de áudio bruto chamando IMemoryBufferByteAccess.GetBuffer e transmita-o para o tipo de dados de amostra dos dados de áudio.
  • Preencha o buffer com dados e retorne o AudioFrame para envio para o gráfico de áudio.

Nó de saída de quadro de áudio

Um nó de saída de quadro de áudio permite receber e processar a saída de dados de áudio do gráfico de áudio com o código personalizado que você criar. Um cenário de exemplo para isso é a análise do sinal na saída de áudio. Crie um AudioFrameOutputNode chamando CreateFrameOutputNode.

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

O evento AudioGraph.QuantumStarted é disparado quando o gráfico de áudio inicia o processamento de um quantum de dados de áudio. Você pode acessar os dados de áudio de dentro do manipulador para esse evento.

Observação

Se você quiser recuperar quadros de áudio em uma cadência regular, sincronizado com o gráfico de áudio, chame AudioFrameOutputNode.GetFrame dentro do manipulador de eventos QuantumStarted síncronos. O evento QuantumProcessed é gerado assincronamente depois que o mecanismo de áudio conclui o processamento de áudio, o que significa que sua cadência pode ser irregular. Portanto, você não deve usar o evento QuantumProcessed para o processamento sincronizado de dados de quadros de áudio.

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

}
  • Chame GetFrame para obter um objeto AudioFrame preenchido com dados de áudio do gráfico.
  • Veja a seguir um exemplo de implementação do método auxiliar 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;
    }
}
  • Como o exemplo de nó de entrada de quadro de áudio acima, você precisa declarar a interface COM IMemoryBufferByteAccess e configurar seu projeto para permitir código não seguro a fim de acessar o buffer de áudio subjacente.
  • Obtenha o AudioBuffer do quadro de áudio chamando LockBuffer.
  • Obtenha uma instância da interface COM IMemoryBufferByteAccess do buffer de áudio chamando CreateReference.
  • Obtenha um ponteiro para dados de buffer de áudio bruto chamando IMemoryBufferByteAccess.GetBuffer e transmita-o para o tipo de dados de amostra dos dados de áudio.

Conexões de nós e nós de submixagem

Todos os nós a exposição de tipos de entrada do método AddOutgoingConnection que encaminha o áudio produzido pelo nó para o nó que é passado para o método. O exemplo a seguir conecta um AudioFileInputNode a um AudioDeviceOutputNode, que é uma configuração simples para reproduzir um arquivo de áudio no alto-falante do dispositivo.

fileInputNode.AddOutgoingConnection(deviceOutputNode);

Você pode criar mais de uma conexão de um nó de entrada para outros nós. O exemplo a seguir adiciona outra conexão do AudioFileInputNode para um AudioFileOutputNode. Agora, o áudio do arquivo de áudio é reproduzido no alto-falante do dispositivo e também é gravado em um arquivo de áudio.

fileInputNode.AddOutgoingConnection(fileOutputNode);

Nós de saída também podem receber mais de uma conexão de outros nós. No exemplo a seguir, uma conexão é feita de um AudioDeviceInputNode para o nó AudioDeviceOutput. Como o nó de saída tem conexões do nó de entrada do arquivo e do nó de entrada do dispositivo, a saída conterá uma combinação das duas fontes de áudio. AddOutgoingConnection fornece uma sobrecarga que permite a especificação de um valor de ganho para o sinal que passa pela conexão.

deviceInputNode.AddOutgoingConnection(deviceOutputNode, .5);

Embora nós de saída possam aceitar conexões de vários nós, convém criar uma combinação intermediária de sinais de um ou mais nós antes de passar a combinação para uma saída. Por exemplo, pode ser desejável definir o nível ou aplicar efeitos em um subconjunto de sinais de áudio em um gráfico. Para fazer isso, use o AudioSubmixNode. Você pode se conectar a um nó de submixagem a partir de um ou mais nós ou outros nós de submixagem de entrada. No exemplo a seguir, um novo nó de submixagem é criado com AudioGraph.CreateSubmixNode. Em seguida, são adicionadas conexões de um nó de entrada de arquivo e um nó de saída de quadro para o nó de submixagem. Por fim, o nó de submixagem é conectado a um nó de saída do arquivo.

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

Iniciando e interrompendo nós de gráfico de áudio

Quando AudioGraph.Start é chamado, o gráfico de áudio começa a processar dados de áudio. Cada tipo de nó fornece métodos Start e Stop que fazem com que o nó individual inicie ou pare o processamento de dados. Quando AudioGraph.Stop é chamado, todo o processamento de áudio em todos os nós é parado independentemente do estado dos nós individuais, mas o estado de cada nó pode ser definido enquanto o gráfico de áudio é interrompido. Por exemplo, você pode chamar Stop em um nó individual enquanto o gráfico é parado e, em seguida, chamar AudioGraph.Start para que o nó individual permaneça no estado parado.

Todos os tipos de nó expõem a propriedade ConsumeInput que, quando definida como false, permite que o nó continue o processamento de áudio mas interrompe seu consumo de quaisquer dados de áudio que estejam sendo recebidos de outros nós.

Todos os tipos de nó expõem o método Reset que faz o nó descartar todos os dados de áudio no buffer no momento.

Adicionando efeitos de áudio

A API do gráfico de áudio permite que você adicione efeitos de áudio a cada tipo de nó em um gráfico. Nós de saída, nós de entrada e nós de submixagem podem ter um número ilimitado de efeitos de áudio, limitado somente pelos recursos do hardware. O exemplo a seguir demonstra a adição do efeito de eco interno a um nó de submixagem.

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

submixNode.EffectDefinitions.Add(echoEffect);
  • Todos os efeitos de áudio implementam IAudioEffectDefinition. Cada nó expõe uma propriedade EffectDefinitions que representa a lista de efeitos aplicados ao nó. Adicione um efeito adicionando seu objeto de definição à lista.
  • Várias classes de definição de efeito são fornecidas no namespace Windows.Media.Audio. Estão incluídos:
  • Você pode criar seus próprios efeitos de áudio que implementam IAudioEffectDefinition e aplicá-los a qualquer nó em um gráfico de áudio.
  • Cada nó expõe um método DisableEffectsByDefinition que desabilita todos os efeitos na lista EffectDefinitions do nó que foram adicionados usando a definição especificada. EnableEffectsByDefinition habilita os efeitos com a definição especificada.

Áudio espacial

A partir do Windows 10, versão 1607, AudioGraph oferece suporte ao áudio espacial, que permite que você especifique o local no espaço 3D do qual o audio de qualquer entrada ou nó de submixagem seja emitido. Você também pode especificar uma forma e a direção em que o áudio é emitido, uma velocidade que será usada para fazer a mudança de Doppler do áudio do nó e definir um modelo de decaimento que descreve como o áudio é atenuado com a distância.

Para criar um emissor, primeiro você pode criar uma forma na qual o som é projetado a partir do emissor, que pode ser um cone ou unidirecional. A classe AudioNodeEmitterShape fornece métodos estáticos para criar cada uma dessas formas. Em seguida, crie um modelo de decaimento. Isso define como o volume do áudio do emissor diminui à medida que a distância do ouvinte aumenta. O método CreateNatural cria um modelo de decaimento que emula o decaimento natural de som usando um modelo de queda do quadrado da distância. Por fim, crie um objeto AudioNodeEmitterSettings. Atualmente, esse objeto é usado somente para habilitar e desabilitar atenuações de Doppler baseadas na velocidade do áudio do emissor. Chame o construtor AudioNodeEmitter, transmitindo os objetos de inicialização que você acabou de criar. Por padrão, o emissor é colocado na origem, mas você pode definir a posição do emissor com a propriedade Position.

Observação

Os emissores de nó de áudio somente podem processar o áudio formatado em mono com uma taxa de amostragem de 48 kHz. A tentativa de usar o áudio estéreo ou o áudio com uma taxa de amostragem diferente resultará em uma exceção.

Você deve atribuir o emissor a um nó de áudio ao criá-lo, usando o método sobrecarregado de criação para o tipo de nó que deseja. Neste exemplo, CreateFileInputNodeAsync é usado para criar um nó de entrada do arquivo de um arquivo especificado e o objeto AudioNodeEmitter que você deseja associar ao nó.

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;

O AudioDeviceOutputNode que gera áudio do gráfico para o usuário tem um objeto ouvinte, acessado com a propriedade Listener, que representa a localização, a orientação e a velocidade do usuário no espaço 3D. As posições de todos os emissores no grafo são relativas à posição e à orientação do objeto ouvinte. Por padrão, o ouvinte está localizado na origem (0, 0, 0) voltado para frente, ao longo do eixo Z, mas você pode definir sua posição e orientação com as propriedades 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);

Você pode atualizar a localização, a velocidade e a direção dos emissores no tempo de execução para simular o movimento de uma fonte de áudio pelo espaço 3D.

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

Você também pode atualizar a localização, a velocidade e a orientação do objeto ouvinte no tempo de execução para simular o movimento do usuário pelo espaço 3D.

deviceOutputNode.Listener.Position = newUserPosition;

Por padrão, o áudio espacial é calculado usando o algoritmo de função de transferência relativas à cabeça (HRTF) da Microsoft para atenuar o áudio com base em sua forma, velocidade e posição relativa ao ouvinte. Você pode definir a propriedade SpatialAudioModel como FoldDown para usar um método simples de mixagem estéreo de simulação de áudio espacial que é menos preciso, mas exige menos recursos de CPU e de memória.

Confira também