/* * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #import "AVFAudioProcessor.h" #import "AVFMediaPlayer.h" #import #import #import "AVFKernelProcessor.h" #import #import #import static void InitAudioTap(MTAudioProcessingTapRef tapRef, void *clientInfo, void **tapStorageOut); static void FinalizeAudioTap(MTAudioProcessingTapRef tapRef); static void PrepareAudioTap(MTAudioProcessingTapRef tapRef, CMItemCount maxFrames, const AudioStreamBasicDescription *processingFormat); static void UnprepareAudioTap(MTAudioProcessingTapRef tapRef); static void ProcessAudioTap(MTAudioProcessingTapRef tapRef, CMItemCount numberFrames, MTAudioProcessingTapFlags flags, AudioBufferList *bufferListInOut, CMItemCount *numberFramesOut, MTAudioProcessingTapFlags *flagsOut); static OSStatus AVFTapRenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData); class AVFTapContext { public: AVFTapContext(AVFSoundLevelUnitPtr slu, AVFAudioSpectrumUnitPtr spectrum, AVFAudioEqualizerPtr eq) : audioSLU(slu), audioSpectrum(spectrum), audioEQ(eq) { } ~AVFTapContext() { // AudioUnits have already been deallocated by now // shared_ptrs get freed automatically } AudioUnit spectrumUnit; AudioUnit volumeUnit; AudioUnit eqUnit; AudioUnit renderUnit; // the last unit in our chain CMItemCount totalFrames; // Hold on to these while we're running AVFSoundLevelUnitPtr audioSLU; AVFAudioSpectrumUnitPtr audioSpectrum; AVFAudioEqualizerPtr audioEQ; }; @implementation AVFAudioProcessor - (id) init { if ((self = [super init]) != nil) { _soundLevelUnit = AVFSoundLevelUnitPtr(new AVFSoundLevelUnit()); _audioSpectrum = AVFAudioSpectrumUnitPtr(new AVFAudioSpectrumUnit()); _audioEqualizer = AVFAudioEqualizerPtr(new AVFAudioEqualizer()); _volume = 1.0f; _balance = 0.0f; _audioDelay = 0LL; } return self; } - (void) dealloc { _soundLevelUnit = nullptr; _audioSpectrum = nullptr; _audioEqualizer = nullptr; } - (void) setAudioTrack:(AVAssetTrack *)track { if (track != _audioTrack) { // reset the audio mixer if it's already been created // this theoretically should never happen... _mixer = nil; } _audioTrack = track; } - (AVAudioMix*) mixer { if (!self.audioTrack) { return nil; } if (!_mixer) { AVMutableAudioMix *mixer = [AVMutableAudioMix audioMix]; if (mixer) { AVMutableAudioMixInputParameters *audioMixInputParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:self.audioTrack]; if (audioMixInputParameters && [audioMixInputParameters respondsToSelector:@selector(setAudioTapProcessor:)]) { MTAudioProcessingTapCallbacks callbacks; callbacks.version = kMTAudioProcessingTapCallbacksVersion_0; callbacks.clientInfo = (__bridge void *)self; callbacks.init = InitAudioTap; callbacks.finalize = FinalizeAudioTap; callbacks.prepare = PrepareAudioTap; callbacks.unprepare = UnprepareAudioTap; callbacks.process = ProcessAudioTap; MTAudioProcessingTapRef audioProcessingTap; if (noErr == MTAudioProcessingTapCreate(kCFAllocatorDefault, &callbacks, kMTAudioProcessingTapCreationFlag_PreEffects, &audioProcessingTap)) { objc_msgSend(audioMixInputParameters, @selector(setAudioTapProcessor:), audioProcessingTap); CFRelease(audioProcessingTap); // owned by the mixer now mixer.inputParameters = @[audioMixInputParameters]; _mixer = mixer; } } } } return _mixer; } - (void) setVolume:(float)volume { _volume = volume; if (_soundLevelUnit != nullptr) { _soundLevelUnit->setVolume(volume); } } - (void) setBalance:(float)balance { _balance = balance; if (_soundLevelUnit != nullptr) { _soundLevelUnit->setBalance(balance); } } @end void InitAudioTap(MTAudioProcessingTapRef tapRef, void *clientInfo, void **tapStorageOut) { // retain the AU kernels so they don't get freed while we're running AVFAudioProcessor *processor = (__bridge AVFAudioProcessor *)clientInfo; if (processor) { AVFTapContext *context = new AVFTapContext(processor.soundLevelUnit, processor.audioSpectrum, processor.audioEqualizer); *tapStorageOut = context; } } void FinalizeAudioTap(MTAudioProcessingTapRef tapRef) { AVFTapContext *context = (AVFTapContext*)MTAudioProcessingTapGetStorage(tapRef); if (context) { delete context; } } static OSStatus SetupAudioUnit(AudioUnit unit, const AudioStreamBasicDescription *processingFormat, UInt32 maxFrames) { OSStatus status = noErr; if (noErr == status) { status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, processingFormat, sizeof(AudioStreamBasicDescription)); } if (noErr == status) { status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, processingFormat, sizeof(AudioStreamBasicDescription)); } if (noErr == status) { status = AudioUnitSetProperty(unit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFrames, sizeof(UInt32)); } if (noErr == status) { status = AudioUnitInitialize(unit); } return status; } static OSStatus ConnectAudioUnits(AudioUnit source, AudioUnit sink) { AudioUnitConnection connection; connection.sourceAudioUnit = source; connection.sourceOutputNumber = 0; connection.destInputNumber = 0; return AudioUnitSetProperty(sink, kAudioUnitProperty_MakeConnection, kAudioUnitScope_Input, 0, &connection, sizeof(connection)); } AudioUnit FindAudioUnit(OSType type, OSType subType, OSType manu) { AudioUnit audioUnit = NULL; AudioComponentDescription audioComponentDescription; audioComponentDescription.componentType = type; audioComponentDescription.componentSubType = subType; audioComponentDescription.componentManufacturer = manu; audioComponentDescription.componentFlags = 0; audioComponentDescription.componentFlagsMask = 0; AudioComponent audioComponent = AudioComponentFindNext(NULL, &audioComponentDescription); if (audioComponent) { AudioComponentInstanceNew(audioComponent, &audioUnit); } return audioUnit; } void PrepareAudioTap(MTAudioProcessingTapRef tapRef, CMItemCount maxFrames, const AudioStreamBasicDescription *processingFormat) { AVFTapContext *context = (AVFTapContext*)MTAudioProcessingTapGetStorage(tapRef); // Validate the audio format before we enable the processor // Failures here should rarely, if ever, happen so leave the NSLogs in for // easier diagnosis in the field if (processingFormat->mFormatID != kAudioFormatLinearPCM) { NSLog(@"AVFAudioProcessor needs linear PCM"); return; } // Use the convenient kAudioFormatFlagsNativeFloatPacked to check if we can // process the incoming audio if ((processingFormat->mFormatFlags & kAudioFormatFlagsNativeFloatPacked) != kAudioFormatFlagsNativeFloatPacked) { NSLog(@"AVFAudioProcessor needs native endian packed float samples!!"); return; } // Get an instance of our sound level unit context->eqUnit = NULL; if (context->audioEQ != nullptr) { context->eqUnit = NewKernelProcessorUnit(static_pointer_cast(context->audioEQ)); if (context->eqUnit) { OSStatus status = SetupAudioUnit(context->eqUnit, processingFormat, (UInt32)maxFrames); if (noErr != status) { NSLog(@"Error creating audio equalizer unit: %d", status); AudioComponentInstanceDispose(context->eqUnit); context->eqUnit = NULL; } } } context->spectrumUnit = NULL; if (context->audioSpectrum != nullptr) { context->spectrumUnit = NewKernelProcessorUnit(static_pointer_cast(context->audioSpectrum)); if (context->spectrumUnit) { OSStatus status = SetupAudioUnit(context->spectrumUnit, processingFormat, (UInt32)maxFrames); if (noErr != status) { NSLog(@"Error creating audio spectrum unit: %d", status); AudioComponentInstanceDispose(context->spectrumUnit); context->spectrumUnit = NULL; } } } context->volumeUnit = NULL; if (context->audioSLU != nullptr) { context->volumeUnit = NewKernelProcessorUnit(static_pointer_cast(context->audioSLU)); if (context->volumeUnit) { OSStatus status = SetupAudioUnit(context->volumeUnit, processingFormat, (UInt32)maxFrames); if (noErr != status) { NSLog(@"Error setting up Sound Level Unit: %d", status); AudioComponentInstanceDispose(context->volumeUnit); context->volumeUnit = NULL; } } } /* * Use AudioUnitConnections to build a processing graph * The last unit in the chain will be the unit we call to render, it will * pull through the graph until we get to the first, which will fetch samples * via the render proc we install. * * The graph will look like this: * (render proc) -> eqUnit -> spectrumUnit -> volUnit * * This will allow the EQ settings to affect the spectrum output, but not * the volume or balance. */ AudioUnit firstUnit = NULL; context->renderUnit = NULL; // Set initial settings if (context->eqUnit) { if (context->renderUnit) { ConnectAudioUnits(context->renderUnit, context->eqUnit); } context->renderUnit = context->eqUnit; if (!firstUnit) { firstUnit = context->eqUnit; } } if (context->spectrumUnit) { if (context->renderUnit) { ConnectAudioUnits(context->renderUnit, context->spectrumUnit); } context->renderUnit = context->spectrumUnit; if (!firstUnit) { firstUnit = context->spectrumUnit; } } if (context->volumeUnit) { if (context->renderUnit) { ConnectAudioUnits(context->renderUnit, context->volumeUnit); } context->renderUnit = context->volumeUnit; if (!firstUnit) { firstUnit = context->volumeUnit; } } // Set up a render callback on our first unit if (firstUnit) { AURenderCallbackStruct renderCB; renderCB.inputProc = (AURenderCallback)AVFTapRenderCallback; renderCB.inputProcRefCon = (void*)tapRef; AudioUnitSetProperty(firstUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &renderCB, sizeof(renderCB)); } context->totalFrames = 0; } void UnprepareAudioTap(MTAudioProcessingTapRef tapRef) { AVFTapContext *context = (AVFTapContext*)MTAudioProcessingTapGetStorage(tapRef); context->renderUnit = NULL; if (context->spectrumUnit) { AudioUnitUninitialize(context->spectrumUnit); AudioComponentInstanceDispose(context->spectrumUnit); context->spectrumUnit = NULL; } if (context->volumeUnit) { AudioUnitUninitialize(context->volumeUnit); AudioComponentInstanceDispose(context->volumeUnit); context->volumeUnit = NULL; } if (context->eqUnit) { AudioUnitUninitialize(context->eqUnit); AudioComponentInstanceDispose(context->eqUnit); context->eqUnit = NULL; } } void ProcessAudioTap(MTAudioProcessingTapRef tapRef, CMItemCount numberFrames, uint32_t flags, AudioBufferList *bufferListInOut, CMItemCount *numberFramesOut, uint32_t *flagsOut) { AVFTapContext *context = (AVFTapContext*)MTAudioProcessingTapGetStorage(tapRef); OSStatus status = noErr; if (context->renderUnit) { AudioTimeStamp audioTimeStamp; audioTimeStamp.mSampleTime = context->totalFrames; audioTimeStamp.mFlags = kAudioTimeStampSampleTimeValid; status = AudioUnitRender(context->renderUnit, 0, &audioTimeStamp, 0, (UInt32)numberFrames, bufferListInOut); if (noErr != status) { return; } context->totalFrames += numberFrames; *numberFramesOut = numberFrames; } else { MTAudioProcessingTapGetSourceAudio(tapRef, numberFrames, bufferListInOut, flagsOut, NULL, numberFramesOut); } } static OSStatus AVFTapRenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { MTAudioProcessingTapRef tapRef = static_cast(inRefCon); return MTAudioProcessingTapGetSourceAudio(tapRef, inNumberFrames, ioData, NULL, NULL, NULL); }