![](/img/trans.png)
[英]IOS Core Audio - MP3 to WAV working only when 1 channel, how to get stereo?
[英]How to equalize stereo input and apply audio effect only to single channel on iOS?
我需要按以下方式在iOS上處理立體聲音頻文件:
我目前擁有的是:
+-------------------+
| AVAudioPlayerNode +------------------------+
+--------^----------+ |
| |
+--------+---------+ +--------v---------+
File ---> AVAudioPCMBuffer | | AVAudioMixerNode +---> Output
+--------+---------+ +--------^---------+
| |
+--------v----------+ +-------------------+ |
| AVAudioPlayerNode +--> AVAudioUnitEffect +-+
+-------------------+ +-------------------+
該效果是AVAudioUnitEffect的子類。
我在使立體聲輸入顯示為單聲道並將AVAudioPlayerNode輸出到單獨的通道時遇到麻煩。
我試圖將PlayerNodes的音量設置為0.5,平移為-1.0和1.0,但是,由於輸入是立體聲,因此無法產生所需的效果。
使用AVFoundation,我認為我至少有兩個選擇:要么……
(1)均衡PlayerNode的通道,使兩個PlayerNode都顯示為單聲道-之后,我可以使用與之前相同的邏輯:在兩個PlayerNode上具有相同的音量,向左和向右平移另一個音量,並在MixerNode之后對一個PlayerNode施加效果,結果效果僅出現在右輸出通道中。
(2)保持PlayerNodes為立體聲(pan = 0.0),僅在一個PlayerNode上應用效果,然后告訴MixerNode使用一個PlayerNode的兩個通道作為左聲道的源,使用另一個PlayerNode的右聲道。 我猜想,MixerNode將有效地均衡輸入通道,因此當輸入為單聲道且僅從一個輸出通道可聽到效果時,它會出現。
問題是:上述任何一種策略都是可行的,怎么可能? 我還有其他選擇嗎?
我正在為項目使用Swift,但是可以應付Objective-C。
從缺乏回應和我自己的研究來看,在我看來,AVFoundation可能不是走的路。 使用AVFoundation的簡單性很誘人,但我願意接受其他選擇。 目前,我正在研究MTAudioProcessingTap
-class,它們可能有用。 仍然感謝您的幫助。
通過使用同時播放的兩個AVPlayer,我設法獲得了預期的結果。 一個AVPlayer的輸入在左聲道平均音頻數據,在右聲道靜音。 反之亦然。 最后,該效果僅應用於一個AVPlayer實例。
由於在AVPlayer實例上應用專有效果非常簡單,因此最大的障礙是如何均衡立體聲輸入。
我發現了兩個相關的問題( 使用MultiChannelMixer和MTAudioProcessingTap平移單聲道信號 ,單聲道音頻立體聲→單聲道的AVPlayer播放 )和一個教程( 使用MTAudioProcessingTap處理AVPlayer的音頻) —我嘗試嘗試的幾乎所有其他教程都提到了該問題。 google),所有這些都表明解決方案可能位於MTAudioProcessingTap中。
可悲的是,MTAudioProcessing接頭(或MediaToolbox的任何其他方面)的官方文檔或多或少。 我的意思是,僅在網上找到了一些示例代碼 ,並且通過Xcode找到了標頭(MTAudioProcessingTap.h)。 但是通過上述教程,我設法開始了。
為了使事情變得不太容易,我決定使用Swift,而不是使用Objective-C,因為現有的教程都在其中。 轉換調用還不錯,我什至找到了幾乎可以在Swift 2中創建MTAudioProcessingTap的示例 。 我確實設法鈎住了處理抽頭並用它輕輕地操縱音頻(嗯,我可以按原樣輸出流,並且至少將其完全清零)。 但是,均衡通道是Accelerate框架 (即vDSP部分)的一項任務。
但是,使用在Swift中大量使用指針(例如:vDSP)的C API 變得相當麻煩 -至少與使用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()
我使用“ 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);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.