From 5ab728b2055e129bf67e24f00fdd23b1b5bb1c9c Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Fri, 14 Jan 2022 22:46:41 -0800 Subject: [PATCH] Cog Audio: Overhaul output buffering yet again, adding an extra buffer stage between the converter and the output thread --- Audio/AudioPlayer.h | 1 + Audio/AudioPlayer.m | 33 ++------- Audio/Chain/BufferChain.m | 3 + Audio/Chain/OutputNode.h | 9 +++ Audio/Chain/OutputNode.m | 36 +++++++-- Audio/Output/OutputCoreAudio.h | 10 ++- Audio/Output/OutputCoreAudio.m | 132 ++++++++++++++++++++++++++++----- 7 files changed, 165 insertions(+), 59 deletions(-) diff --git a/Audio/AudioPlayer.h b/Audio/AudioPlayer.h index bae92cf57..10aa16249 100644 --- a/Audio/AudioPlayer.h +++ b/Audio/AudioPlayer.h @@ -95,6 +95,7 @@ //- (BufferChain *)bufferChain; - (void)launchOutputThread; - (void)endOfInputPlayed; +- (void)endOfInputPlayedOut; - (void)sendDelegateMethod:(SEL)selector withObject:(id)obj waitUntilDone:(BOOL)wait; - (void)sendDelegateMethod:(SEL)selector withObject:(id)obj withObject:(id)obj2 waitUntilDone:(BOOL)wait; @end diff --git a/Audio/AudioPlayer.m b/Audio/AudioPlayer.m index 1bc89a479..2d057cd65 100644 --- a/Audio/AudioPlayer.m +++ b/Audio/AudioPlayer.m @@ -353,33 +353,6 @@ { NSMutableString *unixPathNext = [[nextStream path] mutableCopy]; NSMutableString *unixPathPrev = [[[lastChain streamURL] path] mutableCopy]; - - //Get the fragment - NSString *fragmentNext = @""; - NSScanner *scanner = [NSScanner scannerWithString:unixPathNext]; - NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"#1234567890"]; - while (![scanner isAtEnd]) { - NSString *possibleFragment; - [scanner scanUpToString:@"#" intoString:nil]; - - if ([scanner scanCharactersFromSet:characterSet intoString:&possibleFragment] && [scanner isAtEnd]) { - fragmentNext = possibleFragment; - [unixPathNext deleteCharactersInRange:NSMakeRange([scanner scanLocation] - [possibleFragment length], [possibleFragment length])]; - break; - } - } - - NSString *fragmentPrev = @""; - scanner = [NSScanner scannerWithString:unixPathPrev]; - while (![scanner isAtEnd]) { - NSString *possibleFragment; - [scanner scanUpToString:@"#" intoString:nil]; - - if ([scanner scanCharactersFromSet:characterSet intoString:&possibleFragment] && [scanner isAtEnd]) { - fragmentPrev = possibleFragment; - [unixPathPrev deleteCharactersInRange:NSMakeRange([scanner scanLocation] - [possibleFragment length], [possibleFragment length])]; - } - } if ([unixPathNext isEqualToString:unixPathPrev]) pathsEqual = YES; @@ -473,10 +446,14 @@ [semaphore signal]; } - [self notifyStreamChanged:[bufferChain userInfo]]; [output setEndOfStream:NO]; } +- (void)endOfInputPlayedOut +{ + [self notifyStreamChanged:[bufferChain userInfo]]; +} + - (void)sendDelegateMethod:(SEL)selector withObject:(id)obj waitUntilDone:(BOOL)wait { NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:selector]]; diff --git a/Audio/Chain/BufferChain.m b/Audio/Chain/BufferChain.m index 244251048..cf2e61c5d 100644 --- a/Audio/Chain/BufferChain.m +++ b/Audio/Chain/BufferChain.m @@ -231,6 +231,9 @@ - (double)secondsBuffered { double duration = 0.0; + OutputNode * outputNode = [controller output]; + duration += [outputNode secondsBuffered]; + Node * node = [self finalNode]; while (node) { duration += [node secondsBuffered]; diff --git a/Audio/Chain/OutputNode.h b/Audio/Chain/OutputNode.h index edbd8b8c2..a9e0b534d 100644 --- a/Audio/Chain/OutputNode.h +++ b/Audio/Chain/OutputNode.h @@ -23,10 +23,19 @@ OutputCoreAudio *output; BOOL paused; + BOOL started; } - (double)amountPlayed; +- (void)incrementAmountPlayed:(long)count; +- (void)resetAmountPlayed; + +- (void)endOfInputPlayed; +- (void)endOfInputPlayedOut; + +- (double)secondsBuffered; + - (void)setup; - (void)process; - (void)close; diff --git a/Audio/Chain/OutputNode.m b/Audio/Chain/OutputNode.m index 92e8ff05b..c971dee0d 100644 --- a/Audio/Chain/OutputNode.m +++ b/Audio/Chain/OutputNode.m @@ -21,6 +21,7 @@ sampleRatio = 0.0; paused = YES; + started = NO; output = [[OutputCoreAudio alloc] initWithController:self]; @@ -30,6 +31,7 @@ - (void)seek:(double)time { // [output pause]; + [self resetBuffer]; amountPlayed = time; } @@ -37,7 +39,7 @@ - (void)process { paused = NO; - [output start]; + [output start]; } - (void)pause @@ -52,6 +54,31 @@ [output resume]; } +- (void)incrementAmountPlayed:(long)count +{ + amountPlayed += (double)count * sampleRatio; +} + +- (void)resetAmountPlayed +{ + amountPlayed = 0; +} + +- (void)endOfInputPlayed +{ + [controller endOfInputPlayed]; +} + +- (void)endOfInputPlayedOut +{ + [controller endOfInputPlayedOut]; +} + +- (double)secondsBuffered +{ + return (double)([buffer bufferedLength]) / (format.mSampleRate * format.mBytesPerPacket); +} + - (int)readData:(void *)ptr amount:(int)amount { @autoreleasepool { @@ -59,14 +86,7 @@ [self setPreviousNode:[[controller bufferChain] finalNode]]; n = [super readData:ptr amount:amount]; - amountPlayed += (double)n * sampleRatio; - if (endOfStream == YES && !n) - { - amountPlayed = 0.0; - [controller endOfInputPlayed]; //Updates shouldContinue appropriately? - } - /* if (n == 0) { DLog(@"Output Buffer dry!"); } diff --git a/Audio/Output/OutputCoreAudio.h b/Audio/Output/OutputCoreAudio.h index 3f86bf76f..0dc2a38b7 100644 --- a/Audio/Output/OutputCoreAudio.h +++ b/Audio/Output/OutputCoreAudio.h @@ -8,21 +8,27 @@ #import #import -#import #import #import #import #import +#import "Semaphore.h" + @class OutputNode; @interface OutputCoreAudio : NSObject { OutputNode * outputController; + Semaphore * writeSemaphore; + Semaphore * readSemaphore; + BOOL running; BOOL stopping; BOOL stopped; + BOOL started; + BOOL paused; BOOL listenerapplied; @@ -35,8 +41,6 @@ AUAudioUnit *_au; size_t _bufferSize; - - dispatch_semaphore_t _sema; } - (id)initWithController:(OutputNode *)c; diff --git a/Audio/Output/OutputCoreAudio.m b/Audio/Output/OutputCoreAudio.m index c0ba2d351..563ff1aa3 100644 --- a/Audio/Output/OutputCoreAudio.m +++ b/Audio/Output/OutputCoreAudio.m @@ -27,8 +27,10 @@ extern void scale_by_volume(float * buffer, size_t count, float volume); outputDeviceID = -1; listenerapplied = NO; running = NO; + started = NO; - _sema = dispatch_semaphore_create(0); + writeSemaphore = [[Semaphore alloc] init]; + readSemaphore = [[Semaphore alloc] init]; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.outputDevice" options:0 context:NULL]; } @@ -53,16 +55,81 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const } } +- (void)signalEndOfStream +{ + [outputController endOfInputPlayed]; +} + +- (void)signalEndOfStreamBuffered +{ + [outputController resetAmountPlayed]; + [outputController endOfInputPlayedOut]; +} + - (void)threadEntry:(id)arg { running = YES; + started = NO; size_t eventCount = 0; + ssize_t delayedEvent = -1; while (!stopping) { - dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER); if (++eventCount == 128) { [self updateDeviceFormat]; eventCount = 0; } + + if ([outputController shouldReset]) { + [[outputController buffer] empty]; + [outputController setShouldReset:NO]; + } + + void *writePtr; + int toWrite = [[outputController buffer] lengthAvailableToWriteReturningPointer:&writePtr]; + int bytesRead = 0; + if (toWrite > CHUNK_SIZE) + toWrite = CHUNK_SIZE; + if (toWrite) + bytesRead = [outputController readData:writePtr amount:toWrite]; + if (bytesRead) { + [[outputController buffer] didWriteLength:bytesRead]; + [readSemaphore signal]; + if (delayedEvent >= 0) { + if (bytesRead >= delayedEvent) { + delayedEvent = -1; + [self signalEndOfStreamBuffered]; + } + else { + delayedEvent -= bytesRead; + } + } + continue; + } + else if ([outputController shouldContinue] == NO) + break; + else if (!toWrite) { + if (!started) { + started = YES; + if (!paused) { + NSError *err; + [_au startHardwareAndReturnError:&err]; + } + } + } + else { + // End of input possibly reached + if ([outputController endOfStream] == YES) + { + [self signalEndOfStream]; + if (delayedEvent >= 0) { + [self signalEndOfStreamBuffered]; + delayedEvent = -1; + } + else + delayedEvent = [[outputController buffer] bufferedLength]; + } + } + [readSemaphore signal]; + [writeSemaphore timedWait:5000]; } stopped = YES; [self stop]; @@ -142,7 +209,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const ^(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop) { if ([deviceName isEqualToString:userDeviceName]) { err = [self setOutputDeviceByID:deviceID]; - + #if 0 // Disable. Would cause loop by triggering `-observeValueForKeyPath:ofObject:change:context:` above. // Update `outputDevice`, in case the ID has changed. @@ -305,6 +372,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const running = NO; stopping = NO; stopped = NO; + paused = NO; outputDeviceID = -1; AudioComponentDescription desc; @@ -338,8 +406,9 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const _deviceFormat = nil; [self updateDeviceFormat]; - - __block dispatch_semaphore_t sema = _sema; + + __block Semaphore * writeSemaphore = self->writeSemaphore; + __block Semaphore * readSemaphore = self->readSemaphore; __block OutputNode * outputController = self->outputController; __block float * volume = &self->volume; @@ -347,7 +416,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const { void *readPointer = inputData->mBuffers[0].mData; - int amountToRead, amountRead; + int amountToRead, amountRead = 0; amountToRead = inputData->mBuffers[0].mDataByteSize; @@ -355,20 +424,43 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const { memset(readPointer, 0, amountToRead); self->stopping = YES; - dispatch_semaphore_signal(sema); return 0; } - - amountRead = [outputController readData:(readPointer) amount:amountToRead]; + + void * readPtr; + int toRead = [[outputController buffer] lengthAvailableToReadReturningPointer:&readPtr]; + + if (toRead > amountToRead) + toRead = amountToRead; + + if (toRead) { + memcpy(readPointer, readPtr, toRead); + amountRead = toRead; + [[outputController buffer] didReadLength:toRead]; + [outputController incrementAmountPlayed:amountRead]; + [writeSemaphore signal]; + } // Try repeatedly! Buffer wraps can cause a slight data shortage, as can // unexpected track changes. while ((amountRead < amountToRead) && [outputController shouldContinue] == YES) { int amountRead2; //Use this since return type of readdata isnt known...may want to fix then can do a simple += to readdata - amountRead2 = [outputController readData:(readPointer+amountRead) amount:amountToRead-amountRead]; - amountRead += amountRead2; - usleep(500); + amountRead2 = [[outputController buffer] lengthAvailableToReadReturningPointer:&readPtr]; + if (amountRead2 > (amountToRead - amountRead)) + amountRead2 = amountToRead - amountRead; + if (amountRead2) { + memcpy(readPointer + amountRead, readPtr, amountRead2); + [[outputController buffer] didReadLength:amountRead2]; + + [outputController incrementAmountPlayed:amountRead2]; + + amountRead += amountRead2; + [writeSemaphore signal]; + } + else { + [readSemaphore timedWait:500]; + } } int framesRead = amountRead / sizeof(float); @@ -382,15 +474,11 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const memset(readPointer + amountRead, 0, amountToRead - amountRead); } - dispatch_semaphore_signal(sema); - return 0; }; [_au allocateRenderResourcesAndReturnError:&err]; - [NSThread detachNewThreadSelector:@selector(threadEntry:) toTarget:self withObject:nil]; - return (err == nil); } @@ -401,13 +489,15 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const - (void)start { - NSError *err; - [_au startHardwareAndReturnError:&err]; + [self threadEntry:nil]; } - (void)stop { stopping = YES; + paused = NO; + [writeSemaphore signal]; + [readSemaphore signal]; if (listenerapplied) { AudioObjectPropertyAddress theAddress = { .mSelector = kAudioHardwarePropertyDefaultOutputDevice, @@ -425,8 +515,8 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const while (!stopped) { stopping = YES; - dispatch_semaphore_signal(_sema); - usleep(500); + [readSemaphore signal]; + [writeSemaphore timedWait:5000]; } } @@ -439,6 +529,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const - (void)pause { + paused = YES; [_au stopHardware]; } @@ -446,6 +537,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const { NSError *err; [_au startHardwareAndReturnError:&err]; + paused = NO; } @end