Jar*_*nen 9 audio objective-c avfoundation ios swift
我需要在iOS上处理立体声音频文件,如下所示:
我现在拥有的是:
+-------------------+
| AVAudioPlayerNode +------------------------+
+--------^----------+ |
| |
+--------+---------+ +--------v---------+
File ---> AVAudioPCMBuffer | | AVAudioMixerNode +---> Output
+--------+---------+ +--------^---------+
| |
+--------v----------+ +-------------------+ |
| AVAudioPlayerNode +--> AVAudioUnitEffect +-+
+-------------------+ +-------------------+
Run Code Online (Sandbox Code Playgroud)
效果是AVAudioUnitEffect的子类.
我无法将立体声输入显示为单声道输出并将AVAudioPlayerNode输出到单独的声道.
我试图将PlayerNodes的音量设置为0.5并平移到-1.0和1.0,但是,由于输入是立体声,因此不会产生所需的效果.
有了AVFoundation,我认为我至少有两个选择:要么我......
(1)均衡PlayerNodes的通道,使得两个PlayerNodes都显示为单声道 - 之后我可以使用与之前相同的逻辑:在两个PlayerNodes上具有相等的音量,其他向左平移和其他向右平移并在MixerNode之后应用效果在一个PlayerNode上,结果效果仅出现在右输出通道中.
(2)将PlayerNodes保持为立体声(pan = 0.0),仅对一个PlayerNode应用效果,然后告诉MixerNode使用一个PlayerNode的两个通道作为左声道的源和另一个通道的右声道.我想,然后MixerNode将有效地均衡输入通道,因此它会显示为输入为单声道,并且只能从一个输出通道听到效果.
问题是:上述任何一种策略是否可能以及如何?还有其他选择我忽略了吗?
我正在使用Swift进行项目,但可以应对Objective-C.
从缺乏回应和我自己的研究来看,在我看来AVFoundation可能不是可行的方法.使用AVFoundation的简单性很诱人,但我对其他选择持开放态度.目前我正在研究 - MTAudioProcessingTap
类,它们可能有用.帮助仍然受到赞赏.
我设法通过使用我同时播放的两个 AVPlayer 来获得所需的结果。一个 AVPlayer 的输入具有左声道平均音频数据和右声道静音;反之亦然。最后,该效果仅应用于一个 AVPlayer 实例。
由于在 AVPlayer 实例上应用专有效果被证明是微不足道的,最大的障碍是如何均衡立体声输入。
我发现一对夫妇的相关问题(平移与MultiChannelMixer&MTAudioProcessingTap单声道信号,单声道立体声音频的AVPlayer播放单声道?)和教程(处理AVPlayer的音频与MTAudioProcessingTap -这是几乎所有我试图在其他教程参考google)所有这些都表明解决方案可能位于 MTAudioProcessingTap 中。
遗憾的是,MTAudioProcessing tap(或 MediaToolbox 的任何其他方面)的官方文档或多或少为零。我的意思是,在网上只找到了一些示例代码,并且通过 Xcode 找到了头文件 (MTAudioProcessingTap.h)。但是通过上述教程,我设法开始了。
为了让事情变得不那么容易,我决定使用 Swift,而不是 Objective-C,后者提供了现有的教程。转换调用并没有那么糟糕,我什至找到了在 Swift 2 中创建 MTAudioProcessingTap 的几乎准备好的示例。我确实设法挂钩处理点击并用它轻轻地操纵音频(好吧,我可以按原样输出流并将其完全归零,至少)。然而,均衡通道是Accelerate 框架的一项任务,即它的vDSP部分。
然而,在 Swift 中使用广泛使用指针的 C API(例如:vDSP)会很快变得麻烦——至少与使用 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!) // 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()
Run Code Online (Sandbox Code Playgroud)
我使用“TapType”来区分通道,它的定义(在 Objective-C 中)非常简单:
typedef NS_ENUM(NSUInteger, TapType) {
TapTypeL = 0,
TapTypeR = 1
};
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
最后,实际的举重发生在使用 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);
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1227 次 |
最近记录: |