2016-04-14 8 views
8

Ich brauche eine Stereo-Audiodatei auf iOS zu verarbeiten, wie folgt:Wie wird der Stereo-Eingang entzerrt und der Audio-Effekt nur auf einen einzelnen Kanal auf iOS angewendet?

  • Beide Kanäle sollten gleiche Intensität haben, dh. machen die Stereo als Mono erscheinen
  • Verlegen Sie das Mono-Audio auf der linken und rechten Kanäle
  • Anwenden von Effekten auf das Audio die Ausgabe an den rechten Kanal ist

Was ich zur Zeit haben, ist:

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

Der Effekt ist eine Unterklasse von AVAudioUnitEffect.

Ich habe Probleme, den Stereo-Eingang als Mono erscheinen zu lassen und AVAudioPlayerNode auf separate Kanäle auszugeben.

Ich habe versucht, die Lautstärke der PlayerNodes auf 0,5 und schwenken auf -1,0 und 1,0, aber da der Eingang Stereo ist, führt dies nicht zu gewünschten Effekten.

Mit AVFoundation, Ich denke, ich zumindest zwei Möglichkeiten: entweder ich ...

(1), die Kanäle für PlayerNodes ausgleichen, so dass beiden PlayerNodes als Mono erscheinen - nach dem ich die gleichen Logik wie vor verwenden: mit gleiche Lautstärke auf beiden PlayerNodes, anderes Panning links und anderes rechts und das Anwenden des Effekts auf einen PlayerNode würde nach MixerNode dazu führen, dass der Effekt nur im rechten Ausgangskanal erscheint.

(2) Halten Sie die PlayerNodes als Stereo (Pan = 0.0), wenden Sie den Effekt nur auf einen PlayerNode an und weisen Sie dann den MixerNode an, die beiden Kanäle eines PlayerNode als Quelle für den linken und die anderen Kanäle für die rechte Seite zu verwenden Kanal. Ich nehme an, dann würde der MixerNode die Eingangskanäle effektiv entzerren, so dass er erscheint, da der Eingang Mono ist und der Effekt nur von einem Ausgangskanal gehört werden kann.

Die Frage ist: ist eine der oben genannten Strategien möglich und wie? Gibt es eine andere Option, die ich übersehen habe?

Ich benutze Swift für das Projekt, kann aber Objective-C bewältigen.


Gemessen an der fehlenden Antworten und meine eigene Forschung, scheint es mir AVFoundation vielleicht nicht der richtige Weg zu gehen. Die Einfachheit mit AVFoundation ist verlockend, aber ich bin offen für Alternativen. Momentan forsche ich nach MTAudioProcessingTap -Klassen und sie könnten von Nutzen sein. Hilfe wird immer noch geschätzt.

+1

Können Sie sich erklären, was Du meinst, wenn du sagst "mach die Stereoanlage als Mono", bitte? – mark

+0

Ich habe eine Stereo-Audiodatei. Bei normaler Wiedergabe wird der linke Kanal auf den linken Kanal und der rechte auf den rechten Kanal ausgegeben; Wenn ich nach links schwenke, höre ich nur, was auf dem linken Kanal ist und umgekehrt. Ich möchte die beiden Kanäle des Audios nehmen und beide Kanäle ausgeben (= Mittelwert: '(L + R)/2') zum linken Kanal und zum rechten Kanal. Es sollte so aussehen, als hätte ich ein Mono-Audio, das unabhängig für jeden Kanal ausgegeben wird. Nun, wenn ich den Ton schwenke, sollte es keinen Unterschied geben. Danach würde ich einen Effekt nur auf den rechten Kanal anwenden, also wenn ich schwenken höre ich normale Mono-Version auf der linken und modifizierte Mono-Version auf der rechten Seite. –

Antwort

3

Ich habe das gewünschte Ergebnis erzielt, indem ich zwei AVPlayer benutzt habe, die ich gleichzeitig spiele. Ein AVPlayer hat eine Eingabe, die Audiodaten auf dem linken Kanal und Stille auf der rechten Seite gemittelt hat; und umgekehrt im anderen AVPlayer. Schließlich wird der Effekt nur auf eine AVPlayer-Instanz angewendet.

Da sich die Anwendung des proprietären Effekts auf eine AVPlayer-Instanz als trivial erwies, bestand die größte Hürde darin, den Stereo-Eingang zu entzerren.

fand ich ein paar ähnliche Fragen (Panning a mono signal with MultiChannelMixer & MTAudioProcessingTap, AVPlayer playback of single channel audio stereo→mono) und ein Tutorial (Processing AVPlayer’s audio with MTAudioProcessingTap - die verwiesen wurde alles auf fast die anderen Tutorials, die ich zu Google versucht) alle davon zeigte die Lösung wahrscheinlich innerhalb MTAudioProcessingTap liegt.

Leider ist die offizielle Dokumentation für MTAudioProcessing Tap (oder einen anderen Aspekt von MediaToolbox) mehr oder weniger Null. Ich meine, nur some sample code wurde online gefunden und die Header (MTAudioProcessingTap.h) über Xcode. Aber mit dem gelang es mir zu starten.

Um die Dinge nicht zu einfach zu machen, entschied ich mich für Swift anstelle von Objective-C, in dem vorhandene Tutorials verfügbar waren. Konvertieren der Anrufe war nicht so schlimm und ich fand sogar fast fertig example of creating MTAudioProcessingTap in Swift 2. Ich habe es geschafft, mit der Verarbeitung von Taps zu beginnen und manipuliere leicht Audio damit (nun, ich könnte den Stream so ausgeben, wie er ist und ihn zumindest komplett auf Null stellen). Den Kanal zu entzerren, war jedoch eine Aufgabe für den Accelerate framework, nämlich den vDSP Teil davon.

Verwenden Sie jedoch C-APIs, die häufig Zeiger (Beispiel: vDSP) mit Swift gets cumbersome rather quickly - zumindest im Vergleich zu, wie es mit Objective-C getan wird. Das war auch ein Problem, als ich anfänglich MTAudioProcessingTaps in Swift schrieb: Ich konnte AudioTapContext nicht ohne Fehler weitergeben (in Obj-C ist der Kontext so einfach wie AudioTapContext *context = (AudioTapContext *)MTAudioProcessingTapGetStorage(tap);) und alle UnsafeMutablePointers ließen mich denken, dass Swift nicht das richtige Werkzeug ist für die Arbeit.

Also, für die Verarbeitung Klasse habe ich Swift aufgegeben und refactored es in Objective-C.
Und wie bereits erwähnt, verwende ich zwei AVPlayer; so in AudioPlayerController.swift ich habe:

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

Ich benutze „TapType“, um verschiedene Kanäle und sie definiert ist (in Objective-C) so einfach wie:

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

MTAudioProcessingTap Rückrufe recht erstellt werden ähnlich wie in the tutorial. obwohl ich die TapType zu Kontext Bei der Erzeugung speichern, damit ich es in ProcessCallback überprüfen:

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

Und schließlich die eigentlichen Gewichtheber geschehen in Prozess Rückruf mit VDSP Funktionen:

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

was für eine großartige, technologisch dichte Antwort! –