~zanneth/StreamPlayer

a206890edc4dad8109d8cf52e31237a133a40cd8 — Charles Magahern 7 years ago b24ef38
Support for audio processing taps
M StreamPlayer/Source/MainViewController.m => StreamPlayer/Source/MainViewController.m +9 -1
@@ 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;
    }
    

M StreamPlayer/Source/ZANStreamPlayer.h => StreamPlayer/Source/ZANStreamPlayer.h +17 -1
@@ 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;

M StreamPlayer/Source/ZANStreamPlayer.m => StreamPlayer/Source/ZANStreamPlayer.m +69 -1
@@ 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