Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este artigo mostra como usar as APIs no Windows. Media.Audio namespace para criar grafos de áudio para cenários de roteamento, combinação e processamento de áudio.
Um grafo 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 grafo de dispositivos de entrada de áudio, arquivos de áudio ou de código personalizado.
Nós de saída de áudio são o destino do áudio processado pelo grafo. O áudio pode ser roteado para fora do grafo para dispositivos de saída de áudio, arquivos de áudio ou código personalizado.
Os nós de submixagem recebem áudio de um ou mais nós e o combinam em uma única saída que pode ser encaminhada para outros nós no grafo.
Depois que todos os nós tiverem sido criados e as conexões entre eles configuradas, basta iniciar o grafo de áudio, e os dados de áudio fluem dos nós de entrada, passando por quaisquer nós de submix, até os nós de saída. Esse modelo torna cenários como gravar do microfone de um dispositivo para um arquivo de áudio, reproduzir áudio de um arquivo para o alto-falante de um dispositivo ou misturar áudio de várias fontes de maneira rápida e fácil de implementar.
Cenários adicionais são habilitados com a adição de efeitos de áudio ao grafo de áudio. Cada nó em um grafo de áudio pode ser preenchido com zero ou mais efeitos de áudio que executam o processamento de áudio no áudio que passa pelo nó. Há vários efeitos integrados, como eco, equalizador, limitador e reverb, que podem ser aplicados 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.
Escolhendo Windows Runtime AudioGraph ou XAudio2
As APIs de grafo de áudio Windows Runtime oferecem funcionalidade que também pode ser implementada usando as APIs XAudio2 baseadas em COM. Veja a seguir os recursos da estrutura de grafo de áudio Windows Runtime que diferem do XAudio2.
As APIs do grafo de áudio do Windows Runtime:
- São significativamente mais fáceis de usar do que XAudio2.
- Pode ser usado do C# além de ter suporte para C++.
- Pode usar arquivos de áudio, incluindo formatos de arquivo compactados, diretamente. O XAudio2 opera apenas em buffers de áudio e não fornece recursos de E/S de arquivo.
- Pode usar o pipeline de áudio de baixa latência em Windows.
- Dê suporte à alternância automática de ponto de extremidade quando os parâmetros de ponto de extremidade padrão forem usados. Por exemplo, se o usuário alternar do alto-falante de um dispositivo para um headset, o áudio será redirecionado automaticamente 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 que contém as configurações de configuração do grafo, e, em seguida, chamando AudioGraph.CreateAsync. O CreateAudioGraphResult retornado fornece acesso ao grafo de áudio criado ou retorna um valor de erro se 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ó de áudio são criados usando 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 interrompe o processamento de áudio. Cada nó no grafo pode ser iniciado e interrompido de forma independente enquanto o grafo está em execução, mas nenhum nó está ativo quando o grafo é interrompido. ResetAllNodes faz com que todos os nós no grafo descartem todos os dados atualmente em seus buffers de áudio.
O evento QuantumStarted ocorre quando o grafo 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 quântico do grafo de áudio determina o número de amostras que são processadas ao mesmo tempo. Por padrão, o tamanho quântico é de 10 ms com base na taxa de exemplo padrão. Se você especificar um tamanho quântico personalizado definindo a propriedade DesiredSamplesPerQuantum , também deverá definir a propriedade QuantumSizeSelectionMode como ClosestToDesired ou o valor fornecido será ignorado. Se esse valor for usado, o sistema escolherá um tamanho quântico o mais próximo possível do que você especificar. Para determinar o tamanho quântico real, verifique o SamplesPerQuantum do AudioGraph depois que ele for criado.
Se você planeja usar apenas o grafo de áudio com arquivos e não pretende enviar a saída para um dispositivo de áudio, é recomendável usar o tamanho quântico padrão sem definir a propriedade DesiredSamplesPerQuantum.
A propriedade DesiredRenderDeviceAudioProcessing determina a quantidade de processamento que o dispositivo de renderização primária executa na saída do grafo de áudio. A configuração Padrão 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, especialmente 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 QuantumSizeSelectionMode estiver definido como LowestLatency, o grafo de áudio usará automaticamente Raw para DesiredRenderDeviceAudioProcessing.
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 grafo de áudio dá 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, definir MaxPlaybackSpeedFactor como o menor valor exigido pelo aplicativo reduzirá o consumo de memória do aplicativo. Se o aplicativo só reproduzir conteúdo em velocidade normal, é recomendável que você defina MaxPlaybackSpeedFactor como 1.
As EncodingProperties determinam o formato de áudio usado pelo grafo. Há suporte apenas para formatos float de 32 bits.
O PrimaryRenderDevice define o dispositivo de renderização primário para o grafo 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 quânticos para outros nós no grafo. Se não houver dispositivos de renderização de áudio presentes no sistema, a criação do grafo de áudio falhará.
Você pode permitir que o grafo 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. 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, 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 dispositivo insere áudio no grafo 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 MediaCategory 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, poderá usar a classe Windows.Devices.Enumeration.DeviceInformation para obter uma lista dos dispositivos de captura de áudio disponíveis no sistema chamando FindAllAsync e passando o seletor de dispositivo de captura 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, 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 de dispositivo encaminha o áudio do grafo para um dispositivo de renderização de áudio, como alto-falantes ou um headset. Crie um DeviceOutputNode chamando CreateDeviceOutputNodeAsync. O nó de saída usa 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 do arquivo
Um nó de entrada de arquivo permite que você alimente dados de um arquivo 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 arquivo dão suporte aos 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 final do arquivo será usado. O valor da hora de início deve ser menor que o valor da hora de término e o valor da hora de término deve ser menor ou igual à duração do arquivo de áudio, o que pode ser determinado verificando o valor da propriedade Duration .
- Procure uma posição no arquivo de áudio chamando Seek e especificando o deslocamento de tempo para o 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. Portanto, 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 seis vezes no total. Definir LoopCount como nulo faz com que o arquivo seja loopado indefinidamente. Para interromper a repetição, defina o valor como 0.
- Ajuste a velocidade em que o arquivo de áudio é reproduzido definindo o PlaybackSpeedFactor. Um valor de 1 indica a velocidade original do arquivo, .5 é de meia velocidade e 2 é de velocidade dupla.
Nó de entrada 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 em disco, um fluxo ou uma fonte de rede de streaming adaptável. Um nó MediaSourceAudioInputNode permite encaminhar dados de áudio de uma MediaSource para o grafo de áudio. Crie um MediaSourceAudioInputNode chamando CreateMediaSourceAudioInputNodeAsync, passando um objeto MediaSource que representa o conteúdo que você deseja reproduzir. Um CreateMediaSourceAudioInputNodeResult é retornado, que você pode usar para determinar o status da operação verificando a propriedade Status . Se o status for Success, você poderá obter o MediaSourceAudioInputNode criado ao acessar a propriedade Node. O exemplo a seguir mostra a criação de um nó a partir de um objeto AdaptiveMediaSource que representa a transmissão de conteúdo pela 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 final 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();
}
Embora a reprodução de um arquivo do disco provavelmente sempre seja 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 grafo de áudio. Se um MediaSource não puder mais ser reproduzido durante a reprodução, o grafo de áudio disparará o evento UnrecoverableErrorOccurred. Você pode usar o manipulador desse evento para parar e descartar o grafo de áudio e, em seguida, reinicializar seu grafo.
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 do arquivo
Um nó de saída de arquivo permite direcionar dados de áudio do grafo 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 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;
}
- Os nós de saída do arquivo dão suporte aos seguintes formatos de arquivo: mp3, wav, wma, m4a.
- Você deve chamar AudioFileOutputNode.Stop para interromper o processamento do nó antes de chamar AudioFileOutputNode.FinalizeAsync ou uma exceção será gerada.
Nó de entrada do quadro de áudio
Um nó de entrada de frame de áudio permite que você insira no grafo de áudio os dados de áudio que você gera no seu próprio código. 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 grafo 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 objeto QuantumStarted manipulador de eventos expõe a propriedade RequiredSamples que indica quantos exemplos o grafo de áudio precisa para preencher o quantum a ser processado.
- Chame AudioFrameInputNode.AddFrame para passar um objeto AudioFrame preenchido com dados de áudio no grafo.
- Você pode usar MediaFrameReader com dados de áudio para obter objetos AudioFrame de uma fonte de quadro de mídia, que pode ser passada para um FrameInputNode usando o método AddFrame .
- 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, 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 a seguir mostra uma implementação de exemplo de um método auxiliar GenerateAudioData que cria um método auxiliar AudioFrame e os popula 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 esse método acessa o buffer bruto subjacente aos tipos de Windows Runtime, ele deve ser declarado usando a palavra-chave unsafe. Você também deve configurar seu projeto em Microsoft Visual Studio para permitir a compilação de código não seguro abrindo a página Properties do projeto, clicando na página de propriedades Build e selecionando a caixa de seleção Allow Unsafe Code.
- Inicialize uma nova instância do AudioFrame, no Windows. Mídia namespace, passando o tamanho do buffer desejado para o construtor. O tamanho do buffer é o número de exemplos multiplicados 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 os dados brutos do buffer de áudio chamando IMemoryBufferByteAccess.GetBuffer e faça a conversão dele para o tipo de dados de amostra dos dados de áudio.
- Preencha o buffer com dados e retorne o AudioFrame para envio ao grafo de áudio.
Nó de saída do quadro de áudio
Um nó de saída de quadros de áudio permite que você receba e processe dados de áudio gerados pelo grafo de áudio com código personalizado que você criar. Um cenário de exemplo para isso é executar a 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 grafo 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.
Note
Se você quiser recuperar quadros de áudio em uma cadência regular, sincronizada com o grafo de áudio, chame AudioFrameOutputNode.GetFrame de dentro do manipulador de eventos QuantumStarted síncrono. O evento QuantumProcessed é gerado de forma assíncrona após o mecanismo de áudio concluir 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 grafo.
- 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;
}
}
- Assim como no exemplo acima do nó de entrada de quadro de áudio, você precisará declarar a interface COM IMemoryBufferByteAccess e configurar seu projeto para permitir código inseguro 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 de buffer de áudio chamando IMemoryBufferByteAccess.GetBuffer e converta-os no tipo de dados de exemplo dos dados de áudio.
Conexões entre nós e nós de submix
Todos os tipos de nó de entrada expõem o método AddOutgoingConnection que roteia o áudio produzido pelo nó para o nó 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 a partir 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);
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 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, talvez você queira 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, talvez você queira definir o nível ou aplicar efeitos a um subconjunto dos sinais de áudio em um grafo. Para fazer isso, use o AudioSubmixNode. Você pode se conectar a um nó de submix a partir de um ou mais nós de entrada ou de outros nós de submix. No exemplo a seguir, um novo nó de submix é criado com AudioGraph.CreateSubmixNode. Em seguida, as conexões são adicionadas de um nó de entrada de arquivo e de um nó de entrada de quadro ao nó de submix. Por fim, o nó de submix está conectado a um nó de saída para arquivo.
private void CreateSubmixNode()
{
AudioSubmixNode submixNode = audioGraph.CreateSubmixNode();
fileInputNode.AddOutgoingConnection(submixNode);
frameInputNode.AddOutgoingConnection(submixNode);
submixNode.AddOutgoingConnection(fileOutputNode);
}
Iniciando e interrompendo nós de grafo de áudio
Quando AudioGraph.Start é chamado, 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 interrompa o processamento de dados. Quando 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 grafo de áudio é interrompido. Por exemplo, você pode chamar Stop em um nó específico enquanto o grafo está parado e, em seguida, chamar AudioGraph.Start, e esse nó específico permanecerá no estado parado.
Todos os tipos de nó expõem a propriedade ConsumInput que, quando definida como false, permite que o nó continue o processamento de áudio, mas impede que ele consuma os 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.
Adicionando efeitos de áudio
A API de grafo de áudio permite adicionar efeitos de áudio a todos os tipos de nós em um grafo. Nós de saída, nós de entrada e nós de submix podem ter um número ilimitado de efeitos de áudio, limitados apenas pelos recursos do hardware. O exemplo a seguir mostra como adicionar o efeito de eco integrado a um nó de submix.
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 adicionando seu objeto de definição à lista.
- Há várias classes de definição de efeitos fornecidas no namespace Windows.Media.Audio. Estes incluem:
- Você pode criar seus próprios efeitos de áudio que implementam IAudioEffectDefinition e aplicá-los a qualquer nó em um grafo 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
AudioGraph oferece suporte a áudio espacial, o que permite especificar a posição no espaço 3D de onde é emitido o áudio de qualquer nó de entrada ou nó de submix. Você também pode especificar um formato e uma direção em que 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 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 omnidirecional. A classe AudioNodeEmitterShape fornece métodos estáticos para criar cada uma dessas formas. Em seguida, crie um modelo de decadência. 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 atenuação que emula a atenuação natural do som usando um modelo de atenuação proporcional ao quadrado da distância. Por fim, crie um objeto AudioNodeEmitterSettings. Atualmente, esse objeto é usado apenas para habilitar e desabilitar a atenuação do Doppler baseada em velocidade do áudio do emissor. Chame o construtor AudioNodeEmitter , passando 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 .
Note
Os emissores de nó de áudio apenas podem processar áudio no formato mono com uma taxa de amostragem de 48 kHz. Tentar usar áudio estéreo ou áudio com uma taxa de amostragem diferente gerará 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ó que você deseja. 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 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 o áudio do grafo ao usuário tem um objeto listener, acessado por meio da propriedade Listener, que representa a posiçã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) voltada para frente ao longo do eixo Z, mas você pode definir sua 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);
Você pode atualizar o local, a velocidade e a direção dos emissores em runtime para simular o movimento de uma fonte de áudio por meio do espaço 3D.
AudioNodeEmitter emitter = fileInputNode.Emitter;
emitter.Position = newObjectPosition;
emitter.DopplerVelocity = newObjectPosition - oldObjectPosition;
Você também pode atualizar o local, a velocidade e a orientação do objeto ouvinte em runtime 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 combinação estéreo simples de simular áudio espacial menos preciso, mas que requer menos recursos de CPU e memória.
Consulte também
Windows developer