Как выровнять стереовход и применить звуковой эффект только к одному каналу на iOS?
мне нужно обработать стереофонический аудиофайл на iOS следующим образом:
- оба канала должны иметь одинаковую интенсивность, то есть. сделайте стерео выглядеть как моно
- маршрута моно аудио на левый и правый каналы
- применить эффекты к аудио, которое выводится на правый канал
в настоящее время у меня есть:
+-------------------+
| AVAudioPlayerNode +------------------------+
+--------^----------+ |
| |
+--------+---------+ +--------v---------+
File ---> AVAudioPCMBuffer | | AVAudioMixerNode +---> Output
+--------+---------+ +--------^---------+
| |
+--------v----------+ +-------------------+ |
| AVAudioPlayerNode +--> AVAudioUnitEffect +-+
+-------------------+ +-------------------+
эффект является подклассом AVAudioUnitEffect.
у меня проблемы создание стереовхода в виде моно и вывод AVAudioPlayerNode на отдельные каналы.
Я попытался установить громкость PlayerNodes в 0.5 и pan в -1.0 и 1.0, но, поскольку вход стерео, это не дает желаемых эффектов.
С AVFoundation, я полагаю, что у меня есть по крайней мере два варианта: либо я...
(1) выровняйте каналы для PlayerNodes, чтобы оба PlayerNodes выглядели как моно-после чего я мог бы использовать ту же логику, что и раньше: имея равный объем на обоих PlayerNodes, другое панорамирование влево и вправо и применение эффекта на одном PlayerNode, после MixerNode, результат эффект появляется только в правом выходном канале.
(2) Держите PlayerNodes как стерео (pan = 0.0), применяйте эффект только к одному PlayerNode, а затем скажите MixerNode использовать оба канала одного PlayerNode в качестве источника для левого канала и каналов другого для правого канала. Я полагаю, что тогда MixerNode эффективно уравнял бы вход каналы, так что это будет выглядеть как вход моно и эффект можно услышать только от одного выходного канала.
вопрос в том, возможна ли любая из вышеупомянутых стратегий и как? Есть ли другой вариант, который я упустил?
Я использую Swift для проекта, но могу справиться с Objective-C.
судя по отсутствию ответов и моим собственным исследованиям, мне кажется, что AVFoundation может быть не так. Простота использования AVFoundation заманчиво, но я открыт для альтернатив. В настоящее время я исследую MTAudioProcessingTap
-классах и они могут быть полезны. Помощь по-прежнему ценится.
1 ответов
мне удалось получить желаемый результат, используя два AVPlayers, которые я играю одновременно. Один AVPlayer имеет вход, который имеет усредненные звуковые данные на левом канале и тишину справа; и наоборот в другом AVPlayer. Наконец, эффект применяется только к одному экземпляру AVPlayer.
поскольку применение проприетарного эффекта на экземпляре AVPlayer оказалось тривиальным, самым большим препятствием было выравнивание стереовхода.
я нашел пару смежных вопросы (панорамирование моносигнала с помощью MultiChannelMixer & MTAudioProcessingTap, AVPlayer воспроизведение одноканального аудио стерео→моно) и учебник (обработка аудио AVPlayer с MTAudioProcessingTap - на который ссылались почти во всех других учебниках, которые я пытался google), все из которых указали, что решение, вероятно, находится в MTAudioProcessingTap.
к сожалению, официальная документация для MTAudioProcessing tap (или любой другой аспект MediaToolbox) более или менее шь. Я имею в виду, только пример кода был найден онлайн и заголовки (MTAudioProcessingTap.h) через Xcode. Но с вышеупомянутый учебник мне удалось запустить.
чтобы сделать вещи не слишком легкими, я решил использовать Swift, а не Objective-C, в котором были доступны существующие учебники. Преобразование вызовов было не так уж плохо, и я даже нашел почти готов пример создание MTAudioProcessingTap в Swift 2. Мне удалось зацепиться за обработку кранов и слегка манипулировать аудио с ним (ну, я мог бы вывести поток как есть и полностью обнулить его, по крайней мере). Однако выравнивание каналов было задачей для ускорить рамки, а именно vDSP часть.
однако, используя API C, которые широко используют указатели (case in point: vDSP) с Swift становится громоздким, а быстро!--11--> - по крайней мере, по сравнению с тем, как это делается с Objective-C. Это также было проблемой, когда я изначально писал MTAudioProcessingTaps в Swift: я не мог передать AudioTapContext без сбоев (в Obj-C получение контекста так же просто, как AudioTapContext *context = (AudioTapContext *)MTAudioProcessingTapGetStorage(tap);
) и все UnsafeMutablePointers заставили меня думать, что Swift не является правильным инструментом для работы.
так, для класса обработки, я угробил Свифт и переработать это в Objective-С.
И, как упоминалось ранее, я использую два AVPlayers; так в AudioPlayerController.Свифт У меня:
var left = AudioTap.create(TapType.L)
var right = AudioTap.create(TapType.R)
asset = AVAsset(URL: audioList[index].assetURL!) // audioList is [MPMediaItem]. asset is class property
let leftItem = AVPlayerItem(asset: asset)
let rightItem = AVPlayerItem(asset: asset)
var leftTap: Unmanaged<MTAudioProcessingTapRef>?
var rightTap: Unmanaged<MTAudioProcessingTapRef>?
MTAudioProcessingTapCreate(kCFAllocatorDefault, &left, kMTAudioProcessingTapCreationFlag_PreEffects, &leftTap)
MTAudioProcessingTapCreate(kCFAllocatorDefault, &right, kMTAudioProcessingTapCreationFlag_PreEffects, &rightTap)
let leftParams = AVMutableAudioMixInputParameters(track: asset.tracks[0])
let rightParams = AVMutableAudioMixInputParameters(track: asset.tracks[0])
leftParams.audioTapProcessor = leftTap?.takeUnretainedValue()
rightParams.audioTapProcessor = rightTap?.takeUnretainedValue()
let leftAudioMix = AVMutableAudioMix()
let rightAudioMix = AVMutableAudioMix()
leftAudioMix.inputParameters = [leftParams]
rightAudioMix.inputParameters = [rightParams]
leftItem.audioMix = leftAudioMix
rightItem.audioMix = rightAudioMix
// leftPlayer & rightPlayer are class properties
leftPlayer = AVPlayer(playerItem: leftItem)
rightPlayer = AVPlayer(playerItem: rightItem)
leftPlayer.play()
rightPlayer.play()
я использую "TapType" для разделения каналов, и он определяется (в Objective-C) так же просто, как:
typedef NS_ENUM(NSUInteger, TapType) {
TapTypeL = 0,
TapTypeR = 1
};
mtaudioprocessingtap обратные вызовы создаются в значительной степени так же, как в учебнике. Однако при создании Я сохраняю TapType в контекст, чтобы проверить его в ProcessCallback:
static void tap_InitLeftCallback(MTAudioProcessingTapRef tap, void *clientInfo, void **tapStorageOut) {
struct AudioTapContext *context = calloc(1, sizeof(AudioTapContext));
context->channel = TapTypeL;
*tapStorageOut = context;
}
и, наконец, фактическая тяжелая атлетика происходит в процессе обратного вызова с vDSP функции:
static void tap_ProcessCallback(MTAudioProcessingTapRef tap, CMItemCount numberFrames, MTAudioProcessingTapFlags flags, AudioBufferList *bufferListInOut, CMItemCount *numberFramesOut, MTAudioProcessingTapFlags *flagsOut) {
// output channel is saved in context->channel
AudioTapContext *context = (AudioTapContext *)MTAudioProcessingTapGetStorage(tap);
// this fetches the audio for processing (and for output)
OSStatus status;
status = MTAudioProcessingTapGetSourceAudio(tap, numberFrames, bufferListInOut, flagsOut, NULL, numberFramesOut);
// NB: we assume the audio is interleaved stereo, which means the length of mBuffers is 1 and data alternates between L and R in `size` intervals.
// If audio wasn’t interleaved, then L would be in mBuffers[0] and R in mBuffers[1]
uint size = bufferListInOut->mBuffers[0].mDataByteSize / sizeof(float);
float *left = bufferListInOut->mBuffers[0].mData;
float *right = left + size;
// this is where we equalize the stereo
// basically: L = (L + R) / 2, and R = (L + R) / 2
// which is the same as: (L + R) * 0.5
// ”vasm” = add two vectors (L & R), multiply by scalar (0.5)
float div = 0.5;
vDSP_vasm(left, 1, right, 1, &div, left, 1, size);
vDSP_vasm(right, 1, left, 1, &div, right, 1, size);
// if we would end the processing here the audio would be virtually mono
// however, we want to use distinct players for each channel, so here we zero out (multiply the data by 0) the other
float zero = 0;
if (context->channel == TapTypeL) {
vDSP_vsmul(right, 1, &zero, right, 1, size);
} else {
vDSP_vsmul(left, 1, &zero, left, 1, size);
}
}