Compartilhar via


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 os dados de áudio fluem.

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

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

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

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

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 zero ou mais efeitos de áudio que executam o processamento de áudio no áudio que passa pelo nó. Existem 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 personalizados que funcionam exatamente da mesma forma que os efeitos internos.

Observação

O exemplo AudioGraph UWP implementa o código discutido nesta visão geral. Você pode baixar o exemplo para ver o código no contexto ou para usar como ponto de partida para seu próprio aplicativo.

Escolhendo o AudioGraph ou XAudio2 do Windows Runtime

As APIs de gráfico de áudio do Windows Runtime oferecem funcionalidade que também pode ser implementada usando as APIs 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 do que XAudio2.
  • Podem ser usadas a partir de C#, além de serem suportadas para C++.
  • Podem usar arquivos de áudio, incluindo formatos de arquivo compactados, diretamente. O XAudio2 só opera em buffers de áudio e não fornece nenhum recurso de E/S de arquivo.
  • Podem usar o pipeline de áudio de baixa latência no Windows 10.
  • Suportam comutação automática de ponto de extremidade quando parâmetros de ponto de extremidade padrão são usados. Por exemplo, se o usuário alternar do alto-falante de um dispositivo para um fone de ouvido, o áudio será redirecionado automaticamente para a nova entrada.

Classe AudioGraph

A classe AudioGraph é o 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ó de áudio. Crie uma instância da classe AudioGraph inicializando um objeto AudioGraphSettings que contém definições de configuração para o gráfico e, em seguida, chamando AudioGraph.CreateAsync. O CreateAudioGraphResult retornado dá acesso ao gráfico de áudio criado ou fornece um valor de erro se a criação do gráfico de áudio falhar.

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 interrompe o processamento de áudio. Cada nó no gráfico pode ser iniciado e interrompido de forma independente enquanto o gráfico está em execução, mas nenhum nó está ativo quando o gráfico é parado. ResetAllNodes faz com que todos os nós no gráfico descarte todos os dados atualmente nos seus buffers de áudio.

  • 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 do quantum do gráfico de áudio determina o número de amostras que são processadas ao mesmo tempo. Por padrão, o tamanho do quantum é de 10 ms com base na taxa de amostragem padrão. Se você especificar um tamanho do quantum personalizado definindo a propriedade DesiredSamplesPerQuantum, também deverá definir a QuantumSizeSelectionMode como ClosestToDesired ou o valor fornecido será ignorado. Se esse valor for usado, o sistema escolherá um tamanho do quantum o mais próximo possível daquele que você especificar. Para determinar o tamanho do quantum real, verifique o SamplesPerQuantum do AudioGraph depois que ele tiver sido criado.

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

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

  • Se o QuantumSizeSelectionMode estiver 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 para definir um valor máximo usado para as propriedades AudioFileInputNode.PlaybackSpeedFactor, AudioFrameInputNode.PlaybackSpeedFactor e MediaSourceInputNode.PlaybackSpeedFactor. Quando um gráfico de áudio suporta 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, definir MaxPlaybackSpeedFactor para o menor valor exigido pelo seu aplicativo reduzirá o consumo de memória do seu aplicativo. Se o seu aplicativo só reproduzir conteúdo em velocidade normal, é recomendável definir MaxPlaybackSpeedFactor como 1.

  • EncodingProperties determina o formato de áudio usado pelo gráfico. Apenas os formatos float de 32 bits são suportados.

  • PrimaryRenderDevice define o dispositivo de renderização primário para o gráfico de áudio. Se você não definir isso, o dispositivo do sistema padrão será usado. O dispositivo de renderização primário é usado para calcular os tamanhos do quantum para outros nós no gráfico. Se não houver dispositivos de renderização de áudio presentes no sistema, a criação do 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 do sistema chamando FindAllAsync e enviando no seletor de dispositivo de renderização de áudio retornado por Windows.Media.Devices.MediaDevice.GetAudioRenderSelector. Você pode 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, use-o 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 do dispositivo

Um nó de entrada de dispositivo alimenta o áudio no 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 um 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 quiser especificar um dispositivo de captura de áudio específico para o nó de entrada do dispositivo, você poderá usar a classe Windows.Devices.Enumeration.DeviceInformation para obter uma lista dos dispositivos de captura de áudio disponíveis do sistema chamando FindAllAsync e enviando no seletor de dispositivo de renderização de áudio retornado por Windows.Media.Devices.MediaDevice.GetAudioCaptureSelector. Você pode 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, envie 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 de dispositivo envia áudio do gráfico para um dispositivo de renderização de áudio, como alto-falantes ou 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 de 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;
}
  • Os nós de entrada de arquivo suportam os seguintes formatos de arquivo: mp3, wav, wma, m4a.
  • Defina a propriedade StartTime para especificar o deslocamento de tempo no arquivo em que a reprodução deve começar. Se essa propriedade for nula, o início do arquivo será usado. Defina a propriedade EndTime para especificar o deslocamento de tempo no arquivo em que a reprodução deve terminar. Se essa propriedade for nula, o fim do arquivo será usado. O valor de hora de início deve ser menor que o valor de hora de término, e o valor de hora de término deve ser menor ou igual à duração do arquivo de áudio, que pode ser determinada verificando o valor da propriedade Duration.
  • Procure uma posição no arquivo de áudio chamando Seek e especificando o deslocamento de tempo no arquivo para o qual a posição de reprodução deve ser movida. O valor especificado deve estar dentro do intervalo StartTime e EndTime. Obtenha a posição de reprodução atual do nó com a propriedade Position somente leitura.
  • Habilite o looping do arquivo de áudio definindo a propriedade LoopCount. Quando não nulo, esse valor indica o número de vezes que o arquivo será reproduzido após a reprodução inicial. Assim, por exemplo, definir 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. Definir LoopCount como nulo faz com que o arquivo seja repetido indefinidamente. Para parar o looping, defina o valor como 0.
  • Ajuste a velocidade na qual o arquivo de áudio é reproduzido definindo o PlaybackSpeedFactor. Um valor de 1 indica a velocidade original do arquivo, .5 é meia velocidade e 2 é velocidade dupla.

Nó de entrada MediaSource

A classe MediaSource fornece uma maneira comum de referenciar a 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 em disco, um fluxo ou uma fonte de rede de streaming adaptável. Um nó **MediaSourceAudioInputNode permite direcionar dados de áudio de um MediaSource para o gráfico de áudio. Crie um MediaSourceAudioInputNode chamando CreateMediaSourceAudioInputNodeAsync, passando um objeto MediaSource que representa o conteúdo que você deseja reproduzir. Um **CreateMediaSourceAudioInputNodeResult é retornado, e você pode usar para determinar o status da operação verificando a propriedade Status. Se o status for Êxito, você poderá obter o MediaSourceAudioInputNode criado acessando a propriedade Node. O exemplo a seguir mostra a criação de um nó a partir de um objeto AdaptiveMediaSource que representa o streaming de conteúdo pela rede. Para obter mais informações sobre como trabalhar com o MediaSource, consulte Itens de mídia, listas de reprodução e faixas. Para obter mais informações sobre streaming de conteúdo de mídia 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 tiver atingido o final do conteúdo MediaSource, registre um manipulador para o evento MediaSourceCompleted.

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

Embora a reprodução de um arquivo do disco seja provavelmente sempre concluída com êxito, 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 que estão fora do controle do gráfico de áudio. Se um MediaSource se tornar não reproduzível durante a reprodução, o gráfico de áudio acionará o evento UnrecoverableErrorOccurred. Você pode usar o manipulador para esse evento para parar e descartar o gráfico de áudio e, em seguida, reinicializá-lo.

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 direcionar 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 gerados no seu próprio código para o gráfico de áudio. Isso permite 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 é gerado quando o gráfico de áudio está pronto para começar a processar o próximo quantum de dados de áudio. Você fornece seus dados de áudio gerados personalizados 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 eventos QuantumStarted expõe a propriedade RequiredSamples que indica quantas amostras o gráfico de áudio precisa para preencher 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 de uma fonte de quadro de mídia, que pode ser passada para um FrameInputNode usando o método AddFrame. Para saber mais, confira Processar quadros de áudio com o MediaFrameReader.
  • Um exemplo de implementação do método auxiliar GenerateAudioData é mostrado abaixo.

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 do 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 abrindo a página Propriedades do projeto, clicando na página de propriedades Build e marcando 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 amostra.
  • 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 brutos do buffer de áudio chamando IMemoryBufferByteAccess.GetBuffer e o converta no tipo de dados de exemplo dos dados de áudio.
  • Preencha o buffer com dados e retorne o AudioFrame para envio no gráfico de áudio.

Nó de saída de quadro de áudio

Um nó de saída de quadro de áudio permite que você receba e processe 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 realização de análise de 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 é gerado quando o gráfico de áudio começa a processar 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, sincronizada com o gráfico de áudio, chame AudioFrameOutputNode.GetFrame de dentro do manipulador de eventos QuantumStarted síncrono. O evento QuantumProcessed é gerado de forma assíncrona depois que o mecanismo de áudio concluiu o processamento de áudio, o que significa que sua cadência pode ser irregular. Portanto, você não deve usar o evento QuantumProcessed para processamento sincronizado de dados de quadro 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.
  • Um exemplo de implementação do método auxiliar ProcessFrameOutput é mostrado abaixo.
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ê precisará 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 brutos do buffer de áudio chamando IMemoryBufferByteAccess.GetBuffer e o converta no tipo de dados de exemplo dos dados de áudio.

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

Todos os tipos de nós de entrada expõem o método AddOutgoingConnection que roteia o áudio produzido pelo nó para o nó que é passado ao 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 a 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);

Os 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 dispositivo, a saída conterá uma mistura de áudio de ambas as fontes. AddOutgoingConnection fornece uma sobrecarga que permite especificar um valor de ganho para o sinal que passa pela conexão.

deviceInputNode.AddOutgoingConnection(deviceOutputNode, .5);

Embora os 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 mixagem para uma saída. Por exemplo, convém definir o nível ou aplicar efeitos a um subconjunto dos sinais de áudio em um gráfico. Para fazer isso, use AudioSubmixNode. Você pode se conectar a um nó de submixagem a partir de um ou mais nós de entrada ou outros nós de submixagem. No exemplo a seguir, um novo nó de submixagem é criado com AudioGraph.CreateSubmixNode. Em seguida, as conexões são adicionadas de um nó de entrada de arquivo e um nó de saída de quadro ao nó de submixagem. Finalmente, o nó de submixagem é conectado a um nó de saída de arquivo.

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

Iniciar e parar 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 é interrompido, independentemente do estado de 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 está parado e, em seguida, chamar AudioGraph.Start e o nó individual permanecerá 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 impede que ele consuma quaisquer dados de áudio que estão sendo inseridos de outros nós.

Todos os tipos de nó expõem o método Reset que faz com que o nó descarte todos os dados de áudio atualmente em seu buffer.

Adicionar efeitos de áudio

A API de gráfico de áudio permite que você adicione efeitos de áudio a cada tipo de nó em um gráfico. Os nós de saída, nós de entrada e nós de submixagem podem ter um número ilimitado de efeitos de áudio, limitados apenas 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 a esse nó. Adicione um efeito incluindo seu objeto de definição na lista.
  • Há várias classes de definição de efeito que são fornecidas no namespace Windows.Media.Audio. Eles incluem:
  • 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 tipo de 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, o AudioGraph oferece suporte a áudio espacial, o que permite especificar o local no espaço 3D a partir do qual o áudio de qualquer nó de entrada ou submixagem é emitido. Você também pode especificar uma forma e direção na qual o áudio é emitido, uma velocidade que será usada para Doppler deslocar o áudio do nó e definir um modelo de decay que descreve como o áudio é atenuado com a distância.

Para criar um emissor, você pode primeiro criar uma forma na qual o som é projetado a partir do emissor, que pode ser um cone ou omnidirecional. A classe AudioNodeEmitterShape fornece métodos estáticos para criar cada uma dessas formas. Em seguida, crie um modelo de decay. 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 decay que emula o decay natural do som usando um modelo de redução quadrada de distância. Finalmente, crie um objeto AudioNodeEmitterSettings. Atualmente, esse objeto é usado apenas para habilitar e desabilitar a atenuação Doppler baseada em velocidade do áudio do emissor. Chame o construtor AudioNodeEmitter, enviando nos 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 só podem processar áudio formatado em mono com uma taxa de amostragem de 48 kHz. Tentar usar áudio estéreo ou áudio com uma taxa de amostragem diferente resultará em uma exceção.

Você atribui o emissor a um nó de áudio ao criá-lo usando o método de criação sobrecarregado para o tipo de nó desejado. Neste exemplo, CreateFileInputNodeAsync é usado para criar um nó de entrada de arquivo a partir de um arquivo especificado e do 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 de 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 gráfico são relativas à posição e orientação do objeto ouvinte. Por padrão, o ouvinte está localizado na origem (0,0,0) voltada 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, velocidade e direção dos emissores no tempo de execução para simular o movimento de uma fonte de áudio através do 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 em 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 HRTF (função de transferência relativa à cabeça) da Microsoft para atenuar o áudio com base em sua forma, velocidade e posição em relação ao ouvinte. Você pode definir a propriedade SpatialAudioModel como FoldDown para usar um método de mixagem estéreo simples de simulação de áudio espacial que é menos preciso, mas requer menos recursos de CPU e memória.

Confira também