Поделиться через


Пользовательские звуковые эффекты

В этой статье описывается создание компонента среда выполнения Windows, реализующего интерфейс IBasicAudioEffect для создания пользовательских эффектов для аудиопотоков. Пользовательские эффекты можно использовать с несколькими различными API-интерфейсами среда выполнения Windows, включая MediaCapture, который предоставляет доступ к камере устройства, MediaComposition, что позволяет создавать сложные композиции из клипов мультимедиа и Аудиограф, который позволяет быстро собрать граф различных звуковых входных, выходных и вложенных узлов.

Добавление пользовательского эффекта в приложение

Настраиваемый звуковой эффект определяется в классе, реализующего интерфейс IBasicAudioEffect . Этот класс нельзя включить непосредственно в проект приложения. Вместо этого необходимо использовать компонент среда выполнения Windows для размещения класса звуковых эффектов.

Добавление компонента среда выполнения Windows для эффекта звука

  1. В Microsoft Visual Studio с открытым решением перейдите в меню "Файл " и выберите "Добавить> новый проект".
  2. Выберите тип проекта компонента среда выполнения Windows (универсальная версия Windows).
  3. В этом примере присвойте проекту имя AudioEffectComponent. Это имя будет ссылаться в коде позже.
  4. Щелкните OK.
  5. Шаблон проекта создает класс с именем Class1.cs. В Обозреватель решений щелкните правой кнопкой мыши значок Class1.cs и выберите "Переименовать".
  6. Переименуйте файл в ExampleAudioEffect.cs. Visual Studio отобразит запрос на обновление всех ссылок на новое имя. Нажмите кнопку Да.
  7. Откройте ExampleAudioEffect.cs и обновите определение класса, чтобы реализовать интерфейс IBasicAudioEffect.
public sealed class ExampleAudioEffect : IBasicAudioEffect

Чтобы получить доступ ко всем типам, используемым в примерах в этой статье, необходимо включить следующие пространства имен в файл класса эффектов.

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

Реализация интерфейса IBasicAudioEffect

Звуковой эффект должен реализовать все методы и свойства интерфейса IBasicAudioEffect. В этом разделе описывается простая реализация этого интерфейса для создания базового эффекта эхо.

Свойство SupportedEncodingProperties

Система проверяет свойство SupportedEncodingProperties , чтобы определить, какие свойства кодирования поддерживаются вашим эффектом. Обратите внимание, что если потребитель эффекта не может закодировать звук с помощью указанных свойств, система вызовет close on your effect и удалит эффект из звукового конвейера. В этом примере объекты AudioEncodingProperties создаются и добавляются в возвращенный список для поддержки 44,1 кГц и 48 кГц, 32-разрядной кодировки с плавающей запятой.

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

Метод SetEncodingProperties

Системные вызовы SetEncodingProperties по вашему эффекту позволяют узнать свойства кодирования для звукового потока, на котором работает эффект. Чтобы реализовать эффект эхо, в этом примере используется буфер для хранения одного секунды звуковых данных. Этот метод позволяет инициализировать размер буфера до количества выборок в одну секунду звука на основе частоты выборки, в которой кодируется звук. Эффект задержки также использует целочисленный счетчик для отслеживания текущей позиции в буфере задержки. Так как SetEncodingProperties вызывается всякий раз, когда эффект добавляется в звуковой конвейер, это хорошее время для инициализации этого значения до 0. Вы также можете записать объект AudioEncodingProperties, переданный в этот метод, чтобы использовать его в другом месте.

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

Метод SetProperties

Метод SetProperties позволяет приложению, использующее эффект для настройки параметров эффекта. Свойства передаются в виде карты IPropertySet имен и значений свойств.

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

Этот простой пример будет смешивать текущий образец звука со значением из буфера задержки в соответствии со значением свойства Mix . Свойство объявляется, и TryGetValue используется для получения значения, заданного вызывающим приложением. Если значение не задано, используется значение по умолчанию 5. Обратите внимание, что это свойство доступно только для чтения. Значение свойства должно быть задано с помощью SetProperties.

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

Метод ProcessFrame

Метод ProcessFrame заключается в том, что эффект изменяет звуковые данные потока. Метод вызывается один раз на кадр и передается объект ProcessAudioFrameContext . Этот объект содержит входной объект AudioFrame, содержащий входящие кадры для обработки, и выходной объект AudioFrame, в который записываются звуковые данные, которые будут переданы остальной части звукового конвейера. Аудиокадр — это буфер звуковых примеров, представляющих короткий срез звуковых данных.

Для доступа к буферу данных аудиофрейма требуется COM-взаимодействие, поэтому необходимо включить пространство имен System.Runtime.InteropServices в файл класса эффектов , а затем добавить следующий код в пространство имен для импорта интерфейса для доступа к буферу звука.

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

Примечание.

Так как этот метод обращается к собственному неуправляемым буферу изображений, необходимо настроить проект, чтобы разрешить небезопасный код.

  1. В Обозреватель решений щелкните правой кнопкой мыши проект AudioEffectComponent и выберите "Свойства".
  2. Перейдите на вкладку Сборка.
  3. Установите флажок "Разрешить небезопасный код".

 

Теперь можно добавить реализацию метода ProcessFrame в эффект. Во-первых, этот метод получает объект AudioBuffer как из входных, так и выходных аудиокадров. Обратите внимание, что выходной кадр открыт для записи и ввода для чтения. Затем для каждого буфера получается IMemoryBufferReference путем вызова CreateReference. Затем фактический буфер данных получается путем приведения объектов IMemoryBufferReference в качестве интерфейса взаимодействия COM, определенного выше, IMemoryByteAccess, а затем вызывая GetBuffer.

Теперь, когда буферы данных получены, можно считывать из входного буфера и записывать в выходной буфер. Для каждого примера в входномbuffer значение получается и умножается на 1 — смешивание , чтобы задать значение сухого сигнала эффекта. Затем образец извлекается из текущей позиции в буфере эхо и умножается на mix , чтобы задать мокрое значение эффекта. Выходной пример имеет значение суммы сухих и влажных значений. Наконец, каждый входной образец хранится в эхо-буфере, и текущий индекс образца увеличивается.

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

Метод Close

Система вызовет метод Close Close в классе, когда эффект должен завершить работу. Этот метод следует использовать для удаления созданных ресурсов. Аргументом метода является MediaEffectClosedReason , который позволяет узнать, был ли эффект закрыт обычно, если произошла ошибка, или если эффект не поддерживает требуемый формат кодирования.

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

Метод DiscardQueuedFrames

Метод DiscardQueuedFrames вызывается при сбросе эффекта. Типичный сценарий заключается в том, что эффект сохраняет ранее обработанные кадры для обработки текущего кадра. При вызове этого метода следует удалить набор сохраненных предыдущих кадров. Этот метод можно использовать для сброса состояния, связанного с предыдущими кадрами, а не только с накопленными звуковыми кадрами.

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

Свойство TimeIndependent

Свойство TimeIndependent TimeIndependent позволяет системе знать, не требуется ли для вашего эффекта однородное время. Если задано значение true, система может использовать оптимизации, повышающие производительность эффекта.

public bool TimeIndependent { get { return true; } }

Свойство UseInputFrameForOutput

Задайте свойству UseInputFrameForOutput значение true, чтобы сообщить системе, что результат будет записывать выходные данные в звуковой буфер входного кадра объекта ProcessAudioFrameContext, переданный в ProcessFrame, а не записывать в выходной кадр.

public bool UseInputFrameForOutput { get { return false; } }

Добавление пользовательского эффекта в приложение

Чтобы использовать звуковой эффект из приложения, необходимо добавить ссылку на проект эффекта в приложение.

  1. В Обозреватель решений в проекте приложения щелкните правой кнопкой мыши ссылки и выберите "Добавить ссылку".
  2. Разверните вкладку "Проекты" , выберите "Решение" и установите флажок для имени проекта эффекта. В этом примере имя — AudioEffectComponent.
  3. Нажмите кнопку ОК.

Если класс звуковых эффектов объявлен другим пространством имен, обязательно включите это пространство имен в файл кода.

using AudioEffectComponent;

Добавление пользовательского эффекта в узел AudioGraph

Общие сведения об использовании звуковых графов см. в разделе "Звуковые графы". В следующем фрагменте кода показано, как добавить пример эффекта эхо, показанного в этой статье, на узел звукового графа. Сначала создается свойство PropertySet, а для свойства Mix, определенного эффектом, устанавливается значение. Далее вызывается конструктор AudioEffectDefinition, передав полное имя класса пользовательского типа эффекта и набор свойств. Наконец, определение эффекта добавляется в свойство EffectDefinitions существующего FileInputNode, что приводит к обработке звука, созданного пользовательским эффектом.

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

После добавления в узел настраиваемый эффект можно отключить путем вызова DisableEffectsByDefinition и передачи объекта AudioEffectDefinition. Дополнительные сведения об использовании звуковых графов в приложении см. в разделе AudioGraph.

Добавление пользовательского эффекта в клип в MediaComposition

В следующем фрагменте кода демонстрируется добавление пользовательского звукового эффекта в клип и фоновую звуковую дорожку в композиции мультимедиа. Общие рекомендации по созданию композиций мультимедиа из клипов и добавлению фоновых звуковых треков см. в разделе "Композиции мультимедиа" и редактирование.

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