@@ 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;
}
@@ 7,6 7,7 @@
//
#import <Foundation/Foundation.h>
+#import <CoreAudio/CoreAudioTypes.h>
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<ZANStreamPlayerDelegate> 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;
@@ 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 () <NSURLSessionDataDelegate>
@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