blob: db2b713dc68d28c18524222c88c108a2bbf6a507 [file] [log] [blame]
/*
* 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 <AVFoundation/AVFoundation.h>
#import <MediaToolbox/MediaToolbox.h>
#import "AVFKernelProcessor.h"
#import <CoreFoundation/CoreFoundation.h>
#import <pthread.h>
#import <objc/message.h>
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<AVFKernelProcessor>(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<AVFKernelProcessor>(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<AVFKernelProcessor>(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<MTAudioProcessingTapRef>(inRefCon);
return MTAudioProcessingTapGetSourceAudio(tapRef, inNumberFrames, ioData, NULL, NULL, NULL);
}