Compartir a través de


Efectos de audio personalizados

En este artículo se describe cómo crear un componente de Windows Runtime que implemente la interfaz IBasicAudioEffect para crear efectos personalizados para secuencias de audio. Los efectos personalizados se pueden usar con varias API de Windows Runtime diferentes, como MediaCapture, que proporciona acceso a la cámara de un dispositivo, MediaComposition, que le permite crear composiciones complejas fuera de clips multimedia, y AudioGraph, que le permite ensamblar rápidamente un grafo de varios nodos de entrada, salida y submezcla de audio.

Adición de un efecto personalizado a la aplicación

Un efecto de audio personalizado se define en una clase que implementa la interfaz IBasicAudioEffect. Esta clase no se puede incluir directamente en el proyecto de la aplicación. En su lugar, debe usar un componente de Windows Runtime para hospedar la clase de efectos de audio.

Agregar un componente de Windows Runtime para el efecto de audio

  1. En Microsoft Visual Studio, con la solución abierta, vaya al menú Archivo y seleccione Agregar->Nuevo proyecto.
  2. Seleccione el tipo de proyecto Componente de Windows Runtime (Universal Windows).
  3. En este ejemplo, asigne al proyecto el nombre AudioEffectComponent. Se hará referencia a este nombre en el código más adelante.
  4. Haga clic en OK.
  5. La plantilla de proyecto crea una clase denominada Class1.cs. En el Explorador de soluciones, haga clic con el botón derecho en el icono de Class1.cs y seleccione Cambiar nombre.
  6. Cambie el nombre del archivo por ExampleAudioEffect.cs. Visual Studio mostrará un mensaje que le preguntará si desea actualizar todas las referencias al nuevo nombre. Haga clic en .
  7. Abra ExampleAudioEffect.cs y actualice la definición de clase para implementar la interfaz IBasicAudioEffect.
public sealed class ExampleAudioEffect : IBasicAudioEffect

Debe incluir los siguientes espacios de nombres en el archivo de clase del efecto para tener acceso a todos los tipos usados en los ejemplos de este artículo.

using Windows.Media.Effects;
using Windows.Media.MediaProperties;
using Windows.Foundation.Collections;
using System.Runtime.InteropServices;
using Windows.Media;
using Windows.Foundation;

Implementación de la interfaz IBasicAudioEffect

El efecto de audio debe implementar todos los métodos y propiedades de la interfaz IBasicAudioEffect. Esta sección le guía a través de una implementación sencilla de esta interfaz para crear un efecto de eco básico.

Propiedad SupportedEncodingProperties

El sistema comprueba la propiedad SupportedEncodingProperties para determinar qué propiedades de codificación son compatibles con el efecto. Tenga en cuenta que si el consumidor del efecto no puede codificar audio con las propiedades que especifique, el sistema llamará a Close en el efecto y quitará el efecto de la canalización de audio. En este ejemplo, se crean objetos AudioEncodingProperties y se agregan a la lista devuelta para admitir 44,1 kHz y 48 kHz, float de 32 bits, codificación mono.

public IReadOnlyList<AudioEncodingProperties> SupportedEncodingProperties
{
    get
    {
        var supportedEncodingProperties = new List<AudioEncodingProperties>();
        AudioEncodingProperties encodingProps1 = AudioEncodingProperties.CreatePcm(44100, 1, 32);
        encodingProps1.Subtype = MediaEncodingSubtypes.Float;
        AudioEncodingProperties encodingProps2 = AudioEncodingProperties.CreatePcm(48000, 1, 32);
        encodingProps2.Subtype = MediaEncodingSubtypes.Float;

        supportedEncodingProperties.Add(encodingProps1);
        supportedEncodingProperties.Add(encodingProps2);

        return supportedEncodingProperties;
        
    }
}

Método SetEncodingProperties

El sistema llama a SetEncodingProperties en el efecto para informarle de las propiedades de codificación de la secuencia de audio en la que funciona el efecto. Para implementar un efecto de eco, en este ejemplo se usa un búfer para almacenar un segundo de datos de audio. Este método proporciona la oportunidad de inicializar el tamaño del búfer en el número de muestras en un segundo de audio, en función de la frecuencia de muestreo en la que se codifica el audio. El efecto de retraso también usa un contador entero para realizar un seguimiento de la posición actual en el búfer de retraso. Puesto que se llama a SetEncodingProperties cada vez que se agrega el efecto a la canalización de audio, este es un buen momento para inicializar ese valor en 0. También es posible que quiera capturar el objeto AudioEncodingProperties pasado a este método para usarlo en otro lugar del efecto.

private float[] echoBuffer;
private int currentActiveSampleIndex;
private AudioEncodingProperties currentEncodingProperties;
public void SetEncodingProperties(AudioEncodingProperties encodingProperties)
{
    currentEncodingProperties = encodingProperties;
    echoBuffer = new float[encodingProperties.SampleRate]; // exactly one second delay
    currentActiveSampleIndex = 0;
}

Método SetProperties

El método SetProperties permite que la aplicación que usa el efecto ajuste los parámetros de efecto. Las propiedades se pasan como un mapa IPropertySet de nombres y valores de propiedad.

IPropertySet configuration;
public void SetProperties(IPropertySet configuration)
{
    this.configuration = configuration;
}

En este ejemplo sencillo se combinará la muestra de audio actual con un valor del búfer de retraso según el valor de la propiedad Mix. Se declara una propiedad y TryGetValue se usa para obtener el valor establecido por la aplicación que realiza la llamada. Si no se ha establecido ningún valor, se utiliza un valor predeterminado de .5. Tenga en cuenta que esta propiedad es de solo lectura. El valor de la propiedad debe establecerse mediante SetProperties.

public float Mix
{
    get
    {
        object val;
        if (configuration != null && configuration.TryGetValue("Mix", out val))
        {
            return (float)val;
        }
        return .5f;
    }
}

Método ProcessFrame

El método ProcessFrame es donde el efecto modifica los datos de audio de la secuencia. El método se llama una vez por fotograma y se pasa un objeto ProcessAudioFrameContext. Este objeto contiene un objeto AudioFrame de entrada que contiene el marco entrante que se va a procesar y un objeto AudioFrame de salida en el que se escriben datos de audio que se pasarán al resto de la canalización de audio. Un fotograma de audio es un búfer de muestras de audio que representa un segmento corto de datos de audio.

El acceso al búfer de datos de un AudioFrame requiere interoperabilidad COM, por lo que debe incluir el espacio de nombres System.Runtime.InteropServices en el archivo de clase de efecto y, a continuación, agregar el código siguiente dentro del espacio de nombres para que el efecto importe la interfaz para acceder al búfer de audio.

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

Nota:

Dado que esta técnica tiene acceso a un búfer de imágenes nativo y no administrado, deberá configurar el proyecto para permitir código no seguro.

  1. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto AudioEffectComponent y seleccione Propiedades.
  2. Seleccione la pestaña Crear.
  3. Active la casilla Permitir código no seguro.

 

Ahora puede agregar la implementación del método ProcessFrame al efecto. En primer lugar, este método obtiene un objeto AudioBuffer de los fotogramas de audio de entrada y salida. Tenga en cuenta que el marco de salida se abre para escribir y la entrada para leer. A continuación, se obtiene una IMemoryBufferReference para cada búfer mediante una llamada a CreateReference. A continuación, el búfer de datos real se obtiene mediante la conversión de los objetos IMemoryBufferReference como la interfaz de interoperabilidad COM definida anteriormente, IMemoryByteAccess y, a continuación, llamando a GetBuffer.

Ahora que se han obtenido los búferes de datos, puede leer del búfer de entrada y escribir en el búfer de salida. Para cada muestra del búfer de entrada, el valor se obtiene y multiplica por 1 - Mix para establecer el valor de señal seca del efecto. A continuación, se recupera una muestra de la posición actual en el búfer de eco y se multiplica por Mix para establecer el valor húmedo del efecto. El ejemplo de salida se establece en la suma de los valores secos y húmedos. Por último, cada ejemplo de entrada se almacena en el búfer de eco y se incrementa el índice de ejemplo actual.

unsafe public void ProcessFrame(ProcessAudioFrameContext context)
{
    AudioFrame inputFrame = context.InputFrame;
    AudioFrame outputFrame = context.OutputFrame;

    using (AudioBuffer inputBuffer = inputFrame.LockBuffer(AudioBufferAccessMode.Read),
                        outputBuffer = outputFrame.LockBuffer(AudioBufferAccessMode.Write))
    using (IMemoryBufferReference inputReference = inputBuffer.CreateReference(),
                                    outputReference = outputBuffer.CreateReference())
    {
        byte* inputDataInBytes;
        byte* outputDataInBytes;
        uint inputCapacity;
        uint outputCapacity;

        ((IMemoryBufferByteAccess)inputReference).GetBuffer(out inputDataInBytes, out inputCapacity);
        ((IMemoryBufferByteAccess)outputReference).GetBuffer(out outputDataInBytes, out outputCapacity);

        float* inputDataInFloat = (float*)inputDataInBytes;
        float* outputDataInFloat = (float*)outputDataInBytes;

        float inputData;
        float echoData;

        // Process audio data
        int dataInFloatLength = (int)inputBuffer.Length / sizeof(float);

        for (int i = 0; i < dataInFloatLength; i++)
        {
            inputData = inputDataInFloat[i] * (1.0f - this.Mix);
            echoData = echoBuffer[currentActiveSampleIndex] * this.Mix;
            outputDataInFloat[i] = inputData + echoData;
            echoBuffer[currentActiveSampleIndex] = inputDataInFloat[i];
            currentActiveSampleIndex++;

            if (currentActiveSampleIndex == echoBuffer.Length)
            {
                // Wrap around (after one second of samples)
                currentActiveSampleIndex = 0;
            }
        }
    }
}

Método Close

El sistema llamará al método CloseClose en la clase cuando se cierre el efecto. Debe usar este método para eliminar los recursos que haya creado. El argumento del método es un MediaEffectClosedReason que le permite saber si el efecto se cerró normalmente, si se produjo un error o si el efecto no admite el formato de codificación necesario.

public void Close(MediaEffectClosedReason reason)
{
    // Dispose of effect resources
    echoBuffer = null;
}

Método DiscardQueuedFrames

Se llama al método DiscardQueuedFrames cuando se debe restablecer el efecto. Un escenario típico para esto es si el efecto almacena fotogramas procesados previamente para usarlos en el procesamiento del fotograma actual. Cuando se llama a este método, debe eliminar el conjunto de fotogramas anteriores que guardó. Este método se puede usar para restablecer cualquier estado relacionado con fotogramas anteriores, no solo fotogramas de audio acumulados.

public void DiscardQueuedFrames()
{
    // Reset contents of the samples buffer
    Array.Clear(echoBuffer, 0, echoBuffer.Length - 1);
    currentActiveSampleIndex = 0;
}

Propiedad TimeIndependent

La propiedad TimeIndependent TimeIndependent permite al sistema saber si el efecto no requiere un tiempo uniforme. Cuando se establece en true, el sistema puede usar optimizaciones que mejoran el rendimiento del efecto.

public bool TimeIndependent { get { return true; } }

Propiedad UseInputFrameForOutput

Establezca la propiedad UseInputFrameForOutput en true para indicar al sistema que el efecto escribirá su salida en el búfer de audio del InputFrame del ProcessAudioFrameContext pasado a ProcessFrame en lugar de escribir en OutputFrame.

public bool UseInputFrameForOutput { get { return false; } }

Adición del efecto personalizado a la aplicación

Para usar el efecto de audio de la aplicación, debe agregar una referencia al proyecto de efecto a la aplicación.

  1. En el Explorador de soluciones, en su proyecto de aplicación, haga clic con el botón derecho en Referencias y seleccione Agregar referencia.
  2. Expanda la pestaña Proyectos, seleccione Solución y, a continuación, active la casilla del nombre del proyecto de efecto. En este ejemplo, el nombre es AudioEffectComponent.
  3. Haga clic en Aceptar

Si la clase de efectos de audio se declara es un espacio de nombres diferente, asegúrese de incluir ese espacio de nombres en el archivo de código.

using AudioEffectComponent;

Adición del efecto personalizado a un nodo AudioGraph

Para obtener información general sobre el uso de gráficos de audio, consulte Gráficos de audio. El siguiente fragmento de código muestra cómo agregar el efecto echoe de ejemplo que se muestra en este artículo a un nodo de grafo de audio. En primer lugar, se crea un PropertySet y se establece un valor para la propiedad Mix, definida por el efecto. A continuación, se llama al constructor AudioEffectDefinition, pasando el nombre de clase completo del tipo de efecto personalizado y el conjunto de propiedades. Por último, la definición del efecto se agrega a la propiedad EffectDefinitions de un FileInputNode existente, lo que provoca que el efecto personalizado procese el audio emitido.

// Create a property set and add a property/value pair
PropertySet echoProperties = new PropertySet();
echoProperties.Add("Mix", 0.5f);

// Instantiate the custom effect defined in the 'AudioEffectComponent' project
AudioEffectDefinition echoEffectDefinition = new AudioEffectDefinition(typeof(ExampleAudioEffect).FullName, echoProperties);
fileInputNode.EffectDefinitions.Add(echoEffectDefinition);

Una vez agregado a un nodo, el efecto personalizado se puede deshabilitar llamando a DisableEffectsByDefinition y pasando el objeto AudioEffectDefinition. Para obtener más información sobre el uso de gráficos de audio en la aplicación, consulte AudioGraph.

Agregar el efecto personalizado a un clip en MediaComposition

El siguiente fragmento de código muestra cómo agregar el efecto de audio personalizado a un clip de vídeo y una pista de audio de fondo en una composición multimedia. Para obtener instrucciones generales para crear composiciones multimedia a partir de clips de vídeo y agregar pistas de audio de fondo, consulte Composiciones multimedia y edición.

// Create a property set and add a property/value pair
PropertySet echoProperties = new PropertySet();
echoProperties.Add("Mix", 0.5f);

// Instantiate the custom effect defined in the 'AudioEffectComponent' project
AudioEffectDefinition echoEffectDefinition = new AudioEffectDefinition(typeof(ExampleAudioEffect).FullName, echoProperties);

// Add custom audio effect to the current clip in the timeline
var currentClip = composition.Clips.FirstOrDefault(
    mc => mc.StartTimeInComposition <= mediaPlayerElement.MediaPlayer.PlaybackSession.Position &&
    mc.EndTimeInComposition >= mediaPlayerElement.MediaPlayer.PlaybackSession.Position);
currentClip.AudioEffectDefinitions.Add(echoEffectDefinition);

// Add custom audio effect to the first background audio track
if (composition.BackgroundAudioTracks.Count > 0)
{
    composition.BackgroundAudioTracks[0].AudioEffectDefinitions.Add(echoEffectDefinition);
}