Gráficos de audio

En este artículo se muestra cómo usar las API del 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 gráfico desde dispositivos de entrada de audio, archivos de audio o código personalizado. lat

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

  • Los nodos de submezcla toman audio de uno o varios nodos y los combinan en una única salida que se puede enrutar a otros nodos del gráfico.

Una vez creados todos los nodos y configuradas las conexiones entre ellos, basta con iniciar el gráfico de audio y los datos de audio fluyen desde los nodos de entrada, a través de cualquier nodo de submezcla, a los nodos de salida. Este modelo hace que 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 sean rápidos y fáciles de implementar.

Se habilitan escenarios adicionales con la adición de efectos de audio al gráfico de audio. Cada nodo de un gráfico de audio se puede rellenar con cero o más efectos de audio que realizan el procesamiento de audio en el audio que pasa por el nodo. Hay varios efectos integrados, como eco, ecualizador, limitación y reverberación, que se pueden conectar a un nodo de audio con solo unas pocas líneas de código. También puede crear sus propios efectos de audio personalizados que funcionen exactamente igual que los efectos integrados.

Nota:

El ejemplo de UWP de AudioGraph implementa el código descrito en esta introducción. Puede descargar el ejemplo para ver el código en contexto o para usarlo como punto de partida para su propia aplicación.

Elección de AudioGraph de Windows Runtime o XAudio2

Las API de gráfico de audio de Windows Runtime ofrecen funcionalidad que también se puede implementar mediante las API XAudio2 basadas en COM. A continuación se muestran las características del marco de gráfico de audio de Windows Runtime que difieren de XAudio2.

Las API de gráfico de audio de Windows Runtime:

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

Clase AudioGraph

La clase AudioGraph es el elemento principal de todos los nodos que componen el gráfico. Use este objeto para crear instancias de todos los tipos de nodo de audio. Cree una instancia de la clase AudioGraph mediante la inicialización de un objeto AudioGraphSettings que contenga los valores de configuración del gráfico y que, a continuación, llame a AudioGraph.CreateAsync. El CreateAudioGraphResult devuelto proporciona acceso al 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 empiece 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 ejecuta el gráfico, pero no hay nodos activos cuando se detiene el gráfico. ResetAllNodes hace que todos los nodos del gráfico descarten los datos que se encuentran actualmente en sus búferes de audio.

  • El evento QuantumStarted se produce cuando el gráfico inicia 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 necesaria es AudioRenderCategory. Especificar este valor permite al sistema optimizar la canalización de audio para la categoría especificada.

  • El tamaño de cuanto del gráfico de audio determina el número de muestras que se procesan al mismo tiempo. De forma predeterminada, el tamaño de cuanto es de 10 ms basado en la frecuencia de muestreo predeterminada. Si especifica un tamaño de cuanto personalizado estableciendo la propiedad DesiredSamplesPerQuantum, también debe establecer la propiedad QuantumSizeSelectionMode en ClosestToDesired; de lo contrario, se omite el valor proporcionado. Si se usa este valor, el sistema elegirá un tamaño de cuanto lo más cercano posible al especificado. Para determinar el tamaño de cuanto real, compruebe SamplesPerQuantum de AudioGraph una vez creado.

  • Si solo tiene previsto usar el gráfico de audio con archivos y no tiene previsto la salida en un dispositivo de audio, se recomienda usar el tamaño de cuanto predeterminado; para ello, no establezca la propiedad DesiredSamplesPerQuantum.

  • La propiedad DesiredRenderDeviceAudioProcessing determina la cantidad de procesamiento que realiza el dispositivo de reproducción principal en la salida del gráfico de audio. El valor Predeterminado permite al sistema usar el procesamiento de audio predeterminado para la categoría de reproducción de audio especificada. Este procesamiento puede mejorar significativamente el sonido del audio en algunos dispositivos, especialmente dispositivos móviles con altavoces pequeños. El valor Sin procesar puede mejorar el rendimiento al minimizar la cantidad de procesamiento de señales realizada, pero puede dar lugar a una calidad de sonido inferior en algunos dispositivos.

  • Si QuantumSizeSelectionMode se establece en LowestLatency, el gráfico de audio usará automáticamente el valor Sin procesar para DesiredRenderDeviceAudioProcessing.

  • A partir de Windows 10, versión 1803, puede establecer la propiedad AudioGraphSettings.MaxPlaybackSpeedFactor para establecer el valor máximo que se usará 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, establecer MaxPlaybackSpeedFactor en el valor más bajo requerido por la aplicación reducirá el consumo de memoria de la aplicación. Si la aplicación solo va a reproducir contenido a velocidad normal, se recomienda establecer MaxPlaybackSpeedFactor en 1.

  • EncodingProperties determina el formato de audio usado por el gráfico. Solo se admiten formatos flotantes de 32 bits.

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

Puede permitir que el gráfico de audio use el dispositivo de reproducción de audio predeterminado o use la clase Windows.Devices.Enumeration.DeviceInformation para obtener una lista de los dispositivos de reproducción de audio disponibles del sistema llamando a FindAllAsync y transfiriendo el selector de dispositivos de reproducción de audio devuelto por Windows.Media.Devices.MediaDevice.GetAudioRenderSelector. Puede elegir uno de los objetos DeviceInformation devueltos mediante programación o mostrar la interfaz de usuario para permitir que el usuario seleccione un dispositivo y después úselo 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 del dispositivo

Un nodo de entrada de dispositivo alimenta el audio en el gráfico desde un dispositivo de captura de audio conectado al sistema, como un micrófono. Cree un objeto DeviceInputNode que use el dispositivo de captura de audio predeterminado del sistema llamando a CreateDeviceInputNodeAsync. Proporcione 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 desea 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 transfiriendo el selector de dispositivos de reproducció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, transferirlo 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 salida del dispositivo

Un nodo de salida de dispositivo inserta audio del gráfico en un dispositivo de reproducción de audio, como altavoces o auriculares. Cree un DeviceOutputNode llamando a CreateDeviceOutputNodeAsync. El nodo de salida usa 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 permite alimentar datos de un archivo de audio en el gráfico. Cree 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 archivo admiten los siguientes formatos de archivo: mp3, wav, wma, m4a.
  • Establezca la propiedad StartTime para especificar el desfase de tiempo en el archivo donde debe comenzar la reproducción. Si esta propiedad es nula, se usa el principio del archivo. Establezca la propiedad EndTime para especificar el desfase de tiempo en el archivo donde debe finalizar la reproducción. Si esta propiedad es nula, se usa el final del archivo. El valor de hora de inicio debe ser inferior al valor de hora de finalización y el valor de hora de finalización debe ser menor o igual que la duración del archivo de audio, que se puede determinar comprobando el valor de la propiedad Duration.
  • Busque una posición en el archivo de audio llamando a Seek y especificando el desfase de tiempo en el archivo al que se debe mover la posición de reproducción. El valor especificado debe estar dentro del intervalo StartTime y EndTime. Obtenga la posición de reproducción actual del nodo con la propiedad Position de solo lectura.
  • Habilite el bucle del archivo de audio estableciendo la propiedad LoopCount. Cuando no es nulo, este valor indica las veces que se reproducirá el archivo después de la reproducción inicial. Por ejemplo, si establece LoopCount en 1, el archivo se reproducirá 2 veces en total; si lo establece en 5, se reproducirá 6 veces en total. Establecer LoopCount en nulo hace que el archivo se repita indefinidamente. Para detener el bucle, establezca el valor en 0.
  • Ajuste la velocidad a la que se reproduce el archivo de audio estableciendo PlaybackSpeedFactor. Un valor de 1 indica la velocidad original del archivo, 5 es a media velocidad y 2 es a doble velocidad.

Nodo de entrada de 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 transmisión o un origen de red de streaming adaptable. Un nodo **MediaSourceAudioInputNode permite dirigir los datos de audio desde un objeto MediaSource al gráfico de audio. Cree un MediaSourceAudioInputNode llamando a CreateMediaSourceAudioInputNodeAsync y transfiera 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 accediendo 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 que es probable que la reproducción de un archivo desde el disco siempre se complete correctamente, los medios transmitidos desde un origen de red pueden fallar 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 un MediaSource se vuelve irreproducible durante la reproducción, el gráfico de audio lanzará el evento UnrecoverableErrorOccurred. Puede usar el controlador para este evento para detener y eliminar el gráfico de audio y, a continuación, reinicializar el gráfico.

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

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. Cree 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 secuencias de audio

Un nodo de entrada de secuencias de audio le permite insertar datos de audio que genere en su propio código en el gráfico de audio. Esto permite escenarios como la creación de 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 comenzar a procesar el siguiente cuanto de datos de audio. Proporcione los datos de audio generados personalizados desde el 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 transferido al controlador de eventos QuantumStarted expone la propiedad RequiredSamples, que indica cuántos ejemplos necesita el gráfico de audio para rellenar el cuanto que se va a procesar.
  • Llame a AudioFrameInputNode.AddFrame para transferir un objeto AudioFrame con datos de audio al gráfico.
  • En Windows 10, versión 1803, se introdujo un nuevo conjunto de API para usar MediaFrameReader con datos de audio. Estas API permiten obtener objetos AudioFrame de un origen de secuencias multimedia, que se pueden transferir a FrameInputNode mediante el método AddFrame. Para obtener más información, consulte Proceso de secuencias de audio con MediaFrameReader.
  • A continuación se muestra una implementación de ejemplo del método auxiliar GenerateAudioData.

Para rellenar un AudioFrame con datos de audio, debe obtener acceso al búfer de memoria subyacente de la secuencia de audio. Para ello, debe inicializar la interfaz COM IMemoryBufferByteAccess agregando el código siguiente dentro del 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 código siguiente muestra una implementación de ejemplo de un método auxiliar GenerateAudioData que crea un AudioFrame y lo 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;
}
  • Dado que este método accede al búfer sin procesar subyacente a los tipos de Windows Runtime, debes declararte mediante la palabra clave inseguro. También debes configurar el proyecto en Microsoft Visual Studio para permitir la compilación de código no seguro; para ello, abre la página Propiedades del proyecto, haz clic en la página de propiedades Compilar y activa la casilla Permitir código no seguro.
  • Inicialice una nueva instancia de AudioFrame en el espacio de nombres Windows.Media, transfiriendo el tamaño de búfer deseado al constructor. El tamaño del búfer es el número de muestras multiplicadas por el tamaño de cada muestra.
  • Obtenga el AudioBuffer de la secuencia de audio llamando a LockBuffer.
  • Obtenga una instancia de la interfaz COM IMemoryBufferByteAccess desde el búfer de audio llamando a CreateReference.
  • Obtenga un puntero a los datos de búfer de audio sin procesar llamando a IMemoryBufferByteAccess.GetBuffer y cámbielo al tipo de datos de ejemplo de los datos de audio.
  • Rellene el búfer con datos y devuelva el AudioFrame para enviarlo al gráfico de audio.

Nodo de salida de secuencias de audio

Un nodo de salida de secuencias de audio permite recibir y procesar la salida de datos de audio del gráfico de audio con código personalizado que cree. Un escenario de ejemplo es realizar el análisis de señal en la salida de audio. Cree 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. Puede acceder a los datos de audio desde el controlador para este evento.

Nota:

Si desea recuperar secuencias de audio con una cadencia normal, sincronizada con el gráfico de audio, llame a AudioFrameOutputNode.GetFrame desde el controlador de eventos sincrónico QuantumStarted. 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 secuencias de audio.

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

}
  • Llame a GetFrame para obtener un objeto AudioFrame con datos de audio del gráfico.
  • A continuación se muestra una implementación de ejemplo 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 anterior del nodo de entrada de secuencias de audio, deberá declarar la interfaz COM IMemoryBufferByteAccess y configurar el proyecto para permitir código no seguro para acceder al búfer de audio subyacente.
  • Obtenga el AudioBuffer de la secuencia de audio llamando a LockBuffer.
  • Obtenga una instancia de la interfaz COM IMemoryBufferByteAccess desde el búfer de audio llamando a CreateReference.
  • Obtenga un puntero a los datos de búfer de audio sin procesar llamando a IMemoryBufferByteAccess.GetBuffer y cámbielo al tipo de datos de ejemplo de los datos de audio.

Conexiones de nodos 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 nodo que se transfiere al método. En el ejemplo siguiente se conecta un AudioFileInputNode con un AudioDeviceOutputNode, que es una configuración sencilla para reproducir un archivo de audio en el altavoz del dispositivo.

fileInputNode.AddOutgoingConnection(deviceOutputNode);

Puede crear más de una conexión desde un nodo de entrada a otros nodos. En el ejemplo siguiente se agrega otra conexión desde un AudioFileInputNode a un AudioFileOutputNode. Ahora, el audio del 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 de otros nodos. En el ejemplo siguiente se realiza una conexión desde un AudioDeviceInputNode al nodo AudioDeviceOutput. Dado que el nodo de salida tiene conexiones desde el nodo de entrada de archivo y el nodo de entrada del dispositivo, la salida contendrá una combinación de audio de ambos orígenes. AddOutgoingConnection proporciona una sobrecarga que permite especificar un valor de ganancia para la señal que pasa por la conexión.

deviceInputNode.AddOutgoingConnection(deviceOutputNode, .5);

Aunque los nodos de salida pueden aceptar conexiones de varios nodos, es posible que desee crear una combinación intermedia de señales de uno o varios nodos antes de transferir la combinación a una salida. Por ejemplo, puede que desee establecer el nivel o aplicar efectos a un subconjunto de las señales de audio de un gráfico. Para ello, use AudioSubmixNode. Puede conectarse a un nodo de submezcla desde uno o varios 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, las conexiones se agregan desde un nodo de entrada de archivo y un nodo de salida de secuencias al nodo de submezcla. Por último, el nodo de submezcla se conecta a un nodo de salida de archivo.

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

Inicio y detención de nodos de gráficos de audio

Cuando se llama a AudioGraph.Start, el gráfico de audio comienza a procesar datos de audio. Cada tipo de nodo proporciona métodos Start y Stop, que hacen que el nodo individual inicie o detenga el procesamiento de datos. Cuando se llama a AudioGraph.Stop, todo el procesamiento de audio en todos los nodos se detiene, independientemente del estado de los nodos individuales, pero el estado de cada nodo se puede establecer mientras se detiene el gráfico de audio. Por ejemplo, podría llamar a Stop en un nodo individual mientras se detiene el gráfico y, a continuación, llamar a AudioGraph.Start, y el nodo individual permanecerá en estado detenido.

Todos los tipos de nodo exponen la propiedad ConsumInput que, cuando se establece en false, permite al nodo continuar el procesamiento de audio, pero impide que consuma datos de audio que se introduzcan desde otros nodos.

Todos los tipos de nodo exponen el método Reset, que hace que el nodo descarte los datos de audio que se encuentran actualmente en su búfer.

Adición de efectos de audio

La API de gráficos de audio permite agregar efectos de audio a cada tipo de nodo de un gráfico. Los nodos de salida, los nodos de entrada y los nodos de submezcla pueden tener un número ilimitado de efectos de audio, limitado solo por las funcionalidades del hardware. En el ejemplo siguiente se 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. Para agregar un efecto, añada el objeto de definición a la lista.
  • Hay varias clases de definición de efectos que se proporcionan en el espacio de nombres Windows.Media.Audio. Entre ellas se incluyen las siguientes:
  • Puede crear sus propios efectos de audio que implementen IAudioEffectDefinition y aplicarlos a cualquier nodo de 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 permite especificar la ubicación en el espacio 3D desde el que se emite audio desde cualquier nodo de entrada o submezcla. También puede especificar una forma y dirección en la que se emite el audio, una velocidad que se utilizará para el desplazamiento Doppler del audio del nodo y definir un modelo de decadencia que describa cómo se atenúa el audio con la distancia.

Para crear un emisor, primero puede crear una forma en la que el sonido se proyecta desde el emisor, que puede ser un cono u omnidireccional. La clase AudioNodeEmitterShape proporciona métodos estáticos para crear cada una de estas formas. A continuación, cree un modelo de decadencia. Esto define cómo disminuye el volumen del audio del emisor a medida que aumenta la distancia desde el agente de escucha. El método CreateNatural crea un modelo de decadencia que emula la decadencia natural del sonido mediante un modelo de distancia al cuadrado. Por último, cree 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. Llame al constructor AudioNodeEmitter y transfiera los objetos de inicialización que acaba de crear. De forma predeterminada, el emisor se coloca en el origen, pero puede establecer la posición del emisor con la propiedad Position.

Nota:

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

El emisor se asigna a un nodo de audio al crearlo mediante el método de creación sobrecargado para el tipo de nodo que desee. En este ejemplo, se utiliza CreateFileInputNodeAsync para crear un nodo de entrada de archivos a partir de un archivo especificado y del objeto AudioNodeEmitter que desee asociar al 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;

El AudioDeviceOutputNode que genera audio del gráfico al usuario tiene un objeto de escucha, 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 forma predeterminada, el agente de escucha se encuentra en el origen (0,0,0) orientado hacia delante a lo largo del eje Z, pero puede establecer su posición y 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);

Puede actualizar la ubicación, la velocidad y la dirección de los emisores en 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 puede actualizar la ubicación, la velocidad y la orientación del objeto de escucha en tiempo de ejecución para simular el movimiento del usuario a través del espacio 3D.

deviceOutputNode.Listener.Position = newUserPosition;

De forma predeterminada, el audio espacial se calcula mediante el algoritmo de la función de transferencia relacionada con encabezados (HRTF) de Microsoft para atenuar el audio en función de su forma, velocidad y posición en relación con el agente de escucha. Puede establecer la propiedad SpatialAudioModel en FoldDown para utilizar un método de mezcla estéreo simple de simulación de audio espacial que es menos preciso pero requiere menos recursos de CPU y memoria.

Consulte también