Cog Audio: Overhaul output buffering yet again, adding an extra buffer stage between the converter and the output thread
parent
a1522aeb6e
commit
5ab728b205
|
@ -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
|
||||
|
|
|
@ -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]];
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
|
|
|
@ -8,21 +8,27 @@
|
|||
|
||||
#import <AssertMacros.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <dispatch/dispatch.h>
|
||||
|
||||
#import <CoreAudio/AudioHardware.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AudioUnit/AudioUnit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
#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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue