2016-04-14 20 views
8

Ho bisogno di elaborare un file audio stereo su iOS come segue:Come equalizzare l'ingresso stereo e applicare l'effetto audio solo al canale singolo su iOS?

  • Entrambi i canali devono avere pari intensità, vale a dire. rendere lo stereo apparire come mono
  • indirizzare l'audio mono ad entrambi i canali sinistro e destro
  • Applicare effetti all'audio, che è l'uscita al canale destro

Quello che ho attualmente è:

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

L'effetto è una sottoclasse di AVAudioUnitEffect.

Sto riscontrando problemi nel rendere l'ingresso stereo visualizzato come mono e nella trasmissione di AVAudioPlayerNode su canali separati.

Ho provato a impostare il volume PlayerNodes su 0,5 e pan a -1.0 e 1.0, ma, poiché l'ingresso è stereo, questo non produce gli effetti desiderati.

Con AVFoundation, immagino ho almeno due opzioni: o io ...

(1) equalizzare i canali per PlayerNodes così entrambe PlayerNodes appaiono come mono - dopo che ho potuto usare la stessa logica di prima: avere uguale volume su entrambi i PlayerNode, altri panning verso sinistra e verso destra e applicando l'effetto su un PlayerNode, dopo che MixerNode, l'effetto apparirà solo nel canale di uscita destro.

(2) Mantenere i PlayerNode come stereo (pan = 0.0), applicare l'effetto solo su un PlayerNode e quindi dire al MixerNode di utilizzare i due canali di PlayerNode come sorgente per il canale sinistro e i canali dell'altro per il diritto canale. Suppongo quindi che il MixerNode possa effettivamente equalizzare i canali di ingresso in modo che appaia come l'ingresso sia mono e l'effetto può essere ascoltato solo da un canale di uscita.

La domanda è: è possibile una delle strategie di cui sopra e come? C'è un'altra opzione che ho trascurato?

Sto usando Swift per il progetto, ma posso farcela con Objective-C.


A giudicare dalla mancanza di risposte e mia ricerca, mi sembra AVFoundation potrebbe non essere la strada da percorrere. La semplicità che utilizza AVFoundation è allettante, ma sono aperto a alternative. Attualmente sto ricercando le classi MTAudioProcessingTap e potrebbero essere utili. L'aiuto è ancora apprezzato.

+1

Ci può spiegare che cosa intendi quando dici "fai apparire lo stereo come mono", per favore? – mark

+0

Ho un file audio stereo. Quando si suona normalmente il canale sinistro viene emesso sul canale sinistro e sul canale destro verso destra; se faccio una panoramica a sinistra, sento solo ciò che è sul canale sinistro e viceversa. Voglio prendere entrambi i canali dell'audio ed emettere entrambi i canali (= media: '(L + R)/2') sul canale sinistro e sul canale destro. Dovrebbe essere come se avessi un audio mono che viene emesso indipendentemente su ciascun canale. Ora, se eseguo il pan dell'audio, non dovrebbe esserci alcuna differenza. Dopo questo, applicherei un effetto solo al canale destro, quindi se faccio una panoramica, sento la normale versione mono sulla sinistra e la versione mono modificata sulla destra. –

risposta

3

Sono riuscito a ottenere il risultato desiderato utilizzando due AVPlayer che suono contemporaneamente. È stato introdotto un AVPlayer che ha mediato i dati audio sul canale sinistro e il silenzio sulla destra; e viceversa nell'altro AVPlayer. Infine, l'effetto viene applicato solo a un'istanza di AVPlayer.

Poiché applicare l'effetto proprietario su un'istanza di AVPlayer si è rivelata banale, l'ostacolo più grande è stato come equalizzare l'input stereo.

ho trovato un paio di domande correlate (Panning a mono signal with MultiChannelMixer & MTAudioProcessingTap, AVPlayer playback of single channel audio stereo→mono) e un tutorial (Processing AVPlayer’s audio with MTAudioProcessingTap - che è stato riferito a in quasi tutti gli altri tutorial che ho cercato di google) ognuno dei quali ha indicato la soluzione probabilmente si trova all'interno MTAudioProcessingTap.

Purtroppo, la documentazione ufficiale per il tocco MTAudioProcessing (o qualsiasi altro aspetto di MediaToolbox) è più o meno nil. Voglio dire, solo some sample code è stato trovato online e le intestazioni (MTAudioProcessingTap.h) tramite Xcode. Ma con il aforementioned tutorial sono riuscito a iniziare.

Per rendere le cose non troppo facili, ho deciso di utilizzare Swift, anziché Objective-C, in cui erano disponibili tutorial esistenti. La conversione delle chiamate non era poi così male e ho persino trovato quasi pronto example of creating MTAudioProcessingTap in Swift 2. Sono riuscito ad agganciare i tap e a manipolare leggermente l'audio con esso (beh, potrei emettere il flusso così com'è e azzerarlo completamente, almeno). Per equalizzare i canali, tuttavia, era un compito per lo Accelerate framework, ovvero la parte di vDSP.

Tuttavia, utilizzando le API C che utilizzano ampiamente i puntatori (caso in punto: vDSP) con Swift gets cumbersome rather quickly - almeno il confronto con il modo in cui è fatto con Objective-C. Questo è stato anche un problema quando ho inizialmente scritto MTAudioProcessingTaps in Swift: non potevo passare AudioTapContext senza errori (in Obj-C ottenere il contesto è facile come AudioTapContext *context = (AudioTapContext *)MTAudioProcessingTapGetStorage(tap);) e tutti gli UnsafeMutablePointers mi hanno fatto pensare che Swift non è lo strumento giusto per il lavoro.

Quindi, per la classe di elaborazione, ho abbandonato Swift e l'ho refactored in Objective-C.
E, come accennato in precedenza, sto usando due AVPlayer; così in AudioPlayerController.swift ho:

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() 

io uso “TapType” per i canali distinti ed è definita (in Objective-C), il più semplice:

typedef NS_ENUM(NSUInteger, TapType) { 
    TapTypeL = 0, 
    TapTypeR = 1 
}; 

callback MTAudioProcessingTap vengono creati abbastanza allo stesso modo di in the tutorial. Sulla creazione, però, risparmio il TapType al contesto in modo da poter controllare in ProcessCallback:

static void tap_InitLeftCallback(MTAudioProcessingTapRef tap, void *clientInfo, void **tapStorageOut) { 
    struct AudioTapContext *context = calloc(1, sizeof(AudioTapContext)); 
    context->channel = TapTypeL; 
    *tapStorageOut = context; 
} 

E, infine, il sollevamento pesi reale accade nel callback di processo con funzioni 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); 
    } 
} 
+0

che bella risposta tecnologicamente densa! –