Gráficos de audio

En este artículo se muestra cómo usar las API en el espacio de nombres Windows.Media.Audio para crear gráficos de audio para escenarios de enrutamiento, mezcla y procesamiento de audio.

Un gráfico de audio es un conjunto de nodos de audio interconectados a través de los cuales fluyen los datos de audio.

  • Los nodos de entrada de audio proporcionan datos de audio al grafo desde dispositivos de entrada de audio, archivos de audio o desde código personalizado. lat

  • Los nodos de salida de audio son el destino para el audio procesado por el gráfico. El audio del gráfico se puede enrutar a dispositivos de salida de audio, archivos de audio o código personalizado.

  • Los nodos de submezcla toman audio desde uno o varios nodos y lo combinan en una sola salida que puede enrutarse a otros nodos del gráfico.

Después de que se hayan creado todos los nodos y se hayan configurado las conexiones entre ellos, simplemente inicia el gráfico de audio y los datos de audio fluirán desde los nodos de entrada a través de los nodos de submezcla hasta llegar a los nodos de salida. Este modelo permite escenarios como la grabación desde el micrófono de un dispositivo a un archivo de audio, la reproducción de audio desde un archivo al altavoz de un dispositivo o la mezcla de audio desde varias fuentes de forma rápida y fácil de implementar.

Se habilitan escenarios adicionales con la incorporación de efectos de audio al gráfico de audio. Todos los nodos de un gráfico de audio se pueden rellenar con cero o más efectos de audio que realizan el procesamiento de audio en el audio que pasa a través del nodo. Existen varios efectos integrados como eco, ecualizador, limitación y reverberación que se pueden anexar a un nodo de audio con unas pocas líneas de código. También puedes crear tus propios efectos de audio personalizados que funcionan exactamente igual que los efectos integrados.

Nota

La muestra de AudioGraph para UWP implementa el código analizado en esta introducción. Puedes descargar la muestra para ver el código en contexto o para usarla como punto de partida para tu propia aplicación.

Selección de AudioGraph o XAudio2 de Windows Runtime

Las API de gráfico de audio de Windows Runtime ofrecen una funcionalidad que se puede implementar mediante las XAudio2 APIs (API de XAudio2) basadas en COM. Las siguientes son características del marco de gráficos de audio de Windows Runtime que difieren de XAudio2.

API de gráficos de audio de Windows Runtime:

  • Son significativamente más fáciles de usar que XAudio2.
  • Pueden usarse desde C#, además de ser compatibles con C++.
  • Pueden usar archivos de audio directamente, incluidos formatos de archivo comprimido. XAudio2 solo funciona en búferes de audio y no proporciona ninguna funcionalidad de E/S de archivo.
  • Puedes usar la canalización de audio de latencia baja de Windows 10.
  • Admite la conmutación automática de los extremos cuando se usan los parámetros de extremo predeterminados. Por ejemplo, si el usuario cambia del altavoz de un dispositivo a unos auriculares, el audio se redirige automáticamente a la entrada nueva.

Clase AudioGraph

La clase AudioGraph es el elemento primario de todos los nodos que conforman el gráfico. Usa este objeto para crear instancias de todos los tipos de nodo de audio. Crea una instancia de la clase AudioGraph inicializando un objeto AudioGraphSettings que contenga opciones de configuración para el gráfico y, a continuación, llama a AudioGraph.CreateAsync. El valor CreateAudioGraphResult devuelto proporciona acceso a un gráfico de audio creado o proporciona un valor de error si se produce un error en la creación del gráfico de audio.

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 los tipos de nodo de audio se crean mediante los métodos Create* de la clase AudioGraph .

  • El método AudioGraph.Start hace que el gráfico de audio comience a procesar datos de audio. El método AudioGraph.Stop detiene el procesamiento de audio. Cada nodo del gráfico se puede iniciar y detener de forma independiente mientras se está ejecutando el gráfico, pero ningún nodo está activo cuando se detiene el gráfico. ResetAllNodes hace que todos los nodos del gráfico descarten cualquier dato almacenado actualmente en sus búferes de audio.

  • El evento QuantumStarted se produce cuando el gráfico está iniciando el procesamiento de un nuevo cuanto de datos de audio. El evento QuantumProcessed se produce cuando se completa el procesamiento de un cuanto.

  • La única propiedad AudioGraphSettings requerida es AudioRenderCategory. La especificación de este valor permite que el sistema optimice la canalización de audio para la categoría especificada.

  • El tamaño del cuanto del gráfico de audio determina el número de muestras que se procesan a la vez. De manera predeterminada, el tamaño del cuanto es 10 ms, basado en la velocidad de muestra predeterminada. Si se especifica un tamaño de cuanto personalizado estableciendo la propiedad DesiredSamplesPerQuantum, también debes establecer la propiedad QuantumSizeSelectionMode en ClosestToDesired o se omitirá el valor proporcionado. Si se usa este valor, el sistema elige un tamaño de cuanto lo más cerca posible al especificado. Para determinar el tamaño real del cuanto, comprueba SamplesPerQuantum de AudioGraph después de que se haya creado.

  • Si solo vas a usar el gráfico de audio con los archivos y no pretendes una salida a un dispositivo de audio, se recomienda que uses el tamaño de cuanto predeterminado no estableciendo la propiedad DesiredSamplesPerQuantum.

  • La propiedad DesiredRenderDeviceAudioProcessing determina la cantidad de procesamiento que realiza el dispositivo de representación principal en la salida del gráfico de audio. La configuración Default permite que el sistema use el procesamiento de audio predeterminado para la categoría de representación de audio especificada. Este procesamiento puede mejorar considerablemente el sonido de audio en algunos dispositivos, especialmente en los dispositivos móviles con altavoces pequeños. La configuración Raw puede mejorar el rendimiento al reducir la cantidad de procesamiento de señal realizado, pero puede producir una calidad de sonido inferior en algunos dispositivos.

  • Si QuantumSizeSelectionMode está establecido en LowestLatency, el gráfico de audio usará automáticamente Raw para DesiredRenderDeviceAudioProcessing.

  • A partir de Windows 10, versión 1803, puedes establecer la propiedad AudioGraphSettings.MaxPlaybackSpeedFactor para establecer un valor máximo usado para las propiedades AudioFileInputNode.PlaybackSpeedFactor, AudioFrameInputNode.PlaybackSpeedFactor y MediaSourceInputNode.PlaybackSpeedFactor . Cuando un gráfico de audio admite un factor de velocidad de reproducción mayor que 1, el sistema debe asignar memoria adicional para mantener un búfer suficiente de datos de audio. Por este motivo, al establecer MaxPlaybackSpeedFactor en el valor más bajo requerido por la aplicación, se reducirá el consumo de memoria de la aplicación. Si la aplicación solo reproducirá contenido a velocidad normal, se recomienda establecer MaxPlaybackSpeedFactor en 1.

  • EncodingProperties determina el formato de audio usado por el gráfico. Se admiten los formatos flotantes de 32 bits únicamente.

  • PrimaryRenderDevice establece el dispositivo de representación principal para el gráfico de audio. Si no estableces esto, se usa el dispositivo predeterminado del sistema. El dispositivo de representación principal se usa para calcular los tamaños de cuanto de otros nodos en el gráfico. Si no hay ningún dispositivo de representación de audio presente en el sistema, se producirá un error en la creación del gráfico de audio.

Puedes permitir que el gráfico de audio use el dispositivo de representación de audio predeterminado o que use la clase Windows.Devices.Enumeration.DeviceInformation para obtener una lista dispositivos de representación de audio disponibles del sistema mediante una llamada a FindAllAsync y pasando el selector de dispositivos de representación de audio devuelto por Windows.Media.Devices.MediaDevice.GetAudioRenderSelector. Puedes elegir uno de los objetos DeviceInformation devueltos mediante programación o mostrar la interfaz de usuario para que el usuario pueda seleccionar un dispositivo y, a continuación, usarlo para establecer la propiedad PrimaryRenderDevice.

Windows.Devices.Enumeration.DeviceInformationCollection devices =
 await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(Windows.Media.Devices.MediaDevice.GetAudioRenderSelector());

// Show UI to allow the user to select a device
Windows.Devices.Enumeration.DeviceInformation selectedDevice = ShowMyDeviceSelectionUI(devices);


settings.PrimaryRenderDevice = selectedDevice;

Nodo de entrada de dispositivo

Un nodo de entrada de dispositivo envía audio al gráfico desde un dispositivo de captura de audio conectado al sistema, como un micrófono. Crea un objeto DeviceInputNode que use el dispositivo de captura de audio predeterminado del sistema mediante una llamada a CreateDeviceInputNodeAsync. Proporciona una AudioRenderCategory para permitir que el sistema optimice la canalización de audio para la categoría 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;
}

Si quiere especificar un dispositivo de captura de audio específico para el nodo de entrada del dispositivo, puede usar la clase Windows.Devices.Enumeration.DeviceInformation para obtener una lista de los dispositivos de captura de audio disponibles del sistema llamando a FindAllAsync y pasando el selector de dispositivos de representación de audio devuelto por Windows.Media.Devices.MediaDevice.GetAudioCaptureSelector. Puede elegir uno de los objetos DeviceInformation devueltos mediante programación o mostrar la interfaz de usuario para permitir al usuario seleccionar un dispositivo y, a continuación, pasarlo a CreateDeviceInputNodeAsync.

Windows.Devices.Enumeration.DeviceInformationCollection devices =
 await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(Windows.Media.Devices.MediaDevice.GetAudioCaptureSelector());

// Show UI to allow the user to select a device
Windows.Devices.Enumeration.DeviceInformation selectedDevice = ShowMyDeviceSelectionUI(devices);

CreateAudioDeviceInputNodeResult result =
    await audioGraph.CreateDeviceInputNodeAsync(Windows.Media.Capture.MediaCategory.Media, audioGraph.EncodingProperties, selectedDevice);

Nodo de dispositivo de salida

Un nodo de dispositivo de salida envía audio del gráfico a un dispositivo de representación de audio, como los altavoces o auriculares. Crea un DeviceOutputNode llamando a CreateDeviceOutputNodeAsync. El nodo de salida usa el PrimaryRenderDevice del gráfico de audio.

AudioDeviceOutputNode deviceOutputNode;
private async Task CreateDeviceOutputNode()
{
    // Create a device output node
    CreateAudioDeviceOutputNodeResult result = await audioGraph.CreateDeviceOutputNodeAsync();

    if (result.Status != AudioDeviceNodeCreationStatus.Success)
    {
        // Cannot create device output node
        ShowErrorMessage(result.Status.ToString());
        return;
    }

    deviceOutputNode = result.DeviceOutputNode;
}

Nodo de entrada de archivo

Un nodo de entrada de archivo te permite enviar datos desde un archivo de audio al gráfico. Crea un AudioFileInputNode llamando a 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;
}
  • Los nodos de entrada de archivos admiten los siguientes formatos de archivo: mp3, wav, wma y m4a.
  • Establece la propiedad StartTime para especificar el desplazamiento de tiempo en el archivo donde debe comenzar la reproducción. Si esta propiedad es null, se usa el comienzo del archivo. Establece la propiedad EndTime para especificar el desplazamiento de tiempo en el archivo donde debe finalizar la reproducción. Si esta propiedad es null, se usa el final del archivo. El valor de tiempo de inicio debe ser menor que el valor de tiempo final y el valor de hora final debe ser menor o igual a la duración del archivo de audio, que puede determinarse comprobando el valor de la propiedad Duration.
  • Busca una posición en el archivo de audio llamando a Seek y especificando el desplazamiento de tiempo en el archivo al que se moverá la posición de reproducción. El valor especificado no debe superar el rango de StartTime y EndTime. Obtén la posición de reproducción actual del nodo con la propiedad Position de solo lectura.
  • Habilita la función de repetición del archivo de audio estableciendo la propiedad LoopCount. Cuando no sea null, este valor indica el número de veces que el archivo se reproducirá después de la reproducción inicial. Por lo tanto, por ejemplo, establecer LoopCount en 1 hará que el archivo se reproduzca 2 veces en total y, si se establece en 5, hará que el archivo que se reproduzca 6 veces en total. Establecer LoopCount en null hace que el archivo se repita indefinidamente. Para detener la repetición, establece este valor en 0.
  • Ajusta la velocidad a la que el archivo de audio se reproduce estableciendo PlaybackSpeedFactor. Un valor de 1 indica la velocidad original del archivo, .5 es la velocidad media y 2 es el doble de velocidad.

Nodo de entrada mediaSource

La clase MediaSource proporciona una manera común de hacer referencia a medios de diferentes orígenes y expone un modelo común para acceder a datos multimedia independientemente del formato multimedia subyacente que podría ser un archivo en disco, una secuencia o un origen de red de streaming adaptable. Un nodo **MediaSourceAudioInputNode permite dirigir los datos de audio de un objeto MediaSource al grafo de audio. Cree un objeto MediaSourceAudioInputNode llamando a CreateMediaSourceAudioInputNodeAsync, pasando un objeto MediaSource que represente el contenido que desea reproducir. Se devuelve un **CreateMediaSourceAudioInputNodeResult que puede usar para determinar el estado de la operación comprobando la propiedad Status . Si el estado es Correcto, puede obtener el objeto MediaSourceAudioInputNode creado mediante el acceso a la propiedad Node . En el ejemplo siguiente se muestra la creación de un nodo a partir de un objeto AdaptiveMediaSource que representa el streaming de contenido a través de la red. Para obtener más información sobre cómo trabajar con MediaSource, consulte Elementos multimedia, listas de reproducción y pistas. Para obtener más información sobre el streaming de contenido multimedia a través de Internet, consulte Streaming adaptable.

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 recibir una notificación cuando la reproducción haya llegado al final del contenido de MediaSource , registre un controlador para el evento MediaSourceCompleted .

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

Mientras se reproduce un archivo desde un disco, es probable que siempre se complete correctamente, los medios transmitidos desde un origen de red pueden producir un error durante la reproducción debido a un cambio en la conexión de red u otros problemas que están fuera del control del gráfico de audio. Si mediaSource se vuelve reproducible durante la reproducción, el gráfico de audio generará el evento UnrecoverableErrorOccurred . Puede usar el controlador para este evento para detener y eliminar el grafo de audio y, a continuación, reinicializar el grafo.

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

Nodo de salida de archivo

Un nodo de salida de archivo permite dirigir datos de audio desde el gráfico a un archivo de audio. Crea un AudioFileOutputNode llamando a CreateFileOutputNodeAsync.

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

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

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


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

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

    fileOutputNode = result.FileOutputNode;
}

Nodo de entrada de fotogramas de audio

Un nodo de entrada de fotogramas de audio permite insertar datos de audio que se generan en su propio código en el gráfico de audio. Esto permite escenarios como crear un sintetizador de software personalizado. Cree un AudioFrameInputNode llamando a 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;
}

El evento FrameInputNode.QuantumStarted se genera cuando el gráfico de audio está listo para empezar a procesar el siguiente cuanto de datos de audio. Proporcionas los datos de audio generados de forma personalizada desde dentro del controlador a este 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);
    }
}
  • El objeto FrameInputNodeQuantumStartedEventArgs pasado al controlador de eventos QuantumStarted expone la propiedad RequiredSamples que indica cuántas muestras el gráfico de audio necesita para rellenar el cuanto para su procesamiento.
  • Realiza una llamada a AudioFrameInputNode.AddFrame para pasar un objeto AudioFrame rellenado con datos de audio al gráfico.
  • Se introdujo un nuevo conjunto de API para usar MediaFrameReader con datos de audio en Windows 10, versión 1803. Estas API permiten obtener objetos AudioFrame de un origen de fotogramas multimedia, que se pueden pasar a frameInputNode mediante el método AddFrame . Para obtener más información, vea Procesar fotogramas de audio con MediaFrameReader.
  • Se muestra a continuación un ejemplo de implementación del método auxiliar GenerateAudioData.

Para rellenar un AudioFrame con datos de audio, debes obtener acceso al búfer de memoria subyacente del fotograma de audio. Para ello, debes inicializar la interfaz COM IMemoryBufferByteAccess agregando el siguiente código dentro de tu espacio de nombres.

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

El siguiente código muestra un ejemplo de implementación de un método auxiliar GenerateAudioData que crea un AudioFrame y se rellena con datos de audio.

private double audioWaveTheta = 0;

unsafe private AudioFrame GenerateAudioData(uint samples)
{
    // Buffer size is (number of samples) * (size of each sample)
    // We choose to generate single channel (mono) audio. For multi-channel, multiply by number of channels
    uint bufferSize = samples * sizeof(float);
    AudioFrame frame = new Windows.Media.AudioFrame(bufferSize);

    using (AudioBuffer buffer = frame.LockBuffer(AudioBufferAccessMode.Write))
    using (IMemoryBufferReference reference = buffer.CreateReference())
    {
        byte* dataInBytes;
        uint capacityInBytes;
        float* dataInFloat;

        // Get the buffer from the AudioFrame
        ((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacityInBytes);

        // Cast to float since the data we are generating is float
        dataInFloat = (float*)dataInBytes;

        float freq = 1000; // choosing to generate frequency of 1kHz
        float amplitude = 0.3f;
        int sampleRate = (int)audioGraph.EncodingProperties.SampleRate;
        double sampleIncrement = (freq * (Math.PI * 2)) / sampleRate;

        // Generate a 1kHz sine wave and populate the values in the memory buffer
        for (int i = 0; i < samples; i++)
        {
            double sinValue = amplitude * Math.Sin(audioWaveTheta);
            dataInFloat[i] = (float)sinValue;
            audioWaveTheta += sampleIncrement;
        }
    }

    return frame;
}
  • Como este método tiene acceso el búfer sin procesar subyacente a los tipos de Windows Runtime, se debe declarar con la palabra clave unsafe. También debes configurar el proyecto en Microsoft Visual Studio para permitir la compilación del código no seguro; para ello, abre la página Propiedades del proyecto, haz clic en la página de propiedades de Compilación y selecciona la casilla Permitir código no seguro.
  • Inicializa una nueva instancia de AudioFrame en el espacio de nombres Windows.Media pasando el tamaño del búfer deseado al constructor. El tamaño de búfer es el número de muestras multiplicado por el tamaño de cada muestra.
  • Obtén el AudioBuffer de la trama de audio llamando a LockBuffer.
  • Obtenga una instancia de la interfaz COM IMemoryBufferByteAccess desde el búfer de audio mediante una llamada a CreateReference.
  • Obtén un puntero para los datos del búfer de audio sin procesar mediante una llamada a IMemoryBufferByteAccess.GetBuffer y conviértelo en el tipo de datos de muestra de los datos de audio.
  • Rellena el búfer con datos y devuelve el AudioFrame para el envío en el gráfico de audio.

Nodo de salida de fotogramas de audio

Un nodo de salida de fotogramas de audio permite recibir y procesar la salida de datos de audio desde el gráfico de audio con código personalizado que crees. Un escenario de ejemplo para esto es la realización de análisis de señal en la salida de audio. Crea un AudioFrameOutputNode llamando a CreateFrameOutputNode.

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

El evento AudioGraph.QuantumStarted se genera cuando el gráfico de audio comienza a procesar un cuanto de datos de audio. Puedes obtener acceso a los datos de audio desde dentro del controlador para este evento.

Nota

Si desea recuperar fotogramas de audio en una cadencia normal, sincronizada con el gráfico de audio, llame a AudioFrameOutputNode.GetFrame desde el controlador de eventos QuantumStarted sincrónico. El evento QuantumProcessed se genera de forma asincrónica después de que el motor de audio haya completado el procesamiento de audio, lo que significa que su cadencia puede ser irregular. Por lo tanto, no debe usar el evento QuantumProcessed para el procesamiento sincronizado de datos de fotogramas de audio.

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

}
  • Realiza una llamada a GetFrame para obtener un objeto AudioFrame rellenado con datos de audio del gráfico.
  • Se muestra a continuación un ejemplo de implementación del método auxiliar ProcessFrameOutput.
unsafe private void ProcessFrameOutput(AudioFrame frame)
{
    using (AudioBuffer buffer = frame.LockBuffer(AudioBufferAccessMode.Write))
    using (IMemoryBufferReference reference = buffer.CreateReference())
    {
        byte* dataInBytes;
        uint capacityInBytes;
        float* dataInFloat;

        // Get the buffer from the AudioFrame
        ((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacityInBytes);

        dataInFloat = (float*)dataInBytes;
    }
}
  • Al igual que el ejemplo de nodo de entrada de fotogramas de audio anterior, tendrás que declarar la interfaz COM IMemoryBufferByteAccess y configurar el proyecto para permitir que el código no seguro tenga acceso al búfer subyacente de audio.
  • Obtén el AudioBuffer de la trama de audio llamando a LockBuffer.
  • Obtenga una instancia de la interfaz COM IMemoryBufferByteAccess desde el búfer de audio mediante una llamada a CreateReference.
  • Obtén un puntero para los datos del búfer de audio sin procesar mediante una llamada a IMemoryBufferByteAccess.GetBuffer y conviértelo en el tipo de datos de muestra de los datos de audio.

Conexiones de nodo y nodos de submezcla

Todos los tipos de nodos de entrada exponen el método AddOutgoingConnection que enruta el audio generado por el nodo al que se pasa al método. El siguiente ejemplo conecta un AudioFileInputNode a un AudioDeviceOutputNode, que es una configuración simple para reproducir un archivo de audio en el altavoz del dispositivo.

fileInputNode.AddOutgoingConnection(deviceOutputNode);

Puedes crear más de una conexión desde un nodo de entrada a otros nodos. El siguiente ejemplo agrega otra conexión desde AudioFileInputNode a un AudioFileOutputNode. Ahora, el audio desde el archivo de audio se reproduce en el altavoz del dispositivo y también se escribe en un archivo de audio.

fileInputNode.AddOutgoingConnection(fileOutputNode);

Los nodos de salida también pueden recibir más de una conexión desde otros nodos. En el siguiente ejemplo, se establece una conexión desde un AudioDeviceInputNode al nodo AudioDeviceOutput. Dado que el nodo de salida tiene conexiones desde el nodo de entrada del archivo y el nodo de entrada del dispositivo, la salida contendrá una mezcla de audio de ambas fuentes. AddOutgoingConnection proporciona una sobrecarga que te permite especificar un valor de ganancia para la señal que pasa a través de la conexión.

deviceInputNode.AddOutgoingConnection(deviceOutputNode, .5);

Aunque los nodos de salida pueden aceptar conexiones de varios nodos, puedes crear una mezcla intermedia de señales desde uno o varios nodos antes de pasar la combinación a una salida. Por ejemplo, puedes establecer el nivel o aplicar efectos a un subconjunto de las señales de audio en un gráfico. Para llevarlo a cabo, puedes usar el elemento AudioSubmixNode. Puedes conectarte a un nodo de submezcla de uno o más nodos de entrada u otros nodos de submezcla. En el ejemplo siguiente, se crea un nuevo nodo de submezcla con AudioGraph.CreateSubmixNode. A continuación, se agregan las conexiones desde un nodo de entrada de archivo y un nodo de salida de fotogramas al nodo de submezcla. Por último, el nodo de submezcla está conectado a un nodo de salida de archivo.

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

Iniciar y detener los nodos del gráfico de audio

Cuando se realiza la llamada a AudioGraph.Start, el audio gráfico comienza el procesamiento de datos de audio. Cada tipo de nodo proporciona métodos Start y Stop que permiten que el nodo individual inicie o detenga el procesamiento de datos. Cuando se realiza una llamada a AudioGraph.Stop, se detiene todo el procesamiento de audio en todos los nodos independientemente del estado de los nodos individuales, pero puedes establecer el estado de cada nodo mientras el gráfico de audio está detenido. Por ejemplo, podrías realizar una llamada a Stop en un nodo individual mientras se detiene el gráfico y después realizar una llamada a AudioGraph.Start, y el nodo individual permanecerá en estado de detención.

Todos los tipos de nodos exponen la propiedad ConsumeInput que, cuando se establece en false, permite que el nodo continúe con el procesamiento de audio, pero no permite que consuma ningún dato de audio que se envíe desde otros nodos.

Todos los tipos de nodos exponen el método Reset, que permite que el nodo descarte todos los datos de audio actualmente en el búfer.

Agregar efectos de audio

La API de gráfico de audio te permite agregar efectos de audio para cada tipo de nodo en un gráfico. Los nodos de salida, nodos de entrada y nodos de submezcla pueden tener un número ilimitado de efectos de audio, limitados solo por las funcionalidades del hardware. El siguiente ejemplo muestra cómo agregar el efecto de eco integrado a un nodo de submezcla.

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

submixNode.EffectDefinitions.Add(echoEffect);
  • Todos los efectos de audio implementan IAudioEffectDefinition. Cada nodo expone una propiedad EffectDefinitions que representa la lista de efectos aplicados a ese nodo. Agrega un efecto agregando su objeto de definición a la lista.
  • Existen varias clases de definición de efectos que se proporcionan en el espacio de nombres Windows.Media.Audio. Entre ellas se incluyen las siguientes:
  • Puedes crear tus propios efectos de audio que implementen IAudioEffectDefinition y aplicarlos a cualquier nodo en un gráfico de audio.
  • Cada tipo de nodo expone un método DisableEffectsByDefinition que deshabilita todos los efectos de la lista EffectDefinitions del nodo que se agregaron mediante la definición especificada. EnableEffectsByDefinition habilita los efectos con la definición especificada.

Audio espacial

A partir de Windows 10, versión 1607, AudioGraph admite audio espacial, que te permite especificar la ubicación en el espacio 3D desde la que se emite el audio de cualquier nodo de entrada o de submezcla. También puedes especificar una forma y una dirección en la que se emita el audio y una velocidad que se usará para cambiar el audio del nodo a Doppler, así como definir un modelo de caída que describa cómo se atenuará el audio con la distancia.

Para crear un emisor, antes puedes crear una forma en la que el sonido se proyectará desde dicho emisor, que puede ser un cono o una forma omnidireccional. La clase AudioNodeEmitterShape proporciona métodos estáticos para crear cada una de estas formas. A continuación, crea un modelo de caída. Esto define cómo disminuye el volumen del audio desde el emisor a medida que aumenta la distancia desde el oyente. El método CreateNatural crea un modelo de caída que emula la caída natural del sonido mediante un modelo disminución de distancia cuadrado. Por último, crea un objeto AudioNodeEmitterSettings. Actualmente, este objeto solo se usa para habilitar y deshabilitar la atenuación de Doppler basada en velocidad del audio del emisor. Llama al constructor AudioNodeEmitter pasando los objetos de inicialización que acabas de crear. De manera predeterminada, el emisor se coloca en el origen, pero puedes establecer la posición de dicho emisor con la propiedad Position.

Nota

Los emisores del nodo de audio solo pueden procesar audio en formato mono con una frecuencia de muestreo de 48 kHz. Si se intenta usar audio estéreo o audio con una frecuencia de muestreo diferente, se producirá una excepción.

Asigna el emisor a un nodo de audio cuando lo crees mediante el método de creación sobrecargado correspondiente al tipo de nodo que desees. En este ejemplo, se usa CreateFileInputNodeAsync para crear un nodo de entrada de archivos desde un archivo especificado y el objeto AudioNodeEmitter que se desee asociar con el nodo.

var emitterShape = AudioNodeEmitterShape.CreateOmnidirectional();
var decayModel = AudioNodeEmitterDecayModel.CreateNatural(.1, 1, 10, 100);
var settings = AudioNodeEmitterSettings.None;

var emitter = new AudioNodeEmitter(emitterShape, decayModel, settings);
emitter.Position = new System.Numerics.Vector3(10, 0, 5);

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

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

fileInputNode = result.FileInputNode;

La clase AudioDeviceOutputNode que envía audio desde el gráfico al usuario tiene un objeto de oyente, al que se accede con la propiedad Listener, que representa la ubicación, la orientación y la velocidad del usuario en el espacio 3D. Las posiciones de todos los emisores del gráfico son relativas a la posición y orientación del objeto de escucha. De manera predeterminada, el oyente se encuentra en el origen (0,0,0) orientado de frente en el eje Z, pero puedes establecer su posición y su orientación con las propiedades Position y Orientation.

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

Puedes actualizar la ubicación, la velocidad y la dirección de los emisores en el tiempo de ejecución para simular el movimiento de un origen de audio a través del espacio 3D.

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

También puedes actualizar la ubicación, la velocidad y la orientación del objeto Listener en el tiempo de ejecución para simular el movimiento del usuario en el espacio 3D.

deviceOutputNode.Listener.Position = newUserPosition;

De manera predeterminada, el audio espacial se calcula mediante el algoritmo de función de transferencia relativo a la cabeza (HRTF) de Microsoft para atenuar el audio en función de su forma, su velocidad y su posición en relación con el oyente. Puedes establecer la propiedad SpatialAudioModelen FoldDown para usar un método simple de mezcla estéreo de simulación de audio espacial, que es menos preciso pero también requiere menos recursos de CPU y memoria.

Vea también