Niestandardowe efekty dźwiękowe

W tym artykule opisano sposób tworzenia składnika środowisko wykonawcze systemu Windows implementującego interfejs IBasicAudioEffect w celu utworzenia niestandardowych efektów dla strumieni audio. Możesz używać efektów niestandardowych z audiograph, MediaCapture i MediaComposition.

Note

Interfejs IBasicAudioEffect jest interfejsem API środowiska środowisko wykonawcze systemu Windows w przestrzeni nazw Windows.Media.Effects. Te interfejsy API działają tak samo w aplikacjach klasycznych WinUI 3, jak w systemie UWP. Główna różnica polega na tym, jak skonfigurować projekt i odwołać się do składnika efektu.

Dodaj składnik środowisko wykonawcze systemu Windows dla efektu audio

W Visual Studio dodaj projekt składnika środowisko wykonawcze systemu Windows (C#) do rozwiązania WinUI 3:

  1. Kliknij rozwiązanie prawym przyciskiem myszy w Eksplorator rozwiązań i wybierz pozycję Dodaj>nowy Project.
  2. Wybierz szablon projektu Składnik środowiska uruchomieniowego systemu Windows.
  3. Nadaj projektowi nazwę AudioEffectComponent.
  4. Dodaj odwołanie do projektu z głównej aplikacji WinUI 3 do tego projektu składnika.
  5. Zmień nazwę domyślnego pliku klasy na ExampleAudioEffect.cs.

Implementowanie interfejsu IBasicAudioEffect

Efekt dźwiękowy musi implementować wszystkie metody i właściwości interfejsu IBasicAudioEffect .

using System.Collections.Generic;
using Windows.Media;
using Windows.Media.Effects;
using Windows.Media.MediaProperties;
using Windows.Foundation.Collections;

namespace AudioEffectComponent;

public sealed class ExampleAudioEffect : IBasicAudioEffect
{
    private AudioEncodingProperties _currentEncodingProperties;
    private IPropertySet _propertySet;
    private float _echoDelay = 0.5f; // delay in seconds
    private float _echoMix = 0.5f;   // mix level (0-1)

    public bool UseInputFrameForOutput => true;

    public IReadOnlyList<AudioEncodingProperties> SupportedEncodingProperties
    {
        get
        {
            var supportedProperties = new List<AudioEncodingProperties>();

            AudioEncodingProperties encodingProps1 =
                AudioEncodingProperties.CreatePcm(44100, 1, 32);
            encodingProps1.Subtype = MediaEncodingSubtypes.Float;
            supportedProperties.Add(encodingProps1);

            AudioEncodingProperties encodingProps2 =
                AudioEncodingProperties.CreatePcm(48000, 1, 32);
            encodingProps2.Subtype = MediaEncodingSubtypes.Float;
            supportedProperties.Add(encodingProps2);

            return supportedProperties;
        }
    }

    public void SetEncodingProperties(
        AudioEncodingProperties encodingProperties)
    {
        _currentEncodingProperties = encodingProperties;
    }

    public void SetProperties(IPropertySet configuration)
    {
        _propertySet = configuration;

        // Read custom properties if provided
        if (configuration != null &&
            configuration.TryGetValue("EchoDelay", out object delay))
        {
            _echoDelay = (float)delay;
        }

        if (configuration != null &&
            configuration.TryGetValue("EchoMix", out object mix))
        {
            _echoMix = (float)mix;
        }
    }

    public void ProcessFrame(ProcessAudioFrameContext context)
    {
        // Audio processing logic goes here.
        // See the echo effect example below.
    }

    public void Close(MediaEffectClosedReason reason)
    {
        // Clean up resources
    }

    public void DiscardQueuedFrames()
    {
        // Discard any cached frames
    }
}

Tworzenie efektu echa

W poniższym przykładzie pokazano implementację ProcessFrame , która tworzy efekt echa. Efekt przechowuje poprzednie dane audio w buforze i miesza je z bieżącymi danymi audio po konfigurowalnym opóźnieniu.

Aby uzyskać dostęp do nieprzetworzonego buforu audio, potrzebny jest interfejs IMemoryBufferByteAccess COM. Dodaj następującą definicję w przestrzeni nazw efektu:

using System.Runtime.InteropServices;

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

Ważna

Ta technika uzyskuje dostęp do natywnego, niezarządzanego buforu audio. Aby włączyć niebezpieczny kod, musisz dodać <AllowUnsafeBlocks>true</AllowUnsafeBlocks> do pliku .csproj projektu.

private float[] _echoBuffer;
private int _echoBufferPosition;
private float _echoDelay = 0.5f; // delay in seconds
private float _echoMix = 0.5f;   // mix level (0-1)
private Windows.Media.MediaProperties.AudioEncodingProperties _currentEncodingProperties;

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

    using (AudioBuffer inputBuffer =
        inputFrame.LockBuffer(AudioBufferAccessMode.ReadWrite))
    using (IMemoryBufferReference inputReference =
        inputBuffer.CreateReference())
    {
        byte* dataInBytes;
        uint capacityInBytes;

        ((IMemoryBufferByteAccess)inputReference).GetBuffer(
            out dataInBytes, out capacityInBytes);

        float* dataInFloat = (float*)dataInBytes;
        int dataInFloatLength =
            (int)capacityInBytes / sizeof(float);

        // Initialize echo buffer on first call
        if (_echoBuffer == null)
        {
            int delaySamples = (int)(
                _currentEncodingProperties.SampleRate * _echoDelay);
            _echoBuffer = new float[delaySamples];
        }

        for (int i = 0; i < dataInFloatLength; i++)
        {
            float input = dataInFloat[i];
            float echo = _echoBuffer[_echoBufferPosition];

            // Mix the original signal with the echo
            dataInFloat[i] = input + (echo * _echoMix);

            // Store the current sample in the echo buffer
            _echoBuffer[_echoBufferPosition] = input;
            _echoBufferPosition =
                (_echoBufferPosition + 1) % _echoBuffer.Length;
        }
    }
}

Dodawanie efektu do grafu audio

Aby użyć efektu niestandardowego z elementem AudioGraph, utwórz instancję definicji efektu i dodaj ją do węzła:

using Windows.Media.Audio;
using Windows.Media.Effects;

var effectDefinition = new AudioEffectDefinition(
    typeof(AudioEffectComponent.ExampleAudioEffect).FullName);

audioFileInputNode.EffectDefinitions.Add(effectDefinition);

Przekazywanie konfiguracji do efektu

Wartości konfiguracji można przekazać do swojego efektu przy użyciu elementu PropertySet:

var properties = new PropertySet();
properties["EchoDelay"] = 0.3f;
properties["EchoMix"] = 0.6f;

var effectDefinition = new AudioEffectDefinition(
    typeof(AudioEffectComponent.ExampleAudioEffect).FullName,
    properties);

W metodzie efektu SetProperties (pokazanej wcześniej) odczytaj te wartości z właściwości ustawionej w celu skonfigurowania efektu w czasie wykonywania.

Dodaj efekt do obiektu MediaCapture

Możesz również dodać efekt dźwięku niestandardowego do strumienia audio MediaCapture :

var effectDefinition = new AudioEffectDefinition(
    typeof(AudioEffectComponent.ExampleAudioEffect).FullName);

await _mediaCapture.AddAudioEffectAsync(effectDefinition);