如何均衡立体声输入并仅将音频效果应用于iOS上的单个频道?

时间:2016-04-14 08:03:46

标签: ios objective-c swift audio avfoundation

我需要在iOS上处理立体声音频文件,如下所示:

  • 两个频道应具有相同的强度,即。使立体声显示为单声道
  • 将单声道音频路由到左右声道
  • 将效果应用于输出到右声道的音频

我目前拥有的是:

            +-------------------+
            | AVAudioPlayerNode +------------------------+
            +--------^----------+                        |
                     |                                   |
            +--------+---------+                +--------v---------+
    File ---> AVAudioPCMBuffer |                | AVAudioMixerNode +---> Output
            +--------+---------+                +--------^---------+
                     |                                   |
            +--------v----------+  +-------------------+ |
            | AVAudioPlayerNode +--> AVAudioUnitEffect +-+
            +-------------------+  +-------------------+

效果是AVAudioUnitEffect的子类。

我无法将立体声输入显示为单声道输出并将AVAudioPlayerNode输出到单独的声道。

我尝试将PlayerNodes的音量设置为0.5并平移到-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 个答案:

答案 0 :(得分:4)

我通过使用两个同时播放的AVPlayers设法获得了预期的结果。一个AVPlayer的输入在左声道上有平均音频数据,在右边有静音;反之亦然在另一个AVPlayer中。最后,效果仅应用于一个AVPlayer实例。

由于在AVPlayer实例上应用专有效果被证明是微不足道的,最大的障碍是如何均衡立体声输入。

我发现了几个相关的问题(Panning a mono signal with MultiChannelMixer & MTAudioProcessingTapAVPlayer playback of single channel audio stereo→mono)和一个教程(Processing AVPlayer’s audio with MTAudioProcessingTap - 几乎所有其他我尝试谷歌的教程都参考了)这表明解决方案可能位于MTAudioProcessingTap。

可悲的是,MTAudioProcessing tap(或MediaToolbox的任何其他方面)的官方文档或多或少 nil。我的意思是,只有some sample code在线找到并且标题(MTAudioProcessingTap.h) )通过Xcode。但随着aforementioned tutorial我成功了。

为了让事情变得不那么容易,我决定使用Swift而不是Objective-C,其中现有的教程可用。转换电话并没有那么糟糕,我甚至找到了几乎准备好的example of creating MTAudioProcessingTap in Swift 2。我确实设法挂钩处理水龙头并用它轻轻地操纵音频(好吧 - 我可以按原样输出流并至少完全归零)。然而,为了均衡频道,Accelerate frameworkvDSP的一项任务,即gets cumbersome rather quickly部分。

但是,使用广泛使用Swift in the tutorial的指针(例如:vDSP)的C API - 至少与使用Objective-C的方式相比。当我最初在Swift中编写MTAudioProcessingTaps时,这也是一个问题:我无法在没有失败的情况下传递AudioTapContext(在Obj-C中获取上下文就像AudioTapContext *context = (AudioTapContext *)MTAudioProcessingTapGetStorage(tap);一样简单)并且所有UnsafeMutablePointers让我认为Swift不是'这是工作的正确工具。

因此,对于处理类,我抛弃了Swift并在Objective-C中重构了它 并且,如前所述,我正在使用两个AVPlayers;所以在AudioPlayerController.swift中我有:

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回调的创建方式与ref几乎相同。但是在创建时,我将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);
    }
}