diff --git a/EZAudio/EZAudioDevice.h b/EZAudio/EZAudioDevice.h index f943b01..c36a3f4 100644 --- a/EZAudio/EZAudioDevice.h +++ b/EZAudio/EZAudioDevice.h @@ -33,6 +33,14 @@ */ + (NSArray *)inputDevices; +//------------------------------------------------------------------------------ + +/** + Enumerates all the available output devices and returns the result in an NSArray of EZAudioDevice instances. + @return An NSArray of output EZAudioDevice instances. + */ ++ (NSArray *)outputDevices; + #if TARGET_OS_IPHONE /** @@ -42,6 +50,13 @@ */ + (EZAudioDevice *)currentInputDevice; +/** + Provides the current EZAudioDevice that is being used to output audio. + - iOS only + @return An EZAudioDevice instance representing the currently selected ouotput device. + */ ++ (EZAudioDevice *)currentOutputDevice; + //------------------------------------------------------------------------------ /** @@ -52,6 +67,16 @@ + (void)enumerateInputDevicesUsingBlock:(void(^)(EZAudioDevice *device, BOOL *stop))block; +//------------------------------------------------------------------------------ + +/** + Enumerates all the available output devices. + - iOS only + @param block When enumerating this block executes repeatedly for each EZAudioDevice found. It contains two arguments - first, the EZAudioDevice found, then a pointer to a stop BOOL to allow breaking out of the enumeration) + */ ++ (void)enumerateOutputDevicesUsingBlock:(void (^)(EZAudioDevice *device, + BOOL *stop))block; + #elif TARGET_OS_MAC /** @@ -63,15 +88,6 @@ //------------------------------------------------------------------------------ -/** - Enumerates all the available output devices and returns the result in an NSArray of EZAudioDevice instances. - - OSX only - @return An NSArray of output EZAudioDevice instances. - */ -+ (NSArray *)outputDevices; - -//------------------------------------------------------------------------------ - /** Enumerates all the available devices. - OSX only diff --git a/EZAudio/EZAudioDevice.m b/EZAudio/EZAudioDevice.m index 4772c11..d6cdde6 100644 --- a/EZAudio/EZAudioDevice.m +++ b/EZAudio/EZAudioDevice.m @@ -49,6 +49,19 @@ //------------------------------------------------------------------------------ ++ (EZAudioDevice *)currentOutputDevice +{ + AVAudioSession *session = [AVAudioSession sharedInstance]; + AVAudioSessionPortDescription *port = [[[session currentRoute] outputs] firstObject]; + AVAudioSessionDataSourceDescription *dataSource = [session outputDataSource]; + EZAudioDevice *device = [[EZAudioDevice alloc] init]; + device.port = port; + device.dataSource = dataSource; + return device; +} + +//------------------------------------------------------------------------------ + + (NSArray *)inputDevices { __block NSMutableArray *devices = [NSMutableArray array]; @@ -61,6 +74,18 @@ //------------------------------------------------------------------------------ ++ (NSArray *)outputDevices +{ + __block NSMutableArray *devices = [NSMutableArray array]; + [self enumerateOutputDevicesUsingBlock:^(EZAudioDevice *device, BOOL *stop) + { + [devices addObject:device]; + }]; + return devices; +} + +//------------------------------------------------------------------------------ + + (void)enumerateInputDevicesUsingBlock:(void (^)(EZAudioDevice *, BOOL *))block { if (!block) @@ -101,6 +126,42 @@ //------------------------------------------------------------------------------ ++ (void)enumerateOutputDevicesUsingBlock:(void (^)(EZAudioDevice *, BOOL *))block +{ + if (!block) + { + return; + } + + AVAudioSessionRouteDescription *currentRoute = [[AVAudioSession sharedInstance] currentRoute]; + NSArray *portDescriptions = [currentRoute outputs]; + + BOOL stop; + for (AVAudioSessionPortDescription *outputDevicePortDescription in portDescriptions) + { + // add any additional sub-devices + NSArray *dataSources = [outputDevicePortDescription dataSources]; + if (dataSources.count) + { + for (AVAudioSessionDataSourceDescription *outputDeviceDataSourceDescription in dataSources) + { + EZAudioDevice *device = [[EZAudioDevice alloc] init]; + device.port = outputDevicePortDescription; + device.dataSource = outputDeviceDataSourceDescription; + block(device, &stop); + } + } + else + { + EZAudioDevice *device = [[EZAudioDevice alloc] init]; + device.port = outputDevicePortDescription; + block(device, &stop); + } + } +} + +//------------------------------------------------------------------------------ + - (NSString *)name { NSMutableString *name = [NSMutableString string]; diff --git a/EZAudio/EZAudioPlayer.m b/EZAudio/EZAudioPlayer.m index 038290d..fab687c 100644 --- a/EZAudio/EZAudioPlayer.m +++ b/EZAudio/EZAudioPlayer.m @@ -194,12 +194,12 @@ _audioFile = [EZAudioFile audioFileWithURL:audioFile.url]; _audioFile.delegate = self; NSAssert(_output,@"No output was found, this should by default be the EZOutput shared instance"); - [_output setAudioStreamBasicDescription:self.audioFile.clientFormat]; + [_output setInputFormat:self.audioFile.clientFormat]; } -(void)setOutput:(EZOutput*)output { _output = output; - _output.outputDataSource = self; + _output.dataSource = self; } #pragma mark - Methods diff --git a/EZAudio/EZAudioUtilities.h b/EZAudio/EZAudioUtilities.h index 227726e..d945d1d 100644 --- a/EZAudio/EZAudioUtilities.h +++ b/EZAudio/EZAudioUtilities.h @@ -46,6 +46,17 @@ typedef struct TPCircularBuffer circularBuffer; } EZPlotHistoryInfo; +//------------------------------------------------------------------------------ + +/** + A data structure that holds information about a node in the context of an AUGraph. + */ +typedef struct +{ + AudioUnit audioUnit; + AUNode node; +} EZAudioNodeInfo; + //------------------------------------------------------------------------------ #pragma mark - Types //------------------------------------------------------------------------------ diff --git a/EZAudio/EZMicrophone.h b/EZAudio/EZMicrophone.h index 882cff9..6ee32ba 100644 --- a/EZAudio/EZMicrophone.h +++ b/EZAudio/EZMicrophone.h @@ -304,7 +304,9 @@ */ - (AudioUnit *)audioUnit; +//------------------------------------------------------------------------------ #pragma mark - Setters +//------------------------------------------------------------------------------ ///----------------------------------------------------------- /// @name Customizing The Microphone Stream Format diff --git a/EZAudio/EZMicrophone.m b/EZAudio/EZMicrophone.m index 825b2ea..3125fb6 100644 --- a/EZAudio/EZMicrophone.m +++ b/EZAudio/EZMicrophone.m @@ -535,21 +535,23 @@ static OSStatus EZAudioMicrophoneCallback(void *inRefCon, - (void)setOutput:(EZOutput *)output { _output = output; - [_output setAudioStreamBasicDescription:self.audioStreamBasicDescription]; - _output.outputDataSource = self; + _output.inputFormat = self.audioStreamBasicDescription; + _output.dataSource = self; } //------------------------------------------------------------------------------ #pragma mark - EZOutputDataSource //------------------------------------------------------------------------------ -- (void) output:(EZOutput *)output +- (OSStatus) output:(EZOutput *)output shouldFillAudioBufferList:(AudioBufferList *)audioBufferList withNumberOfFrames:(UInt32)frames + timestamp:(const AudioTimeStamp *)timestamp { memcpy(audioBufferList, self.info->audioBufferList, sizeof(AudioBufferList) + (self.info->audioBufferList->mNumberBuffers - 1)*sizeof(AudioBuffer)); + return noErr; } //------------------------------------------------------------------------------ diff --git a/EZAudio/EZOutput.h b/EZAudio/EZOutput.h index 129864b..329967f 100644 --- a/EZAudio/EZOutput.h +++ b/EZAudio/EZOutput.h @@ -29,16 +29,23 @@ #elif TARGET_OS_MAC #import #endif - -#import "TPCircularBuffer.h" +#import "EZAudioDevice.h" @class EZOutput; +//------------------------------------------------------------------------------ +#pragma mark - Constants +//------------------------------------------------------------------------------ + +FOUNDATION_EXPORT UInt32 const EZOutputMaximumFramesPerSlice; +FOUNDATION_EXPORT Float64 const EZOutputDefaultSampleRate; + +//------------------------------------------------------------------------------ +#pragma mark - EZOutputDataSource +//------------------------------------------------------------------------------ + /** - The EZOutputDataSource (required for the EZOutput) specifies a receiver to provide audio data when the EZOutput is started. Only ONE datasource method is expected to be implemented and priority is given as such: - 1.) `output:callbackWithActionFlags:inTimeStamp:inBusNumber:inNumberFrames:ioData:` - 2.) `outputShouldUseCircularBuffer:` - 3.) `output:needsBufferListWithFrames:withBufferSize:` + The EZOutputDataSource (required for the EZOutput) specifies a receiver to provide audio data when the EZOutput is started. Since the 0.4.0 release this has been simplified to only one data source method. */ @protocol EZOutputDataSource @@ -47,40 +54,53 @@ /// @name Pulling The Audio Data ///----------------------------------------------------------- -/** - Provides complete override of the output callback function. The delegate is expected to - @param output The instance of the EZOutput that asked for the data - @param ioActionFlags AudioUnitRenderActionFlags provided by the output callback - @param inTimeStamp AudioTimeStamp reference provided by the output callback - @param inBusNumber UInt32 representing the bus number provided by the output callback - @param inNumberFrames UInt32 representing the number of frames provided by the output callback - @param ioData AudioBufferList pointer representing the audio data that will be used for output provided by the output callback (fill this!) - */ --(void)output:(EZOutput*)output -callbackWithActionFlags:(AudioUnitRenderActionFlags*)ioActionFlags - inTimeStamp:(const AudioTimeStamp*)inTimeStamp - inBusNumber:(UInt32)inBusNumber -inNumberFrames:(UInt32)inNumberFrames - ioData:(AudioBufferList*)ioData; +@required /** - Provides output using a circular - @param output The instance of the EZOutput that asked for the data - @return The EZOutputDataSource's TPCircularBuffer structure holding the audio data in a circular buffer - */ --(TPCircularBuffer*)outputShouldUseCircularBuffer:(EZOutput *)output; - - -/** - Provides a way to provide output with data anytime the EZOutput needs audio data to play. This function provides an already allocated AudioBufferList to use for providing audio data into the output buffer. + Provides a way to provide output with data anytime the EZOutput needs audio data to play. This function provides an already allocated AudioBufferList to use for providing audio data into the output buffer. The expected format of the audio data provided here is specified by the EZOutput `inputFormat` property. This audio data will be converted into the client format specified by the EZOutput `clientFormat` property. @param output The instance of the EZOutput that asked for the data. @param audioBufferList The AudioBufferList structure pointer that needs to be filled with audio data @param frames The amount of frames as a UInt32 that output will need to properly fill its output buffer. - @return A pointer to the AudioBufferList structure holding the audio data. If nil or NULL, will output silence. + @param timestamp A AudioTimeStamp pointer to use if you need the current host time. + @return An OSStatus code. If there was no error then use the noErr status code. */ --(void) output:(EZOutput *)output - shouldFillAudioBufferList:(AudioBufferList*)audioBufferList - withNumberOfFrames:(UInt32)frames; +- (OSStatus) output:(EZOutput *)output + shouldFillAudioBufferList:(AudioBufferList *)audioBufferList + withNumberOfFrames:(UInt32)frames + timestamp:(const AudioTimeStamp *)timestamp; + +@end + +//------------------------------------------------------------------------------ +#pragma mark - EZOutputDelegate +//------------------------------------------------------------------------------ + +@protocol EZOutputDelegate + +@optional + +/** + <#Description#> + @param output <#output description#> + @param device <#device description#> + */ +- (void)output:(EZOutput *)output changedDevice:(EZAudioDevice *)device; + +//------------------------------------------------------------------------------ + +/** + <#Description#> + @param output <#output description#> + @param buffer <#buffer description#> + @param bufferSize <#bufferSize description#> + @param numberOfChannels <#numberOfChannels description#> + */ +- (void) output:(EZOutput *)output + playedAudio:(float **)buffer + withBufferSize:(UInt32)bufferSize + withNumberOfChannels:(UInt32)numberOfChannels; + +//------------------------------------------------------------------------------ @end @@ -89,13 +109,10 @@ inNumberFrames:(UInt32)inNumberFrames */ @interface EZOutput : NSObject -#pragma mark - Properties -/** - The EZOutputDataSource that provides the required AudioBufferList to the output callback function - */ -@property (nonatomic,assign) idoutputDataSource; - +//------------------------------------------------------------------------------ #pragma mark - Initializers +//------------------------------------------------------------------------------ + ///----------------------------------------------------------- /// @name Initializers ///----------------------------------------------------------- @@ -105,29 +122,38 @@ inNumberFrames:(UInt32)inNumberFrames @param dataSource The EZOutputDataSource that will be used to pull the audio data for the output callback. @return A newly created instance of the EZOutput class. */ --(id)initWithDataSource:(id)dataSource; +- (instancetype)initWithDataSource:(id)dataSource; /** Creates a new instance of the EZOutput and allows the caller to specify an EZOutputDataSource. @param dataSource The EZOutputDataSource that will be used to pull the audio data for the output callback. - @param audioStreamBasicDescription The AudioStreamBasicDescription of the EZOutput. - @warning AudioStreamBasicDescriptions that are invalid will cause the EZOutput to fail to initialize + @param inputFormat The AudioStreamBasicDescription of the EZOutput. + @warning AudioStreamBasicDescription input formats must be linear PCM! @return A newly created instance of the EZOutput class. */ --(id) initWithDataSource:(id)dataSource - withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription; +- (instancetype)initWithDataSource:(id)dataSource + inputFormat:(AudioStreamBasicDescription)inputFormat; +//------------------------------------------------------------------------------ #pragma mark - Class Initializers +//------------------------------------------------------------------------------ + ///----------------------------------------------------------- /// @name Class Initializers ///----------------------------------------------------------- +/** + Class method to create a new instance of the EZOutput + @return A newly created instance of the EZOutput class. + */ ++ (instancetype)output; + /** Class method to create a new instance of the EZOutput and allows the caller to specify an EZOutputDataSource. @param dataSource The EZOutputDataSource that will be used to pull the audio data for the output callback. @return A newly created instance of the EZOutput class. */ -+(EZOutput*)outputWithDataSource:(id)dataSource; ++ (instancetype)outputWithDataSource:(id)dataSource; /** Class method to create a new instance of the EZOutput and allows the caller to specify an EZOutputDataSource. @@ -136,10 +162,13 @@ inNumberFrames:(UInt32)inNumberFrames @warning AudioStreamBasicDescriptions that are invalid will cause the EZOutput to fail to initialize @return A newly created instance of the EZOutput class. */ -+(EZOutput*)outputWithDataSource:(id)dataSource - withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription; ++ (instancetype)outputWithDataSource:(id)dataSource + inputFormat:(AudioStreamBasicDescription)inputFormat; +//------------------------------------------------------------------------------ #pragma mark - Singleton +//------------------------------------------------------------------------------ + ///----------------------------------------------------------- /// @name Shared Instance ///----------------------------------------------------------- @@ -148,9 +177,108 @@ inNumberFrames:(UInt32)inNumberFrames Creates a shared instance of the EZOutput (one app will usually only need one output and share the role of the EZOutputDataSource). @return The shared instance of the EZOutput class. */ -+(EZOutput*)sharedOutput; ++ (instancetype)sharedOutput; + +//------------------------------------------------------------------------------ +#pragma mark - Properties +//------------------------------------------------------------------------------ + +///----------------------------------------------------------- +/// @name Setting/Getting The Stream Formats +///----------------------------------------------------------- + +/** + Provides the AudioStreamBasicDescription structure used at the beginning of the playback graph which is then converted into the `clientFormat` using the AUConverter audio unit. + @warning The AudioStreamBasicDescription set here must be linear PCM. Compressed formats are not supported...the EZAudioFile's clientFormat performs the audio conversion on the fly from compressed to linear PCM so there is no additional work to be done there. + @return An AudioStreamBasicDescription structure describing + */ +@property (nonatomic, readwrite) AudioStreamBasicDescription inputFormat; + +//------------------------------------------------------------------------------ + +/** + Provides the AudioStreamBasicDescription structure that serves as the common format used throughout the playback graph (similar to how the EZAudioFile as a clientFormat that is linear PCM to be shared amongst other components). The `inputFormat` is converted into this format at the beginning of the playback graph using an AUConverter audio unit. Defaults to the whatever the `defaultClientFormat` method returns is if a custom one isn't explicitly set. + @warning The AudioStreamBasicDescription set here must be linear PCM. Compressed formats are not supported by Audio Units. + @return An AudioStreamBasicDescription structure describing the common client format for the playback graph. + */ +@property (nonatomic, readwrite) AudioStreamBasicDescription clientFormat; + +//------------------------------------------------------------------------------ + +/** + The EZOutputDataSource that provides the audio data in the `inputFormat` for the EZOutput to play. + */ +@property (nonatomic, weak) id dataSource; + +//------------------------------------------------------------------------------ + +/** + The EZOutputDelegate for which to handle the output callbacks + */ +@property (nonatomic, weak) id delegate; + +//------------------------------------------------------------------------------ + +/** + Provides a flag indicating whether the EZOutput is pulling audio data from the EZOutputDataSource for playback. + @return YES if the EZOutput is running, NO if it is stopped + */ +@property (readonly) BOOL isPlaying; + +//------------------------------------------------------------------------------ + +/** + Provides the current volume from the audio player's mixer audio unit in the playback graph. Setting the volume adjusts the gain of the output between 0 and 1. Default is 0.5. + */ +@property (nonatomic, assign) float volume; + +//------------------------------------------------------------------------------ +#pragma mark - Core Audio Properties +//------------------------------------------------------------------------------ + +/** + The AUGraph used to chain together the converter, mixer, and output audio units. + */ +@property (readonly) AUGraph graph; + +//------------------------------------------------------------------------------ + +/** + <#Description#> + */ +@property (readonly) AudioUnit converterAudioUnit; + +//------------------------------------------------------------------------------ + +/** + <#Description#> + */ +@property (readonly) AudioUnit mixerAudioUnit; + +//------------------------------------------------------------------------------ + +/** + <#Description#> + */ +@property (readonly) AudioUnit outputAudioUnit; + +//------------------------------------------------------------------------------ +#pragma mark - Setters +//------------------------------------------------------------------------------ + +///----------------------------------------------------------- +/// @name Getting/Setting The Output's Hardware Device +///----------------------------------------------------------- + +/** + <#Description#> + */ +@property (nonatomic, strong, readwrite) EZAudioDevice *device; + +//------------------------------------------------------------------------------ +#pragma mark - Actions +//------------------------------------------------------------------------------ -#pragma mark - Events ///----------------------------------------------------------- /// @name Starting/Stopping The Output ///----------------------------------------------------------- @@ -158,44 +286,37 @@ inNumberFrames:(UInt32)inNumberFrames /** Starts pulling audio data from the EZOutputDataSource to the default device output. */ --(void)startPlayback; +- (void)startPlayback; + +///----------------------------------------------------------- /** Stops pulling audio data from the EZOutputDataSource to the default device output. */ --(void)stopPlayback; +- (void)stopPlayback; + +//------------------------------------------------------------------------------ +#pragma mark - Subclass +//------------------------------------------------------------------------------ -#pragma mark - Getters ///----------------------------------------------------------- -/// @name Getting The Output Audio Format +/// @name Subclass ///----------------------------------------------------------- /** - Provides the AudioStreamBasicDescription structure containing the format of the microphone's audio. - @return An AudioStreamBasicDescription structure describing the format of the microphone's audio. + The default AudioStreamBasicDescription set as the client format of the output if no custom `clientFormat` is set. Defaults to a 44.1 kHz stereo, non-interleaved, float format. + @return An AudioStreamBasicDescription that will be used as the default stream format. */ --(AudioStreamBasicDescription)audioStreamBasicDescription; +- (AudioStreamBasicDescription)defaultClientFormat; -///----------------------------------------------------------- -/// @name Getting The State Of The Output -///----------------------------------------------------------- +//------------------------------------------------------------------------------ /** - Provides a flag indicating whether the EZOutput is pulling audio data from the EZOutputDataSource for playback. - @return YES if the EZOutput is pulling audio data to the output device, NO if it is stopped + The default AudioStreamBasicDescription set as the `inputFormat` of the output if no custom `inputFormat` is set. Defaults to a 44.1 kHz stereo, non-interleaved, float format. + @return An AudioStreamBasicDescription that will be used as the default stream format. */ --(BOOL)isPlaying; +- (AudioStreamBasicDescription)defaultInputFormat; -#pragma mark - Setters -///----------------------------------------------------------- -/// @name Customizing The Output Format -///----------------------------------------------------------- - -/** - Sets the AudioStreamBasicDescription on the output. - @warning Do not set this during playback. - @param asbd The new AudioStreamBasicDescription to use in place of the current audio format description. - */ --(void)setAudioStreamBasicDescription:(AudioStreamBasicDescription)asbd; +//------------------------------------------------------------------------------ @end diff --git a/EZAudio/EZOutput.m b/EZAudio/EZOutput.m index c9d65fd..06a14e4 100644 --- a/EZAudio/EZOutput.m +++ b/EZAudio/EZOutput.m @@ -26,348 +26,643 @@ #import "EZOutput.h" #import "EZAudioUtilities.h" -@interface EZOutput (){ - BOOL _customASBD; - BOOL _isPlaying; - AudioStreamBasicDescription _outputASBD; - AudioUnit _outputUnit; -} +//------------------------------------------------------------------------------ +#pragma mark - Constants +//------------------------------------------------------------------------------ + +UInt32 const EZOutputMaximumFramesPerSlice = 4096; +Float64 const EZOutputDefaultSampleRate = 44100.0f; + +//------------------------------------------------------------------------------ +#pragma mark - Data Structures +//------------------------------------------------------------------------------ + +typedef struct +{ + // stream format params + AudioStreamBasicDescription inputFormat; + AudioStreamBasicDescription clientFormat; + + // nodes + EZAudioNodeInfo converterNodeInfo; + EZAudioNodeInfo mixerNodeInfo; + EZAudioNodeInfo outputNodeInfo; + + // audio graph + AUGraph graph; +} EZOutputInfo; + +//------------------------------------------------------------------------------ +#pragma mark - Callbacks (Declaration) +//------------------------------------------------------------------------------ + +OSStatus EZOutputConverterInputCallback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData); + +//------------------------------------------------------------------------------ + +OSStatus EZOutputGraphRenderCallback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData); + +//------------------------------------------------------------------------------ +#pragma mark - EZOutput (Interface Extension) +//------------------------------------------------------------------------------ + +@interface EZOutput () +@property (nonatomic, assign) EZOutputInfo *info; @end +//------------------------------------------------------------------------------ +#pragma mark - EZOutput (Implementation) +//------------------------------------------------------------------------------ + @implementation EZOutput -@synthesize outputDataSource = _outputDataSource; -static OSStatus OutputRenderCallback(void *inRefCon, - AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, - UInt32 inBusNumber, - UInt32 inNumberFrames, - AudioBufferList *ioData){ - -// NSLog(@"output something"); - - EZOutput *output = (__bridge EZOutput*)inRefCon; - // Manual override - if ([output.outputDataSource respondsToSelector:@selector(output:callbackWithActionFlags:inTimeStamp:inBusNumber:inNumberFrames:ioData:)]){ - [output.outputDataSource output:output - callbackWithActionFlags:ioActionFlags - inTimeStamp:inTimeStamp - inBusNumber:inBusNumber - inNumberFrames:inNumberFrames - ioData:ioData]; - } - else if ([output.outputDataSource respondsToSelector:@selector(outputShouldUseCircularBuffer:)]){ - - TPCircularBuffer *circularBuffer = [output.outputDataSource outputShouldUseCircularBuffer:output]; - if (!circularBuffer){ - float *left = (float*)ioData->mBuffers[0].mData; - float *right = (float*)ioData->mBuffers[1].mData; - for(int i = 0; i < inNumberFrames; i++){ - left[ i ] = 0.0f; - right[ i ] = 0.0f; - } - return noErr; - }; - - /** - Thank you Michael Tyson (A Tasty Pixel) for writing the TPCircularBuffer, you are amazing! - */ - - // Get the desired amount of bytes to copy - int32_t bytesToCopy = ioData->mBuffers[0].mDataByteSize; - float *left = (float*)ioData->mBuffers[0].mData; - float *right = (float*)ioData->mBuffers[1].mData; - - // Get the available bytes in the circular buffer - int32_t availableBytes; - float *buffer = TPCircularBufferTail(circularBuffer,&availableBytes); - - // Ideally we'd have all the bytes to be copied, but compare it against the available bytes (get min) - int32_t amount = MIN(bytesToCopy,availableBytes); - memcpy( left, buffer, amount); - memcpy( right, buffer, amount); - - // Consume those bytes ( this will internally push the head of the circular buffer) - TPCircularBufferConsume(circularBuffer,amount); - - } - // Provided an AudioBufferList (defaults to silence) - else if ([output.outputDataSource respondsToSelector:@selector(output:shouldFillAudioBufferList:withNumberOfFrames:)]) { - [output.outputDataSource output:output - shouldFillAudioBufferList:ioData - withNumberOfFrames:inNumberFrames]; - } - - return noErr; +//------------------------------------------------------------------------------ +#pragma mark - Dealloc +//------------------------------------------------------------------------------ + +- (void)dealloc +{ + [EZAudioUtilities checkResult:AUGraphStop(self.info->graph) + operation:"Failed to stop graph"]; + [EZAudioUtilities checkResult:AUGraphClose(self.info->graph) + operation:"Failed to close graph"]; + [EZAudioUtilities checkResult:AUGraphUninitialize(self.info->graph) + operation:"Failed to uninitialize graph"]; + free(self.info); } +//------------------------------------------------------------------------------ #pragma mark - Initialization --(id)init { - self = [super init]; - if(self){ - [self _configureOutput]; - } - return self; +//------------------------------------------------------------------------------ + +- (instancetype) init +{ + self = [super init]; + if (self) + { + [self setup]; + } + return self; } --(id)initWithDataSource:(id)dataSource { - self = [super init]; - if(self){ - self.outputDataSource = dataSource; - [self _configureOutput]; - } - return self; +//------------------------------------------------------------------------------ + +- (instancetype)initWithDataSource:(id)dataSource +{ + self = [self init]; + if (self) + { + self.dataSource = dataSource; + } + return self; } --(id) initWithDataSource:(id)dataSource - withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription { - self = [super init]; - if(self){ - _customASBD = YES; - _outputASBD = audioStreamBasicDescription; - self.outputDataSource = dataSource; - [self _configureOutput]; - } - return self; +//------------------------------------------------------------------------------ + +- (instancetype)initWithDataSource:(id)dataSource + inputFormat:(AudioStreamBasicDescription)inputFormat +{ + self = [self initWithDataSource:dataSource]; + if (self) + { + self.inputFormat = inputFormat; + } + return self; } +//------------------------------------------------------------------------------ #pragma mark - Class Initializers -+(EZOutput*)outputWithDataSource:(id)dataSource { - return [[EZOutput alloc] initWithDataSource:dataSource]; +//------------------------------------------------------------------------------ + ++ (instancetype)output +{ + return [[self alloc] init]; } -+(EZOutput *)outputWithDataSource:(id)dataSource - withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription { - return [[EZOutput alloc] initWithDataSource:dataSource withAudioStreamBasicDescription:audioStreamBasicDescription]; +//------------------------------------------------------------------------------ + ++ (instancetype)outputWithDataSource:(id)dataSource +{ + return [[self alloc] initWithDataSource:dataSource]; } +//------------------------------------------------------------------------------ + ++ (instancetype)outputWithDataSource:(id)dataSource + inputFormat:(AudioStreamBasicDescription)inputFormat +{ + return [[self alloc] initWithDataSource:dataSource + inputFormat:inputFormat]; +} + +//------------------------------------------------------------------------------ #pragma mark - Singleton -+(EZOutput*)sharedOutput { - static EZOutput *_sharedOutput = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _sharedOutput = [[EZOutput alloc] init]; - }); - return _sharedOutput; +//------------------------------------------------------------------------------ + ++ (instancetype)sharedOutput +{ + static EZOutput *output; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + output = [[self alloc] init]; + }); + return output; } -#pragma mark - Audio Component Initialization --(AudioComponentDescription)_getOutputAudioComponentDescription { - // Create an output component description for default output device - AudioComponentDescription outputComponentDescription; - outputComponentDescription.componentFlags = 0; - outputComponentDescription.componentFlagsMask = 0; - outputComponentDescription.componentManufacturer = kAudioUnitManufacturer_Apple; - #if TARGET_OS_IPHONE - outputComponentDescription.componentSubType = kAudioUnitSubType_RemoteIO; - #elif TARGET_OS_MAC - outputComponentDescription.componentSubType = kAudioUnitSubType_DefaultOutput; - #endif - outputComponentDescription.componentType = kAudioUnitType_Output; - return outputComponentDescription; -} - --(AudioComponent)_getOutputComponentWithAudioComponentDescription:(AudioComponentDescription)outputComponentDescription { - // Try and find the component - AudioComponent outputComponent = AudioComponentFindNext( NULL , &outputComponentDescription); - NSAssert(outputComponent,@"Couldn't get input component unit!"); - return outputComponent; -} - --(void)_createNewInstanceForOutputComponent:(AudioComponent)outputComponent { - // - [EZAudioUtilities checkResult:AudioComponentInstanceNew( outputComponent, &_outputUnit) - operation:"Failed to open component for output unit"]; -} - -#pragma mark - Configure The Output Unit - -//-(void)_configureOutput { -// -// // Get component description for output -// AudioComponentDescription outputComponentDescription = [self _getOutputAudioComponentDescription]; -// -// // Get the output component -// AudioComponent outputComponent = [self _getOutputComponentWithAudioComponentDescription:outputComponentDescription]; -// -// // Create a new instance of the component and store it for internal use -// [self _createNewInstanceForOutputComponent:outputComponent]; -// -//} +//------------------------------------------------------------------------------ +#pragma mark - Setup +//------------------------------------------------------------------------------ +- (void)setup +{ + // + // Create structure to hold state data + // + self.info = (EZOutputInfo *)malloc(sizeof(EZOutputInfo)); + memset(self.info, 0, sizeof(EZOutputInfo)); + + // + // Setup the audio graph + // + [EZAudioUtilities checkResult:NewAUGraph(&self.info->graph) + operation:"Failed to create graph"]; + + // + // Add converter node + // + AudioComponentDescription converterDescription; + converterDescription.componentType = kAudioUnitType_FormatConverter; + converterDescription.componentSubType = kAudioUnitSubType_AUConverter; + converterDescription.componentManufacturer = kAudioUnitManufacturer_Apple; + [EZAudioUtilities checkResult:AUGraphAddNode(self.info->graph, + &converterDescription, + &self.info->converterNodeInfo.node) + operation:"Failed to add converter node to audio graph"]; + + // + // Add mixer node + // + AudioComponentDescription mixerDescription; + mixerDescription.componentType = kAudioUnitType_Mixer; #if TARGET_OS_IPHONE --(void)_configureOutput { - - // - AudioComponentDescription outputcd; - outputcd.componentFlags = 0; - outputcd.componentFlagsMask = 0; - outputcd.componentManufacturer = kAudioUnitManufacturer_Apple; - outputcd.componentSubType = kAudioUnitSubType_RemoteIO; - outputcd.componentType = kAudioUnitType_Output; - - // - AudioComponent comp = AudioComponentFindNext(NULL,&outputcd); - [EZAudioUtilities checkResult:AudioComponentInstanceNew(comp,&_outputUnit) - operation:"Failed to get output unit"]; - - // Setup the output unit for playback - UInt32 oneFlag = 1; - AudioUnitElement bus0 = 0; - [EZAudioUtilities checkResult:AudioUnitSetProperty(_outputUnit, - kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Output, - bus0, - &oneFlag, - sizeof(oneFlag)) - operation:"Failed to enable output unit"]; - - // Get the hardware sample rate - Float64 hardwareSampleRate = 44100; -#if !(TARGET_IPHONE_SIMULATOR) - hardwareSampleRate = [[AVAudioSession sharedInstance] sampleRate]; -#endif - - // Setup an ASBD in canonical format by default - if (!_customASBD){ - _outputASBD = [EZAudioUtilities stereoCanonicalNonInterleavedFormatWithSampleRate:hardwareSampleRate]; - } - - // Set the format for output - [EZAudioUtilities checkResult:AudioUnitSetProperty(_outputUnit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, - bus0, - &_outputASBD, - sizeof(_outputASBD)) - operation:"Couldn't set the ASBD for input scope/bos 0"]; - - // - AURenderCallbackStruct input; - input.inputProc = OutputRenderCallback; - input.inputProcRefCon = (__bridge void *)self; - [EZAudioUtilities checkResult:AudioUnitSetProperty(_outputUnit, - kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, - bus0, - &input, - sizeof(input)) - operation:"Failed to set the render callback on the output unit"]; - - // - [EZAudioUtilities checkResult:AudioUnitInitialize(_outputUnit) - operation:"Couldn't initialize output unit"]; - - -} + mixerDescription.componentSubType = kAudioUnitSubType_MultiChannelMixer; #elif TARGET_OS_MAC --(void)_configureOutput { - - // - AudioComponentDescription outputcd; - outputcd.componentType = kAudioUnitType_Output; - outputcd.componentSubType = kAudioUnitSubType_DefaultOutput; - outputcd.componentManufacturer = kAudioUnitManufacturer_Apple; - - // - AudioComponent comp = AudioComponentFindNext(NULL, &outputcd); - if (comp == NULL){ - NSLog(@"Failed to get output unit"); - exit(-1); - } - [EZAudioUtilities checkResult:AudioComponentInstanceNew(comp, &_outputUnit) - operation:"Failed to open component for output unit"]; - - - // Setup an ASBD in canonical format by default - if (!_customASBD){ - _outputASBD = [EZAudioUtilities stereoFloatNonInterleavedFormatWithSampleRate:44100]; - } - - // Set the format for output - [EZAudioUtilities checkResult:AudioUnitSetProperty(_outputUnit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, - 0, - &_outputASBD, - sizeof(_outputASBD)) - operation:"Couldn't set the ASBD for input scope/bos 0"]; - - // - AURenderCallbackStruct input; - input.inputProc = OutputRenderCallback; - input.inputProcRefCon = (__bridge void *)(self); - [EZAudioUtilities checkResult:AudioUnitSetProperty(_outputUnit, - kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, - 0, - &input, - sizeof(input)) - operation:"Failed to set the render callback on the output unit"]; - - // - [EZAudioUtilities checkResult:AudioUnitInitialize(_outputUnit) - operation:"Couldn't initialize output unit"]; - -} + mixerDescription.componentSubType = kAudioUnitSubType_StereoMixer; #endif - -#pragma mark - Events --(void)startPlayback { - if (!_isPlaying){ - [EZAudioUtilities checkResult:AudioOutputUnitStart(_outputUnit) - operation:"Failed to start output unit"]; - _isPlaying = YES; - } + mixerDescription.componentManufacturer = kAudioUnitManufacturer_Apple; + [EZAudioUtilities checkResult:AUGraphAddNode(self.info->graph, + &mixerDescription, + &self.info->mixerNodeInfo.node) + operation:"Failed to add mixer node to audio graph"]; + + // + // Add output node + // + AudioComponentDescription outputDescription; + outputDescription.componentType = kAudioUnitType_Output; + outputDescription.componentSubType = [self outputAudioUnitSubType]; + outputDescription.componentManufacturer = kAudioUnitManufacturer_Apple; + [EZAudioUtilities checkResult:AUGraphAddNode(self.info->graph, + &outputDescription, + &self.info->outputNodeInfo.node) + operation:"Failed to add output node to audio graph"]; + + // + // Open the graph + // + [EZAudioUtilities checkResult:AUGraphOpen(self.info->graph) + operation:"Failed to open graph"]; + + // + // Make node connections + // + OSStatus status = [self connectOutputOfSourceNode:self.info->converterNodeInfo.node + sourceNodeOutputBus:0 + toDestinationNode:self.info->mixerNodeInfo.node + destinationNodeInputBus:0 + inGraph:self.info->graph]; + [EZAudioUtilities checkResult:status + operation:"Failed to connect output of source node to destination node in graph"]; + + // + // Connect mixer to output + // + [EZAudioUtilities checkResult:AUGraphConnectNodeInput(self.info->graph, + self.info->mixerNodeInfo.node, + 0, + self.info->outputNodeInfo.node, + 0) + operation:"Failed to connect mixer node to output node"]; + + // + // Get the audio units + // + [EZAudioUtilities checkResult:AUGraphNodeInfo(self.info->graph, + self.info->converterNodeInfo.node, + &converterDescription, + &self.info->converterNodeInfo.audioUnit) + operation:"Failed to get converter audio unit"]; + [EZAudioUtilities checkResult:AUGraphNodeInfo(self.info->graph, + self.info->mixerNodeInfo.node, + &mixerDescription, + &self.info->mixerNodeInfo.audioUnit) + operation:"Failed to get mixer audio unit"]; + [EZAudioUtilities checkResult:AUGraphNodeInfo(self.info->graph, + self.info->outputNodeInfo.node, + &outputDescription, + &self.info->outputNodeInfo.audioUnit) + operation:"Failed to get output audio unit"]; + + // + // Add a node input callback for the converter node + // + AURenderCallbackStruct converterCallback; + converterCallback.inputProc = EZOutputConverterInputCallback; + converterCallback.inputProcRefCon = (__bridge void *)(self); + [EZAudioUtilities checkResult:AUGraphSetNodeInputCallback(self.info->graph, + self.info->converterNodeInfo.node, + 0, + &converterCallback) + operation:"Failed to set render callback on converter node"]; + + // + // Set stream formats + // + [self setClientFormat:[self defaultClientFormat]]; + [self setInputFormat:[self defaultInputFormat]]; + +#if TARGET_OS_IPHONE + EZAudioDevice *currentOutputDevice = [EZAudioDevice currentOutputDevice]; + [self setDevice:currentOutputDevice]; +#elif TARGET_OS_MAC + NSArray *outputDevices = [EZAudioDevice outputDevices]; + EZAudioDevice *defaultOutput = [outputDevices lastObject]; + [self setDevice:defaultOutput]; +#endif + + // + // Set maximum frames per slice to 4096 to allow playback during + // lock screen (iOS only?) + // + UInt32 maximumFramesPerSlice = EZOutputMaximumFramesPerSlice; + [EZAudioUtilities checkResult:AudioUnitSetProperty(self.info->mixerNodeInfo.audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, + 0, + &maximumFramesPerSlice, + sizeof(maximumFramesPerSlice)) + operation:"Failed to set maximum frames per slice on mixer node"]; + + // + // Initialize all the audio units in the graph + // + [EZAudioUtilities checkResult:AUGraphInitialize(self.info->graph) + operation:"Failed to initialize graph"]; + + // + // Add render callback + // + [EZAudioUtilities checkResult:AudioUnitAddRenderNotify(self.info->mixerNodeInfo.audioUnit, + EZOutputGraphRenderCallback, + (__bridge void *)(self)) + operation:"Failed to add render callback"]; } --(void)stopPlayback { - if (_isPlaying){ - [EZAudioUtilities checkResult:AudioOutputUnitStop(_outputUnit) - operation:"Failed to stop output unit"]; - _isPlaying = NO; - } +//------------------------------------------------------------------------------ +#pragma mark - Actions +//------------------------------------------------------------------------------ + +- (void)startPlayback +{ + // + // Start the AUGraph + // + [EZAudioUtilities checkResult:AUGraphStart(self.info->graph) + operation:"Failed to start graph"]; } +//------------------------------------------------------------------------------ + +- (void)stopPlayback +{ + // + // Stop the AUGraph + // + [EZAudioUtilities checkResult:AUGraphStop(self.info->graph) + operation:"Failed to stop graph"]; +} + +//------------------------------------------------------------------------------ #pragma mark - Getters --(AudioStreamBasicDescription)audioStreamBasicDescription { - return _outputASBD; +//------------------------------------------------------------------------------ + +- (BOOL)isPlaying +{ + Boolean isPlaying; + [EZAudioUtilities checkResult:AUGraphIsRunning(self.info->graph, + &isPlaying) + operation:"Failed to check if graph is running"]; + return isPlaying; } --(BOOL)isPlaying { - return _isPlaying; +//------------------------------------------------------------------------------ + +- (float)volume +{ + AudioUnitParameterValue volume; + [EZAudioUtilities checkResult:AudioUnitGetParameter(self.info->mixerNodeInfo.audioUnit, + kMultiChannelMixerParam_Volume, + kAudioUnitScope_Input, + 0, + &volume) + operation:"Failed to get volume from mixer unit"]; + return volume; } +//------------------------------------------------------------------------------ #pragma mark - Setters --(void)setAudioStreamBasicDescription:(AudioStreamBasicDescription)asbd { - BOOL wasPlaying = NO; - if (self.isPlaying){ - [self stopPlayback]; - wasPlaying = YES; - } - _customASBD = YES; - _outputASBD = asbd; - // Set the format for output - [EZAudioUtilities checkResult:AudioUnitSetProperty(_outputUnit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, - 0, - &_outputASBD, - sizeof(_outputASBD)) - operation:"Couldn't set the ASBD for input scope/bos 0"]; - if (wasPlaying) - { - [self startPlayback]; - } +//------------------------------------------------------------------------------ + +- (void)setClientFormat:(AudioStreamBasicDescription)clientFormat +{ + self.info->clientFormat = clientFormat; + [EZAudioUtilities checkResult:AudioUnitSetProperty(self.info->converterNodeInfo.audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + 0, + &self.info->clientFormat, + sizeof(self.info->clientFormat)) + operation:"Failed to set output client format on converter audio unit"]; + [EZAudioUtilities checkResult:AudioUnitSetProperty(self.info->mixerNodeInfo.audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &self.info->clientFormat, + sizeof(self.info->clientFormat)) + operation:"Failed to set input client format on mixer audio unit"]; + [EZAudioUtilities checkResult:AudioUnitSetProperty(self.info->mixerNodeInfo.audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + 0, + &self.info->clientFormat, + sizeof(self.info->clientFormat)) + operation:"Failed to set output client format on mixer audio unit"]; } --(void)dealloc { - [EZAudioUtilities checkResult:AudioOutputUnitStop(_outputUnit) - operation:"Failed to uninitialize output unit"]; - [EZAudioUtilities checkResult:AudioUnitUninitialize(_outputUnit) - operation:"Failed to uninitialize output unit"]; - [EZAudioUtilities checkResult:AudioComponentInstanceDispose(_outputUnit) - operation:"Failed to uninitialize output unit"]; +//------------------------------------------------------------------------------ + +- (void)setDevice:(EZAudioDevice *)device +{ +#if TARGET_OS_IPHONE + + // if the devices are equal then ignore + if ([device isEqual:self.device]) + { + return; + } + + NSError *error; + [[AVAudioSession sharedInstance] setOutputDataSource:device.dataSource error:&error]; + if (error) + { + NSLog(@"Error setting output device data source (%@), reason: %@", + device.dataSource, + error.localizedDescription); + } + +#elif TARGET_OS_MAC + UInt32 outputEnabled = device.outputChannelCount > 0; + NSAssert(outputEnabled, @"Selected EZAudioDevice does not have any output channels"); + [EZAudioUtilities checkResult:AudioUnitSetProperty(self.info->audioUnit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, + 0, + &outputEnabled, + sizeof(outputEnabled)) + operation:"Failed to set flag on device output"]; + + AudioDeviceID deviceId = device.deviceID; + [EZAudioUtilities checkResult:AudioUnitSetProperty(self.info->audioUnit, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &deviceId, + sizeof(AudioDeviceID)) + operation:"Couldn't set default device on I/O unit"]; +#endif + + // store device + _device = device; + + // notify delegate + if ([self.delegate respondsToSelector:@selector(output:changedDevice:)]) + { + [self.delegate output:self changedDevice:device]; + } } +//------------------------------------------------------------------------------ + +- (void)setInputFormat:(AudioStreamBasicDescription)inputFormat +{ + self.info->inputFormat = inputFormat; + [EZAudioUtilities checkResult:AudioUnitSetProperty(self.info->converterNodeInfo.audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &inputFormat, + sizeof(inputFormat)) + operation:"Failed to set input format on converter audio unit"]; +} + +//------------------------------------------------------------------------------ + +- (void)setVolume:(float)volume +{ + AudioUnitParameterID param; +#if TARGET_OS_IPHONE + param = kMultiChannelMixerParam_Volume; +#elif TARGET_OS_MAC + param = kStereoMixerParam_Volume; +#endif + [EZAudioUtilities checkResult:AudioUnitSetParameter(self.info->mixerNodeInfo.audioUnit, + param, + kAudioUnitScope_Input, + 0, + volume, + 0) + operation:"Failed to set volume on mixer unit"]; +} + +//------------------------------------------------------------------------------ +#pragma mark - Core Audio Properties +//------------------------------------------------------------------------------ + +- (AUGraph)graph +{ + return self.info->graph; +} + +//------------------------------------------------------------------------------ + +- (AudioUnit)converterAudioUnit +{ + return self.info->converterNodeInfo.audioUnit; +} + +//------------------------------------------------------------------------------ + +- (AudioUnit)mixerAudioUnit +{ + return self.info->mixerNodeInfo.audioUnit; +} + +//------------------------------------------------------------------------------ + +- (AudioUnit)outputAudioUnit +{ + return self.info->outputNodeInfo.audioUnit; +} + +//------------------------------------------------------------------------------ +#pragma mark - Subclass +//------------------------------------------------------------------------------ + +- (void)cleanupCustomNodes +{ + // + // Override in subclass + // +} + +//------------------------------------------------------------------------------ + +- (OSStatus)connectOutputOfSourceNode:(AUNode)sourceNode + sourceNodeOutputBus:(UInt32)sourceNodeOutputBus + toDestinationNode:(AUNode)destinationNode + destinationNodeInputBus:(UInt32)destinationNodeInputBus + inGraph:(AUGraph)graph +{ + // + // Default implementation is to just connect the source to destination + // + [EZAudioUtilities checkResult:AUGraphConnectNodeInput(graph, + sourceNode, + sourceNodeOutputBus, + destinationNode, + destinationNodeInputBus) + operation:"Failed to connect converter node to mixer node"]; + return noErr; +} + +//------------------------------------------------------------------------------ + +- (AudioStreamBasicDescription)defaultClientFormat +{ + return [EZAudioUtilities stereoFloatNonInterleavedFormatWithSampleRate:EZOutputDefaultSampleRate]; +} + +//------------------------------------------------------------------------------ + +- (AudioStreamBasicDescription)defaultInputFormat +{ + return [EZAudioUtilities stereoFloatNonInterleavedFormatWithSampleRate:EZOutputDefaultSampleRate]; +} + +//------------------------------------------------------------------------------ + +- (OSType)outputAudioUnitSubType +{ +#if TARGET_OS_IPHONE + return kAudioUnitSubType_RemoteIO; +#elif TARGET_OS_MAC + return kAudioUnitSubType_HALOutput; +#endif +} + +//------------------------------------------------------------------------------ + @end + +//------------------------------------------------------------------------------ +#pragma mark - Callbacks (Implementation) +//------------------------------------------------------------------------------ + +OSStatus EZOutputConverterInputCallback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) +{ + EZOutput *output = (__bridge EZOutput *)inRefCon; + + // + // Try to ask the data source for audio data to fill out the output's + // buffer list + // + if ([output.dataSource respondsToSelector:@selector(output:shouldFillAudioBufferList:withNumberOfFrames:timestamp:)]) + { + return [output.dataSource output:output + shouldFillAudioBufferList:ioData + withNumberOfFrames:inNumberFrames + timestamp:inTimeStamp]; + } + else + { + // + // Silence if there is nothing to output + // + for (int i = 0; i < ioData->mNumberBuffers; i++) + { + memset(ioData->mBuffers[i].mData, + 0, + ioData->mBuffers[i].mDataByteSize); + } + } + return noErr; +} + +//------------------------------------------------------------------------------ + +OSStatus EZOutputGraphRenderCallback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) +{ + EZOutput *output = (__bridge EZOutput *)inRefCon; + + // + // provide the audio received delegate callback + // + if (*ioActionFlags & kAudioUnitRenderAction_PostRender) + { + if ([output.delegate respondsToSelector:@selector(output:playedAudio:withBufferSize:withNumberOfChannels:)]) + { + UInt32 channels = output.info->clientFormat.mChannelsPerFrame; + float *buffers[channels]; + for (int i = 0; i < channels; i++) + { + buffers[i] = ioData->mBuffers[i].mData; + } + [output.delegate output:output + playedAudio:buffers + withBufferSize:inNumberFrames + withNumberOfChannels:channels]; + } + } + return noErr; +} \ No newline at end of file diff --git a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h index 3d893f6..9676d01 100644 --- a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h +++ b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h @@ -26,7 +26,12 @@ /** The EZAudioFile representing of the currently selected audio file */ -@property (nonatomic,strong) EZAudioFile *audioFile; +@property (nonatomic, strong) EZAudioFile *audioFile; + +/** + The EZOutput representing the output currently being used to play the audio file. + */ +@property (nonatomic, strong) EZOutput *output; /** The CoreGraphics based audio plot diff --git a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m index fdb099f..926aea2 100644 --- a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m +++ b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m @@ -19,27 +19,31 @@ #pragma mark - Customize the Audio Plot - (void)viewDidLoad { - [super viewDidLoad]; - - /* - Customizing the audio plot's look - */ - // Background color - self.audioPlot.backgroundColor = [UIColor colorWithRed: 0.816 green: 0.349 blue: 0.255 alpha: 1]; - // Waveform color - self.audioPlot.color = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0]; - // Plot type - self.audioPlot.plotType = EZPlotTypeBuffer; - // Fill - self.audioPlot.shouldFill = YES; - // Mirror - self.audioPlot.shouldMirror = YES; - - /* - Try opening the sample file - */ - [self openFileWithFilePathURL:[NSURL fileURLWithPath:kAudioFileDefault]]; - + [super viewDidLoad]; + + // + // Customize the plot's look + // + // Background color + self.audioPlot.backgroundColor = [UIColor colorWithRed: 0.816 green: 0.349 blue: 0.255 alpha: 1]; + // Waveform color + self.audioPlot.color = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0]; + // Plot type + self.audioPlot.plotType = EZPlotTypeBuffer; + // Fill + self.audioPlot.shouldFill = YES; + // Mirror + self.audioPlot.shouldMirror = YES; + + // + // Create an EZOutput instance + // + self.output = [EZOutput outputWithDataSource:self]; + + // + // Try opening the sample file + // + [self openFileWithFilePathURL:[NSURL fileURLWithPath:kAudioFileDefault]]; } //------------------------------------------------------------------------------ @@ -75,15 +79,15 @@ - (void)openFileWithFilePathURL:(NSURL *)filePathURL { // Stop playback - [[EZOutput sharedOutput] stopPlayback]; + [self.output stopPlayback]; self.audioFile = [EZAudioFile audioFileWithURL:filePathURL delegate:self]; self.eof = NO; self.filePathLabel.text = filePathURL.lastPathComponent; self.framePositionSlider.maximumValue = (float)self.audioFile.totalFrames; - // Set the client format from the EZAudioFile on the output - [[EZOutput sharedOutput] setAudioStreamBasicDescription:self.audioFile.clientFormat]; + // Set the input format from the EZAudioFile on the output + [self.output setInputFormat:[self.audioFile clientFormat]]; // Plot the whole waveform self.audioPlot.plotType = EZPlotTypeBuffer; @@ -93,29 +97,27 @@ __weak typeof (self) weakSelf = self; [self.audioFile getWaveformDataWithCompletionBlock:^(float **waveformData, int length) - { - [weakSelf.audioPlot updateBuffer:waveformData[0] - withBufferSize:length]; - }]; + { + [weakSelf.audioPlot updateBuffer:waveformData[0] + withBufferSize:length]; + }]; } //------------------------------------------------------------------------------ - (void)play:(id)sender { - if (![[EZOutput sharedOutput] isPlaying]) + if (![self.output isPlaying]) { if (self.eof) { [self.audioFile seekToFrame:0]; } - [EZOutput sharedOutput].outputDataSource = self; - [[EZOutput sharedOutput] startPlayback]; + [self.output startPlayback]; } else { - [EZOutput sharedOutput].outputDataSource = nil; - [[EZOutput sharedOutput] stopPlayback]; + [self.output stopPlayback]; } } @@ -163,7 +165,7 @@ { __weak typeof (self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ - if ([EZOutput sharedOutput].isPlaying) + if ([self.output isPlaying]) { if (weakSelf.audioPlot.plotType == EZPlotTypeBuffer && weakSelf.audioPlot.shouldFill == YES && @@ -196,29 +198,25 @@ #pragma mark - EZOutputDataSource //------------------------------------------------------------------------------ -- (void) output:(EZOutput *)output +- (OSStatus) output:(EZOutput *)output shouldFillAudioBufferList:(AudioBufferList *)audioBufferList withNumberOfFrames:(UInt32)frames + timestamp:(const AudioTimeStamp *)timestamp { - if ( self.audioFile ) + if (self.audioFile) { UInt32 bufferSize; + BOOL eof; [self.audioFile readFrames:frames audioBufferList:audioBufferList bufferSize:&bufferSize - eof:&_eof]; - if ( _eof ) + eof:&eof]; + if (eof) { - [self seekToFrame:0]; + [self.audioFile seekToFrame:0]; } } -} - -//------------------------------------------------------------------------------ - -- (AudioStreamBasicDescription)outputHasAudioStreamBasicDescription:(EZOutput *)output -{ - return self.audioFile.clientFormat; + return noErr; } //------------------------------------------------------------------------------