From a206890edc4dad8109d8cf52e31237a133a40cd8 Mon Sep 17 00:00:00 2001 From: Charles Magahern Date: Mon, 4 Jul 2016 20:32:59 -0700 Subject: [PATCH] Support for audio processing taps --- StreamPlayer/Source/MainViewController.m | 10 +++- StreamPlayer/Source/ZANStreamPlayer.h | 18 +++++- StreamPlayer/Source/ZANStreamPlayer.m | 70 +++++++++++++++++++++++- 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/StreamPlayer/Source/MainViewController.m b/StreamPlayer/Source/MainViewController.m index d4aac80..8445ab7 100644 --- a/StreamPlayer/Source/MainViewController.m +++ b/StreamPlayer/Source/MainViewController.m @@ -48,6 +48,14 @@ static NSString * const MVCSavedURLStringDefaultsKey = @"SavedURLString"; } } +- (void)streamPlayer:(ZANStreamPlayer *)player + didDecodeAudioData:(NSData *)data + withFramesCount:(NSUInteger)framesCount + format:(const AudioStreamBasicDescription *)format +{ + // cool +} + #pragma mark - Actions - (IBAction)_playPauseButtonPressed:(id)sender @@ -58,7 +66,7 @@ static NSString * const MVCSavedURLStringDefaultsKey = @"SavedURLString"; if (!_player) { NSURL *url = [[NSURL alloc] initWithString:self.urlTextField.stringValue]; - _player = [[ZANStreamPlayer alloc] initWithURL:url]; + _player = [[ZANStreamPlayer alloc] initWithURL:url options:ZANStreamPlayerOptionInstallProcessingTap]; _player.delegate = self; } diff --git a/StreamPlayer/Source/ZANStreamPlayer.h b/StreamPlayer/Source/ZANStreamPlayer.h index e9db695..219a49c 100644 --- a/StreamPlayer/Source/ZANStreamPlayer.h +++ b/StreamPlayer/Source/ZANStreamPlayer.h @@ -7,6 +7,7 @@ // #import +#import NS_ASSUME_NONNULL_BEGIN @@ -18,19 +19,34 @@ NS_ASSUME_NONNULL_BEGIN - (void)streamPlayerPlaybackStateDidChange:(ZANStreamPlayer *)player; - (void)streamPlayer:(ZANStreamPlayer *)player didEncounterError:(NSError *)error; +// must initialize stream player with ZANStreamPlayerOptionInstallProcessingTap +- (void)streamPlayer:(ZANStreamPlayer *)player + didDecodeAudioData:(NSData *)data + withFramesCount:(NSUInteger)framesCount + format:(const AudioStreamBasicDescription *)format; + @end // ----------------------------------------------------------------------------- +typedef NS_OPTIONS(NSUInteger, ZANStreamPlayerOptions) +{ + ZANStreamPlayerOptionNone = 0, + ZANStreamPlayerOptionInstallProcessingTap = (1 << 0), +}; + +// ----------------------------------------------------------------------------- + @interface ZANStreamPlayer : NSObject @property (nonatomic, weak) id delegate; @property (nonatomic, readonly) NSURL *url; +@property (nonatomic, readonly) ZANStreamPlayerOptions options; @property (nonatomic, readonly, getter=isPlaying) BOOL playing; @property (nonatomic, readonly, getter=isStopped) BOOL stopped; -- (instancetype)initWithURL:(NSURL *)url; +- (instancetype)initWithURL:(NSURL *)url options:(ZANStreamPlayerOptions)options; - (void)play; - (void)pause; diff --git a/StreamPlayer/Source/ZANStreamPlayer.m b/StreamPlayer/Source/ZANStreamPlayer.m index fe3ce61..7827b51 100644 --- a/StreamPlayer/Source/ZANStreamPlayer.m +++ b/StreamPlayer/Source/ZANStreamPlayer.m @@ -44,6 +44,14 @@ static void _ZANAudioQueueOutputCallback(void *clientData, AudioQueueRef audioQueue, AudioQueueBufferRef buffer); +static void _ZANAudioQueueProcessingTapCallback(void *clientData, + AudioQueueProcessingTapRef tap, + UInt32 framesCount, + AudioTimeStamp *timestamp, + AudioQueueProcessingTapFlags *flags, + UInt32 *outFramesCount, + AudioBufferList *data); + @interface ZANStreamPlayer () @property (nonatomic, assign) ZANStreamPlaybackState playbackState; @@ -58,6 +66,8 @@ static void _ZANAudioQueueOutputCallback(void *clientData, AudioQueueRef _audioQueue; AudioQueueBufferRef _audioQueueBuffers[AUDIO_QUEUE_BUFFERS_COUNT]; AudioStreamPacketDescription _packetDescriptions[MAX_PACKET_DESCRIPTIONS]; + AudioQueueProcessingTapRef _processingTap; + AudioStreamBasicDescription _processingFormat; NSOperationQueue *_inputQueue; BOOL _queueBuffersUsageStates[AUDIO_QUEUE_BUFFERS_COUNT]; @@ -73,11 +83,12 @@ static void _ZANAudioQueueOutputCallback(void *clientData, AudioStreamBasicDescription _audioStreamDescription; } -- (instancetype)initWithURL:(NSURL *)url +- (instancetype)initWithURL:(NSURL *)url options:(ZANStreamPlayerOptions)options { self = [super init]; if (self) { _url = [url copy]; + _options = options; _inputQueue = [[NSOperationQueue alloc] init]; _inputQueue.maxConcurrentOperationCount = 1; @@ -97,6 +108,11 @@ static void _ZANAudioQueueOutputCallback(void *clientData, [self _closeAudioStream]; [self _destroyAudioOutputQueue]; + if (_processingTap) { + AudioQueueProcessingTapDispose(_processingTap); + _processingTap = NULL; + } + pthread_mutex_destroy(&_queueBuffersMutex); pthread_cond_destroy(&_queueBufferReadyCondition); @@ -348,6 +364,22 @@ static void _ZANAudioQueueOutputCallback(void *clientData, break; } } + + // create processing tap, if requested + if (_options & ZANStreamPlayerOptionInstallProcessingTap) { + UInt32 maxFrames = 0; + status = AudioQueueProcessingTapNew(_audioQueue, + _ZANAudioQueueProcessingTapCallback, + (__bridge void *)self, + kAudioQueueProcessingTap_PostEffects, + &maxFrames, + &_processingFormat, + &_processingTap); + if (status != noErr) { + [self _logError:@"Failed to initialize processing tap (OSStatus = %d)", status]; + break; + } + } } while (0); if (outError) { @@ -579,6 +611,23 @@ static void _ZANAudioQueueOutputCallback(void *clientData, pthread_mutex_unlock(&_queueBuffersMutex); } +- (void)_handleTapCallbackFromTap:(AudioQueueProcessingTapRef)tap + withFramesCount:(UInt32)inNumberFrames + audioTimestamp:(AudioTimeStamp *)ioTimeStamp + processingTapFlags:(AudioQueueProcessingTapFlags *)flags + outFramesCount:(UInt32 *)outNumberFrames + data:(AudioBufferList *)ioData +{ + OSStatus status = AudioQueueProcessingTapGetSourceAudio(tap, inNumberFrames, ioTimeStamp, flags, outNumberFrames, ioData); + if (status != noErr) { + [self _logError:@"Faield to get source audio in processing tap callback (OSStatus = %d)", status]; + } else { + AudioBuffer *buffer = &ioData->mBuffers[0]; + NSData *audioData = [NSData dataWithBytesNoCopy:buffer->mData length:buffer->mDataByteSize freeWhenDone:NO]; + [_delegate streamPlayer:self didDecodeAudioData:audioData withFramesCount:inNumberFrames format:&_processingFormat]; + } +} + - (NSError *)_errorFromOSStatus:(OSStatus)status { NSError *error = nil; @@ -661,4 +710,23 @@ void _ZANAudioQueueOutputCallback(void *clientData, AudioQueueRef audioQueue, Au } } +void _ZANAudioQueueProcessingTapCallback(void *clientData, + AudioQueueProcessingTapRef tap, + UInt32 framesCount, + AudioTimeStamp *timestamp, + AudioQueueProcessingTapFlags *flags, + UInt32 *outFramesCount, + AudioBufferList *data) +{ + @autoreleasepool { + ZANStreamPlayer *player = (__bridge ZANStreamPlayer *)clientData; + [player _handleTapCallbackFromTap:tap + withFramesCount:framesCount + audioTimestamp:timestamp + processingTapFlags:flags + outFramesCount:outFramesCount + data:data]; + } +} + @end -- 2.45.2