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);
}
}
Können Sie sich erklären, was Du meinst, wenn du sagst "mach die Stereoanlage als Mono", bitte? – mark
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. –