Condividi tramite


Effetti audio personalizzati

Questo articolo descrive come creare un componente Windows Runtime che implementa l'interfaccia IBasicAudioEffect per consentire la creazione di effetti personalizzati per i flussi audio. Gli effetti personalizzati possono essere usati con diverse API di Windows Runtime, tra cui MediaCaptureche consente di accedere alla fotocamera di un dispositivo, MediaComposition, che consente di creare composizioni complesse da clip multimediali e AudioGraph che consente di assemblare rapidamente un grafico di vari nodi audio di input, output e submix.

Aggiungere un effetto personalizzato all'app

Un effetto audio personalizzato viene definito in una classe che implementa l'interfaccia IBasicAudioEffect. Questa classe non può essere inclusa direttamente nel progetto dell'app. Si deve invece usare un componente Windows Runtime per ospitare la classe di effetti audio.

Aggiungere un componente Windows Runtime per l'effetto audio

  1. In Microsoft Visual Studio, con la soluzione aperta, passare al menu File e selezionare Aggiungi>Nuovo progetto.
  2. Selezionare il tipo di progetto Componente Windows Runtime (Windows universale).
  3. Per questo esempio assegnare al progetto il nome AudioEffectComponent. A questo nome verrà fatto riferimento nel codice in un secondo momento.
  4. Fare clic su OK.
  5. Il modello di progetto crea una classe denominata Class1.cs. In Esplora soluzioni fare clic con il tasto destro del mouse sull'icona Class1.cs e selezionare Rinomina.
  6. Assegnare il nome ExampleAudioEffect.cs. Visual Studio visualizzerà una richiesta che chiede se si desidera aggiornare tutti i riferimenti al nuovo nome. Fare clic su .
  7. Aprire ExampleAudioEffect.cs e aggiornare la definizione della classe per implementare l'interfaccia IBasicAudioEffect.
public sealed class ExampleAudioEffect : IBasicAudioEffect

È necessario includere gli spazi dei nomi seguenti nel file di classe dell'effetto per accedere a tutti i tipi usati negli esempi in questo articolo.

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

Implementare l'interfaccia IBasicAudioEffect

L'effetto audio deve implementare tutti i metodi e le proprietà dell'interfaccia IBasicAudioEffect. Questa sezione illustra una semplice implementazione di questa interfaccia per creare un effetto echo di base.

SupportedEncodingProperties - proprietà

Il sistema controlla la proprietà SupportedEncodingProperties per determinare quali proprietà di codifica sono supportate dall'effetto. Si noti che se il consumer dell'effetto non può codificare l'audio usando le proprietà specificate, il sistema chiamerà Close sull'effetto e rimuoverà l'effetto dalla pipeline audio. In questo esempio, gli oggetti AudioEncodingProperties vengono creati e aggiunti all'elenco restituito per supportare la codifica mono a 44,1 kHz e 48 kHz, float a 32 bit.

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;
        
    }
}

Metodo SetEncodingProperties

Il sistema chiama SetEncodingProperties sull'effetto per informare le proprietà di codifica per il flusso audio su cui opera l'effetto. Per implementare un effetto echo, questo esempio usa un buffer per archiviare un secondo di dati audio. Questo metodo consente di inizializzare le dimensioni del buffer per il numero di campioni in un secondo di audio, in base alla frequenza di campionamento in cui viene codificato l'audio. L'effetto ritardo usa anche un contatore integer per tenere traccia della posizione corrente nel buffer di ritardo. Poiché SetEncodingProperties viene chiamato ogni volta che l'effetto viene aggiunto alla pipeline audio, è un buon momento per inizializzare tale valore su 0. È anche possibile acquisire l'oggetto AudioEncodingProperties passato a questo metodo per usarlo altrove nell'effetto.

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;
}

Metodo SetProperties

Il metodo SetProperties consente all'app che usa l'effetto di regolare i parametri dell'effetto. Le proprietà vengono passate come mappa IPropertySet di nomi e valori delle proprietà.

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

Questo semplice esempio combina l'esempio audio corrente con un valore del buffer di ritardo in base al valore della proprietà Mix. Una proprietà viene dichiarata e TryGetValue viene usata per ottenere il valore impostato dall'app chiamante. Se non è stato impostato alcun valore, viene usato un valore predefinito pari a .5. Si noti che questa proprietà è di sola lettura. Il valore della proprietà deve essere impostato utilizzando SetProperties.

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

Metodo ProcessFrame

Il metodo ProcessFrame è il percorso in cui l'effetto modifica i dati dello stream audio. Il metodo viene chiamato una volta per fotogramma e viene passato un oggetto ProcessAudioFrameContext. Questo oggetto contiene un AudioFrame di input che contiene il frame in ingresso da elaborare e un oggetto AudioFrame di output in cui si scrivono dati immagine che verranno passati al resto della pipeline audio. Un frame audio è un buffer di campioni audio che rappresentano una breve sezione di dati audio.

L'accesso al buffer di dati di un AudioFrame richiede interoperabilità COM, pertanto è necessario includere lo spazio dei nomi System.Runtime.InteropServices nel file di classe dell'effetto e quindi aggiungere il codice seguente all'interno dello spazio dei nomi per l'effetto per importare l'interfaccia per l'accesso al buffer audio.

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

Nota

Poiché questa tecnica accede a un buffer di immagini non gestito nativo, è necessario configurare il progetto per consentire il codice non sicuro.

  1. In Esplora soluzioni fare clic con il tasto destro del mouse sul progetto AudioEffectComponent, quindi selezionare Proprietà.
  2. Seleziona la scheda Crea.
  3. Selezionare la casella di spunta Consenti codice non sicuro.

 

È ora possibile aggiungere l'implementazione del metodo ProcessFrame all'effetto. In primo luogo, questo metodo ottiene un oggetto AudioBuffer sia dai fotogrammi audio di input che da quello di output. Si noti che il frame di output viene aperto per la scrittura e l'input per la lettura. Successivamente, viene ottenuto un oggetto IMemoryBufferReference per ogni buffer chiamando CreateReference. Il buffer di dati effettivo viene quindi ottenuto eseguendo il cast degli oggetti IMemoryBufferReference come interfaccia di interoperabilità COM definita in precedenza, IMemoryByteAccess e chiamare GetBuffer.

Dopo aver ottenuto i buffer di dati, è possibile leggere dal buffer di input e scrivere nel buffer di output. Per ogni campione nell'inputbuffer, il valore viene ottenuto e moltiplicato per 1 - Mix per impostare il valore del segnale secco dell'effetto. Successivamente, un campione viene recuperato dalla posizione corrente nel buffer echo e moltiplicato per Mix per impostare il valore bagnato dell'effetto. L'esempio di output viene impostato sulla somma dei valori asciutti e bagnati. Infine, ogni esempio di input viene archiviato nel buffer echo e l'indice di esempio corrente viene incrementato.

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;
            }
        }
    }
}

Metodo Close

Il sistema chiamerà il metodo Close close sulla classe quando l'effetto deve essere arrestato. È consigliabile usare questo metodo per eliminare le risorse create. L'argomento del metodo è un oggetto MediaEffectClosedReason che consente di sapere se l'effetto è stato chiuso normalmente, se si è verificato un errore o se l'effetto non supporta il formato di codifica richiesto.

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

Metodo DiscardQueuedFrames

Il metodo DiscardQueuedFrames viene chiamato quando l'effetto deve essere reimpostato. Uno scenario tipico è se l'effetto archivia fotogrammi elaborati in precedenza da usare nell'elaborazione del frame corrente. Quando viene chiamato questo metodo, è necessario eliminare il set di fotogrammi precedenti salvati. Questo metodo può essere usato per reimpostare qualsiasi stato correlato ai fotogrammi precedenti, non solo ai frame audio accumulati.

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

TimeIndependent - proprietà

La proprietà TimeIndependent TimeIndependent consente al sistema di sapere se l'effetto non richiede tempi uniformi. Se impostato su true, il sistema può usare ottimizzazioni che migliorano le prestazioni dell'effetto.

public bool TimeIndependent { get { return true; } }

UseInputFrameForOutput - proprietà

Impostare la proprietà UseInputFrameForOutput su true per indicare al sistema che l'effetto scriverà l'output nel buffer audio di InputFrame di ProcessAudioFrameContext passato in ProcessFrame invece di scriverlo in OutputFrame.

public bool UseInputFrameForOutput { get { return false; } }

Aggiunta dell'effetto personalizzato all'app

Per usare l'effetto audio dall'app, si deve aggiungere un riferimento al progetto di effetto all'app.

  1. In Esplora soluzioni fare clic con il tasto destro del mouse su Riferimenti e selezionare Aggiungi riferimento.
  2. Espandere la scheda Progetti, selezionare Soluzione e quindi selezionare la casella di controllo relativa al nome del progetto effetto. Nell'esempio, il nome file dello script è AudioEffectComponent.
  3. Fare clic su OK.

Se la classe dell'effetto audio viene dichiarata è uno spazio dei nomi diverso, assicurarsi di includere tale spazio dei nomi nel file di codice.

using AudioEffectComponent;

Aggiungere l'effetto personalizzato a un nodo AudioGraph

Per informazioni generali sull'uso dei grafici audio, vedere Grafici audio. Il frammento di codice seguente illustra come aggiungere l'effetto echo di esempio illustrato in questo articolo a un nodo del grafico audio. Innanzitutto, viene creato un oggetto PropertySet e viene impostato un valore per la proprietà Mix, definita dall'effetto. Viene quindi chiamato il costruttore AudioEffectDefinition, passando il nome completo della classe del tipo di effetto personalizzato e il set di proprietà. Infine, la definizione dell'effetto viene aggiunta alla proprietà EffectDefinitions di un FileInputNode esistente, causando l'elaborazione dell'audio generato dall'effetto personalizzato.

// 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);

Dopo l'aggiunta a un nodo, l'effetto personalizzato può essere disabilitato chiamando DisableEffectsByDefinition e passando l'oggetto AudioEffectDefinition. Per maggiori informazioni sull'uso di grafici audio nell'app, vedere AudioGraph.

Aggiungere l'effetto personalizzato a un clip in MediaComposition

Il frammento di codice seguente illustra l'aggiunta dell'effetto audio personalizzato a un clip video e una traccia audio in background in una composizione multimediale. Per indicazioni generali per la creazione di composizioni multimediali da clip video e l'aggiunta di tracce audio in background, vedere Composizioni multimediali e modifica.

// 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);
}