Gráficos de áudio

Este artigo mostra como usar as APIs no Windows. Media.Audio espaço de nomes para criar gráficos de áudio para cenários de encaminhamento, mistura e processamento de áudio.

Um grafo de áudio é um conjunto de nós de áudio interligados através dos quais os dados de áudio fluem.

  • Os nós de entrada de áudio fornecem dados de áudio ao grafo a partir de dispositivos de entrada de áudio, ficheiros de áudio ou de código personalizado.

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

  • Os nós de submix captam áudio de um ou mais nós e combinam-nos numa única saída que pode ser encaminhada para outros nós no gráfico.

Depois de todos os nós serem criados e as ligações entre eles estabelecidas, basta iniciar o gráfico de áudio e os dados de áudio fluem dos nós de entrada, através de quaisquer nós de submistura, para os nós de saída. Este modelo permite implementar, de forma rápida e fácil, cenários como gravar a partir do microfone de um dispositivo para um ficheiro de áudio, reproduzir áudio de um ficheiro através do altifalante do dispositivo ou misturar áudio de múltiplas fontes.

Cenários adicionais são ativados com a adição de efeitos de áudio ao gráfico de áudio. Cada nó num grafo de áudio pode ser preenchido com zero ou mais efeitos de áudio que efetuam o processamento do áudio que passa pelo nó. Existem vários efeitos incorporados, como eco, equalizador, limitação e reverberação, que podem ser ligados a um nó de áudio com apenas algumas linhas de código. Também podes criar os teus próprios efeitos de áudio personalizados que funcionam exatamente da mesma forma que os efeitos incorporados.

Escolher Windows Runtime AudioGraph ou XAudio2

As APIs Windows Runtime de grafos áudio oferecem funcionalidades que também podem ser implementadas utilizando as APIs XAudio2 baseadas em COM. As seguintes são funcionalidades do framework Windows Runtime Audio Graph que diferem do XAudio2.

As APIs de grafos de áudio do Windows Runtime:

  • São significativamente mais fáceis de usar do que o XAudio2.
  • Pode ser usado a partir de C# além de ser suportado para C++.
  • Pode usar ficheiros de áudio, incluindo formatos comprimidos, diretamente. O XAudio2 só funciona com buffers de áudio e não oferece capacidades de I/O de ficheiros.
  • Pode usar o pipeline de áudio de baixa latência no Windows.
  • Suporta a comutação automática de terminal quando são utilizados parâmetros predefinidos do terminal. Por exemplo, se o utilizador mudar do altifalante do dispositivo para um auscultador com microfone, o áudio é automaticamente redirecionado para a nova saída.

Classe AudioGraph

A classe AudioGraph é a classe base de todos os nós que compõem o grafo. Use este 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 as definições de configuração do grafo, e depois chamando AudioGraph.CreateAsync. O CreateAudioGraphResult retornado dá acesso ao grafo de áudio criado ou fornece um valor de erro caso a criação do grafo 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);
        return;
    }

    audioGraph = result.Graph;
}
  • Todos os tipos de nós de áudio são criados utilizando os métodos Create* da classe AudioGraph .

  • O método AudioGraph.Start faz com que o grafo de áudio comece a processar dados de áudio. O método AudioGraph.Stop impede o processamento de áudio. Cada nó no grafo pode ser iniciado e parado independentemente enquanto o grafo está a correr, mas nenhum nó está ativo quando o grafo é parado. ResetAllNodes faz com que todos os nós do grafo descartem quaisquer dados que se encontrem atualmente nos respetivos buffers de áudio.

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

  • A única propriedade AudioGraphSettings que é necessária é AudioRenderCategory. Especificar este valor permite ao sistema otimizar o pipeline de áudio para a categoria especificada.

  • O tamanho quântico do grafo de áudio determina o número de amostras processadas de cada vez. Por predefinição, o tamanho do quantum é de 10 ms à taxa de amostragem predefinida. Se especificar um tamanho quântico personalizado ao definir a propriedade DesiredSamplesPerQuantum, deve também definir a propriedade QuantumSizeSelectionMode como ClosestToDesired; caso contrário, o valor fornecido será ignorado. Se este valor for utilizado, o sistema escolherá um tamanho quântico o mais próximo possível daquele que especificar. Para determinar o tamanho quântico real, verifique o SamplesPerQuantum do AudioGraph após a sua criação.

  • Se planeia usar apenas o gráfico de áudio com ficheiros e não pretende exportar para um dispositivo de áudio, recomenda-se que use o tamanho quântico predefinido, não definindo a propriedade DesiredSamplesPerQuantum .

  • A propriedade DesiredRenderDeviceAudioProcessing determina a quantidade de processamento que o dispositivo principal de renderização realiza na saída do gráfico de áudio. A definição Default permite ao sistema usar o processamento de áudio predefinido para a categoria de renderização de áudio especificada. Este processamento pode melhorar significativamente o som do áudio em alguns dispositivos, particularmente em dispositivos móveis com colunas pequenas. A definição Raw pode melhorar o desempenho ao minimizar a quantidade de processamento de sinal realizada, mas pode resultar numa qualidade de som inferior em alguns dispositivos.

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

  • 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 superior a 1, o sistema deve alocar memória adicional para manter um buffer suficiente de dados de áudio. Por esta razão, definir o MaxPlaybackSpeedFactor para o valor mais baixo exigido pela sua aplicação reduzirá o consumo de memória da aplicação. Se a sua aplicação só reproduzir conteúdo a velocidade normal, recomenda-se que defina o MaxPlaybackSpeedFactor para 1.

  • EncodingProperties determina o formato de áudio utilizado pelo grafo. Apenas são suportados formatos float de 32 bits.

  • O PrimaryRenderDevice define o principal dispositivo de renderização para o gráfico de áudio. Se não definires isto, é usado o dispositivo de sistema predefinido. O dispositivo primário de renderização é usado para calcular os tamanhos quânticos de outros nós no grafo. Se não houver dispositivos de renderização áudio presentes no sistema, a criação de gráficos de áudio falhará.

Pode deixar que o gráfico de áudio utilize o dispositivo de renderização de áudio predefinido ou usar a classe Windows.Devices.Enumeration.DeviceInformation para obter uma lista dos dispositivos de saída de áudio disponíveis no sistema, chamando FindAllAsync e passando o seletor de dispositivo de renderização de áudio devolvido por Windows.Media.Devices.MediaDevice.GetAudioRenderSelector. Pode escolher programaticamente um dos objetos DeviceInformation retornados ou mostrar UI para permitir ao utilizador selecionar um dispositivo e depois 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 do dispositivo

Um nó de entrada de um dispositivo envia áudio para o grafo a partir de um dispositivo de captura de áudio ligado ao sistema, como um microfone. Crie um objeto DeviceInputNode que use o dispositivo de captura de áudio predefinido do sistema, chamando CreateDeviceInputNodeAsync. Especifique uma MediaCategory para que o sistema possa otimizar 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 quiseres especificar um dispositivo específico de captura de áudio para o nó de entrada do dispositivo, podes usar o 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 captura de áudio devolvido por Windows. Media.Devices.MediaDevice.GetAudioCaptureSelector. Pode escolher um dos objetos DeviceInformation devolvidos programaticamente ou mostrar UI para permitir ao utilizador selecionar um dispositivo e depois passá-lo para o 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 envia áudio do grafo para um dispositivo de reprodução de áudio, como colunas ou auscultadores. Crie um DeviceOutputNode chamando CreateDeviceOutputNodeAsync. O nó de saída utiliza o PrimaryRenderDevice do grafo 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 ficheiros

Um nó de entrada de ficheiro de áudio permite-lhe introduzir dados de um ficheiro de áudio no grafo. 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;
    WinRT.Interop.InitializeWithWindow.Initialize(filePicker, _hwnd);
    StorageFile file = await filePicker.PickSingleFileAsync();

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

    CreateAudioFileInputNodeResult result = await audioGraph.CreateFileInputNodeAsync(file);

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

    fileInputNode = result.FileInputNode;
}
  • Os nós de entrada de ficheiros suportam os seguintes formatos de ficheiro: mp3, wav, wma, m4a.
  • Defina a propriedade StartTime para especificar o deslocamento temporal no ficheiro onde a reprodução deve começar. Se esta propriedade for nula, utiliza-se o início do ficheiro. Defina a propriedade EndTime para especificar o deslocamento temporal no ficheiro onde a reprodução deve terminar. Se esta propriedade for nula, utiliza-se o final do ficheiro. O valor do tempo de início deve ser inferior ao valor do tempo final, e o valor do tempo final deve ser inferior ou igual à duração do ficheiro áudio, o que pode ser determinado verificando o valor da propriedade Duração .
  • Procure uma posição no ficheiro de áudio chamando Seek e especificando o deslocamento temporal no ficheiro para onde a posição de reprodução deve ser movida. O valor especificado deve estar dentro dos intervalos StartTime e EndTime . Obtenha a posição atual de reprodução do nó com a propriedade Posição de apenas leitura.
  • Ative o looping do ficheiro de áudio definindo a propriedade LoopCount . Quando não é nulo, este valor indica o número de vezes que o ficheiro será reproduzido após a reprodução inicial. Por exemplo, definir o LoopCount em 1 fará com que o ficheiro seja reproduzido 2 vezes no total, e definir 5 fará com que o ficheiro seja reproduzido 6 vezes no total. Definir o LoopCount para null faz com que o ficheiro fique repetido indefinidamente. Para parar a repetição, defina o valor como 0.
  • Ajuste a velocidade a que o ficheiro de áudio é reproduzido definindo o PlaybackSpeedFactor. Um valor de 1 indica a velocidade original do ficheiro, 0,5 é meia velocidade e 2 é velocidade dupla.

Nó de entrada de MediaSource

A classe MediaSource fornece uma forma comum de referenciar media de diferentes fontes e expõe um modelo comum para aceder a dados de media independentemente do formato subjacente, que pode ser um ficheiro em disco, um stream ou uma fonte de rede de streaming adaptativa. Um nó MediaSourceAudioInputNode permite-lhe direcionar dados de áudio de um MediaSource para o gráfico de áudio. Crie um MediaSourceAudioInputNode chamando CreateMediaSourceAudioInputNodeAsync, passando um objeto MediaSource que represente o conteúdo que pretende reproduzir. É devolvido um CreateMediaSourceAudioInputNodeResult que pode usar para determinar o estado da operação verificando a propriedade Status . Se o estado for Success, pode obter o MediaSourceAudioInputNode criado acedendo à propriedade Node. O exemplo seguinte mostra a criação de um nó a partir de um objeto AdaptiveMediaSource que representa o streaming de conteúdo através da rede.

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

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

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

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

        return;
    }

    mediaSourceInputNode = mediaSourceAudioInputNodeResult.Node;
}

Para receber uma notificação quando a reprodução atingir o fim do conteúdo de MediaSource, registe um processador para o evento MediaSourceCompleted.

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

Embora a reprodução de um ficheiro a partir de disco provavelmente seja sempre concluída com sucesso, os media transmitidos a partir de uma fonte de rede podem falhar durante a reprodução devido a uma alteração na ligação de rede ou outros problemas fora do controlo do gráfico de áudio. Se um MediaSource se tornar inutilizável durante a reprodução, o gráfico de áudio irá emitir o evento UnrecoverableErrorOccurred. Podes usar o handler deste evento para parar e eliminar o gráfico de áudio e depois reinicializar o teu gráfico.

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

Nó de saída de ficheiro

Um nó de saída de ficheiro permite-lhe encaminhar dados de áudio a partir do grafo para um ficheiro de áudio. Crie um AudioFileOutputNode chamando CreateFileOutputNodeAsync.

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

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

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

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

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

    fileOutputNode = result.FileOutputNode;
}

Nó de entrada do quadro de áudio

Um nó de entrada de trama de áudio permite-lhe injetar no grafo de áudio dados de áudio gerados no seu próprio código. Isto permite cenários como criar 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 é ativado quando o gráfico de áudio está pronto para começar a processar o próximo quantum de dados áudio. Fornece a este evento os seus dados de áudio gerados de forma personalizada a partir do handler.

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 no processador de eventos QuantumStarted expõe a propriedade RequiredSamples que indica quantas amostras o grafo áudio precisa para preencher o quantum a ser processado.
  • Chama AudioFrameInputNode.AddFrame para passar um objeto AudioFrame preenchido com dados de áudio para o gráfico.
  • Pode usar MediaFrameReader com dados de áudio para obter objetos AudioFrame a partir de uma origem de fotogramas multimédia, que podem ser passados para um FrameInputNode através do método AddFrame.
  • Um exemplo de implementação do método helper GenerateAudioData é mostrado abaixo.

Para preencher um AudioFrame com dados de áudio, deve aceder ao buffer de memória subjacente do frame de áudio. Para isso, inicialize a interface COM IMemoryBufferByteAccess conforme mostrado abaixo.

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

O código seguinte mostra um exemplo de implementação de um método auxiliar GenerateAudioData que cria um AudioFrame e preenche-o 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 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 este método acede ao buffer bruto subjacente aos tipos de Windows Runtime, deve ser declarado usando a palavra-chave unsafe. Também deve configurar o seu projeto em Microsoft Visual Studio para permitir a compilação de código inseguro abrindo a página Properties do projeto, clicando na página de propriedades Build e selecionando a caixa de seleção Permitir Código Inseguro.
  • Inicialize uma nova instância de AudioFrame, no Windows. Media namespace, passando o tamanho do buffer desejado ao construtor. O tamanho do buffer é o número de amostras multiplicado pelo tamanho de cada amostra.
  • Obtenha o AudioBuffer do frame de áudio chamando LockBuffer.
  • Obtenha uma instância da interface IMemoryBufferByteAccess COM a partir do buffer de áudio chamando o CreateReference.
  • Obtenha um ponteiro para os dados brutos do buffer de áudio chamando IMemoryBufferByteAccess.GetBuffer e transmita para o tipo de dado de amostra dos dados de áudio.
  • Preencha o buffer com dados e devolva o AudioFrame para submeter no grafo de áudio.

Nó de saída do quadro de áudio

Um nó de saída de quadro de áudio permite-lhe receber e processar dados de áudio provenientes do gráfico de áudio com código personalizado que cria. Um exemplo de cenário para isto é realizar uma 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 é ativado quando o gráfico áudio começa a processar um quantum de dados áudio. Pode aceder aos dados de áudio a partir do handler deste evento.

Note

Se quiser obter frames de áudio numa cadência regular, sincronizada com o gráfico de áudio, chame AudioFrameOutputNode.GetFrame no processador do evento síncrono QuantumStarted. O evento QuantumProcessed é levantado de forma assíncrona após o motor de áudio ter concluído o processamento de áudio, o que significa que a sua cadência pode ser irregular. Por isso, não deve usar o evento QuantumProcessed para processamento sincronizado de dados de frames 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.
  • Uma implementação exemplar do método helper ProcessFrameOutput é mostrada 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;
    }
}
  • Tal como no exemplo do nó de entrada do quadro de áudio acima, terá de declarar a interface COM IMemoryBufferByteAccess e configurar o seu projeto para permitir código inseguro para aceder ao buffer de áudio subjacente.
  • Obtenha o AudioBuffer do frame de áudio chamando LockBuffer.
  • Obtenha uma instância da interface IMemoryBufferByteAccess COM a partir do buffer de áudio chamando o CreateReference.
  • Obtenha um ponteiro para os dados brutos do buffer de áudio chamando IMemoryBufferByteAccess.GetBuffer e transmita para o tipo de dado de amostra dos dados de áudio.

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

Todos os tipos de nós de entrada expõem o método AddOutgoingConnection que encaminha o áudio produzido pelo nó para o nó que é passado para o método. O exemplo seguinte liga um AudioFileInputNode a um AudioDeviceOutputNode, numa configuração simples para reproduzir um ficheiro de áudio no altifalante do dispositivo.

fileInputNode.AddOutgoingConnection(deviceOutputNode);

Podes criar mais do que uma ligação de um nó de entrada para outros nós. O exemplo seguinte adiciona outra ligação do AudioFileInputNode para um AudioFileOutputNode. Agora, o áudio do ficheiro de áudio é reproduzido no altifalante do dispositivo e também é gravado num ficheiro de áudio.

fileInputNode.AddOutgoingConnection(fileOutputNode);

Os nós de saída também podem receber mais do que uma conexão de outros nós. No exemplo seguinte, estabelece-se uma ligação entre um nó AudioDeviceInputNode e o nó AudioDeviceOutput. Como o nó de saída tem ligações entre o nó de entrada do ficheiro e o nó de entrada 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 através da conexão.

deviceInputNode.AddOutgoingConnection(deviceOutputNode, .5);

Embora os nós de saída possam aceitar ligações de múltiplos nós, pode querer criar uma mistura intermédia de sinais de um ou mais nós antes de passar a mistura para uma saída. Por exemplo, pode querer definir o nível ou aplicar efeitos a um subconjunto dos sinais de áudio num gráfico. Para isso, use o AudioSubmixNode. Pode ligar um nó de submistura a partir de um ou mais nós de entrada ou de outros nós de submistura. No exemplo seguinte, cria-se um novo nó de submistura com AudioGraph.CreateSubmixNode. Depois, são adicionadas ligações de um nó de entrada de ficheiro e de um nó de entrada de frame para o nó de submix. Finalmente, o nó de submistura está ligado a um nó de saída para ficheiro.

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

Início e paragem de nós do grafo de áudio

Quando AudioGraph.Start é invocado, o grafo 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 de processar dados. Quando o AudioGraph.Stop é chamado, todo o processamento de áudio em todos os nós é interrompido independentemente do estado dos nós individuais, mas o estado de cada nó pode ser definido enquanto o gráfico de áudio está parado. Por exemplo, pode invocar Stop num nó específico enquanto o grafo está parado e, em seguida, invocar AudioGraph.Start, e o nó específico permanecerá no estado de paragem.

Todos os tipos de nós expõem a propriedade ConsumeInput que, quando definida como falsa, permite ao nó continuar o processamento de áudio, mas impede que consuma quaisquer dados de áudio introduzidos por outros nós.

Todos os tipos de nós disponibilizam o método Reset, que faz com que o nó descarte todos os dados de áudio presentes no buffer nesse momento.

Adição de efeitos áudio

A API de grafos de áudio permite-lhe adicionar efeitos de áudio a todos os tipos de nós num grafo. Os nós de saída, nós de entrada e nós de submix podem ter cada um um número ilimitado de efeitos áudio, limitados apenas pelas capacidades do hardware. O exemplo seguinte mostra como adicionar o efeito de eco integrado 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 áudio implementam IAudioEffectDefinition. Cada nó expõe uma propriedade EffectDefinitions que representa a lista de efeitos aplicados a esse nó. Adicione um efeito adicionando o seu objeto de definição à lista.
  • Existem várias classes de definição de efeitos fornecidas no espaço de nomes Windows.Media.Audio. Estas incluem:
  • Podes criar os teus próprios efeitos de áudio que implementam IAudioEffectDefinition e aplicá-los a qualquer nó num grafo de áudio.
  • Cada tipo de nó expõe um método DisableEffectsByDefinition que desativa todos os efeitos na lista EffectDefinitions do nó que foram adicionados usando a definição especificada. O EnableEffectsByDefinition permite os efeitos com a definição especificada.

Áudio espacial

O AudioGraph suporta áudio espacial, o que permite especificar a localização no espaço 3D a partir da qual é emitido o áudio de qualquer nó de entrada ou de submistura. Também pode especificar uma forma e uma direção segundo as quais o áudio é emitido, uma velocidade que será usada para aplicar o efeito Doppler ao áudio do nó e definir um modelo de atenuação que descreva como o áudio é atenuado em função da distância.

Para criar um emissor, pode-se primeiro criar uma forma em que 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 destas formas. De seguida, crie um modelo de decaimento. Isto 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 do som utilizando um modelo de atenuação proporcional ao quadrado da distância. Finalmente, crie um objeto AudioNodeEmitterSettings. Atualmente, este objeto é usado apenas para ativar e desativar a atenuação Doppler baseada em velocidade do áudio do emissor. Chama o construtor AudioNodeEmitter , passando os objetos de inicialização que acabaste de criar. Por defeito, o emissor está colocado na origem, mas pode definir a posição do emissor com a propriedade Posição .

Note

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

Atribuis o emissor a um nó de áudio quando o crias, usando o método de criação sobrecarregada para o tipo de nó que queres. Neste exemplo, CreateFileInputNodeAsync é utilizado para criar um nó de entrada de ficheiro a partir de um ficheiro especificado e do objeto AudioNodeEmitter que pretende 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 Vector3(10, 0, 5);

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

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

fileInputNode = result.FileInputNode;

O AudioDeviceOutputNode que envia áudio do grafo para o utilizador tem um objeto ouvinte, acedido com a propriedade Listener, que representa a localização, orientação e velocidade do utilizador 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 predefinição, o recetor encontra-se na origem (0,0,0), virado para a frente na direção do eixo Z, mas pode definir a respetiva posição e orientação com as propriedades Posição e Orientação.

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

Pode atualizar a localização, velocidade e direção dos emissores em tempo de execução para simular o movimento de uma fonte de áudio através do espaço 3D.

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

Também pode atualizar a localização, velocidade e orientação do objeto ouvinte em tempo de execução para simular o movimento do utilizador através do espaço 3D.

deviceOutputNode.Listener.Position = newUserPosition;

Por predefinição, o áudio espacial é calculado com recurso ao algoritmo de função de transferência relativa à cabeça (HRTF) da Microsoft para atenuar o áudio em função da sua forma, velocidade e posição em relação ao ouvinte. Pode definir a propriedade SpatialAudioModel para FoldDown para usar um método simples de mistura estéreo para simular áudio espacial, que é menos preciso mas requer menos recursos de CPU e memória.

Consulte também