Структура входного буфера в AudioUnit

#c #xcode #audio #buffer #audiounit

#c #xcode #Аудио #буфер #audiounit

Вопрос:

Я написал простой audiounit, который должен поменять местами левый и правый каналы стереоисточника. Перенесенная версия этого кода отлично работала на C для программы командной строки, которая использовала библиотеку BASS, но у меня возникли проблемы с получением того же кода для работы в Xcode для audiounit.

Для ввода в буфер, например, {1, 2, 3, 4, 5, 6}, я бы ожидал, что изменение стерео будет {2, 1, 4, 3, 6, 5}.

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

Первые 4 значения в моем входном буфере: 0.000104 0.000101 0.000080 0.000113

На выходе получается: 0.000101 0.000104 0.000113 0.000080

Я что-то неправильно понял в том, как структурированы буферы ввода / вывода?

 void        First::FirstKernel::Process(    const Float32   *inSourceP,
                                                Float32         *inDestP,
                                                UInt32          inSamplesToProcess,
                                                UInt32          inNumChannels, 
                                                bool            amp;ioSilence )
{


if (!ioSilence) {                                                 

    const Float32 *sourceP = inSourceP;  
    Float32  *destP = inDestP;  
    for (int i = inSamplesToProcess/2; i>0; --i) { 


        *(destP 1) = *sourceP;
        *destP = *(sourceP 1);

        sourceP = sourceP  2;
        destP = destP  2;
}   
}   
}
  

Ответ №1:

Причина, по которой этот код не работает, заключается в том, что вы используете ядра AudioUnit, которые вызывают ваш плагин для обработки одного канала аудиоданных (если я правильно понимаю). Хотя ядра могут быть довольно удобными в некоторых случаях, это определенно не сработает для плагина, который выполняет взаимозависимую стереообработку. Вам передается количество каналов в вашем обратном вызове — вы проверили это значение?

В любом случае, вместо этого вы должны наследовать от AUEffectBase класса и переопределить ProcessBufferLists() метод. Тогда вы получите правильную структуру AudioBufferList, которая содержит буферы без чересстрочной развертки для каждого аудиоканала. Это также даст вам гораздо более точный контроль над процессом рендеринга, чем при использовании ядер.

Редактировать: Хорошо, оказывается, что при обратном вызове ядра всегда передается 1 канал звука. Кроме того, переопределение Render() , как я первоначально предлагал, не лучший способ сделать это. Согласно комментарию в AUEffectBase.h исходном коде:

Если ваше устройство обрабатывает от N до N каналов и между каналами нет взаимодействий, оно может переопределить NewKernel для создания объекта обработки mono для каждого канала. В противном случае не переопределяйте NewKernel, а вместо этого переопределите ProcessBufferLists.

Поскольку AUEffectBase это не является частью «стандартного» кода AudioUnit, вам нужно будет добавить файлы cpp / h в ваш проект. Их можно найти в корне AudioUnit SDK в AudioUnits/AUPublic/OtherBases папке. Итак, для вашего плагина это выглядело бы примерно так:

MyEffect.h:

 #include "AUEffectBase.h"

class MyEffect : public AUEffectBase {
public:
  // Constructor, other overridden methods, etc.
  virtual OSStatus ProcessBufferLists(AudioUnitRenderActionFlags amp;ioActionFlags,
                                      const AudioBufferList amp;inBuffer,
                                      AudioBufferList amp;outBuffer,
                                      UInt32 inFramesToProcess);


private:
  // Private member variables, methods
};
  

MyEffect.cpp:

 // Other stuff ....

OSStatus MyEffect::ProcessBufferLists(AudioUnitRenderActionFlags amp;ioActionFlags,
                                      const AudioBufferList amp;inBuffer,
                                      AudioBufferList amp;outBuffer,
                                      UInt32 inFramesToProcess) {
  const float *srcBufferL = (Float32 *)inBuffer.mBuffers[0].mData;
  const float *srcBufferR = (Float32 *)inBuffer.mBuffers[1].mData;
  float *destBufferL = (Float32 *)outBuffer.mBuffers[0].mData;
  float *destBufferR = (Float32 *)outBuffer.mBuffers[1].mData;

  for(UInt32 frame = 0; frame < inFramesToProcess;   frame) {
    *destBufferL   = *srcBufferL  ;
    *destBufferR   = *srcBufferR  ;
  }
}
  

Комментарии:

1. Спасибо! После долгих усилий мне удалось обнаружить серию сообщений на форуме с людьми, испытывающими ту же проблему, в первую очередь потому, что пример AU, приведенный Apple, не является стереопроцессором. Спасибо за помощь! Однако у меня все еще возникают проблемы с переопределением Render (); прошло некоторое время с тех пор, как я вникал в такого рода вещи, и я немного подзабыл. Во-первых, в заголовочном файле я определяю виртуальную функцию с именем Render, которая принимает те же аргументы, что и в AUEffectBase. Затем в моем cpp-файле я создаю функцию рендеринга ‘First::Render’, которая вызывает функцию процесса… это правильная процедура?

2. Кстати, в текущей версии AudioUnits количество каналов всегда равно 1; Я думаю, это нужно для того, чтобы обеспечить простор для разработки в будущих версиях.

3. @JimmyB Да, оказывается, ядра предназначены строго для N-N обработки, как я и подозревал. Я добавил еще немного информации и пример кода к своему ответу, чтобы отразить это. Надеюсь, это поможет вам, и не забудьте отметить ответ как правильный, если это решит проблему!

4. Без переопределения этих методов таким образом, для каждого канала было бы создано отдельное ядро, верно? В отдельном проекте я пытаюсь реализовать биквадратный фильтр (без взаимодействия между каналами), и это просто дает мне странный эффект кольцевого модулятора. Предыдущий фильтр, который я написал на C с использованием библиотеки BASS, работал просто отлично, но у него был только один канал чередующихся сэмплов. Я думаю, мне нужно лучше понять, как AUs обрабатывают данные, прежде чем я продолжу дальше… Если у вас есть какая-либо информация по этим строкам, я был бы очень благодарен! Еще раз большое спасибо!

5. Ядра @JimmyB — это удобная платформа обработки N-N, построенная поверх AudioUnits, но вам не обязательно их использовать. Если у вас возникли дополнительные проблемы, вам следует задать новый вопрос. 🙂