我成功地使用了两个AVPlayer同时播放来达到期望的效果。一个AVPlayer在左声道上有平均音频数据和右声道上有静音,而另一个AVPlayer则相反。最后,该效果仅应用于一个AVPlayer实例。
由于将专有效果应用于AVPlayer实例非常简单,因此最大的障碍是如何使立体声输入均衡。
我发现了一些相关问题(
Panning a mono signal with MultiChannelMixer & MTAudioProcessingTap,
AVPlayer playback of single channel audio stereo→mono)和教程(
Processing AVPlayer’s audio with MTAudioProcessingTap——几乎所有我尝试谷歌搜索的其他教程都引用了它),它们都表明解决方案可能在MTAudioProcessingTap中。
可悲的是,MTAudioProcessing tap(或MediaToolbox的任何其他方面)的官方文档几乎为零。我的意思是,只有一些示例代码可以在网上找到,并且通过Xcode找到了标题(MTAudioProcessingTap.h)。但是通过上述教程,我设法开始了。
为了不让事情变得太容易,我决定使用Swift而不是Objective-C,因为已经有现成的教程可用。转换调用并不那么困难,我甚至找到了一个几乎准备好的
在Swift 2中创建MTAudioProcessingTap示例。我成功地挂接了处理tap,并轻松操纵音频(嗯——我至少可以输出流并将其完全归零)。然而,均衡通道是
Accelerate框架的任务,即其中的
vDSP部分。
然而,使用大量指针的C APIs(例如:vDSP)与Swift一起使用
变得很快繁琐——至少与Objective-C相比是如此。这也是当我最初用Swift编写MTAudioProcessingTaps时遇到的问题:我无法在不失败的情况下传递AudioTapContext(在Obj-C中,获取上下文就像
AudioTapContext *context = (AudioTapContext *)MTAudioProcessingTapGetStorage(tap);
这样简单),而且所有的UnsafeMutablePointers让我觉得Swift并不适合这项工作。
因此,对于处理类,我放弃了Swift并在Objective-C中进行了重构。
正如之前提到的,我正在使用两个AVPlayer;所以在AudioPlayerController.swift中我有:
var left = AudioTap.create(TapType.L)
var right = AudioTap.create(TapType.R)
asset = AVAsset(URL: audioList[index].assetURL!)
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 = 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) {
AudioTapContext *context = (AudioTapContext *)MTAudioProcessingTapGetStorage(tap);
OSStatus status;
status = MTAudioProcessingTapGetSourceAudio(tap, numberFrames, bufferListInOut, flagsOut, NULL, numberFramesOut);
uint size = bufferListInOut->mBuffers[0].mDataByteSize / sizeof(float);
float *left = bufferListInOut->mBuffers[0].mData;
float *right = left + size;
float div = 0.5;
vDSP_vasm(left, 1, right, 1, &div, left, 1, size);
vDSP_vasm(right, 1, left, 1, &div, right, 1, size);
float zero = 0;
if (context->channel == TapTypeL) {
vDSP_vsmul(right, 1, &zero, right, 1, size);
} else {
vDSP_vsmul(left, 1, &zero, left, 1, size);
}
}
(L + R) / 2
),就像我有一个独立输出到每个声道的单声道音频一样。现在,如果我平移音频,不应该有任何区别。之后,我会仅对右声道应用一个效果,因此,如果我平移,我会在左侧听到正常的单声道版本,在右侧听到修改后的单声道版本。 - Jari Keinänen