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;
|
//- (BufferChain *)bufferChain;
|
||||||
- (void)launchOutputThread;
|
- (void)launchOutputThread;
|
||||||
- (void)endOfInputPlayed;
|
- (void)endOfInputPlayed;
|
||||||
|
- (void)endOfInputPlayedOut;
|
||||||
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj waitUntilDone:(BOOL)wait;
|
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj waitUntilDone:(BOOL)wait;
|
||||||
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj withObject:(id)obj2 waitUntilDone:(BOOL)wait;
|
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj withObject:(id)obj2 waitUntilDone:(BOOL)wait;
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -353,33 +353,6 @@
|
||||||
{
|
{
|
||||||
NSMutableString *unixPathNext = [[nextStream path] mutableCopy];
|
NSMutableString *unixPathNext = [[nextStream path] mutableCopy];
|
||||||
NSMutableString *unixPathPrev = [[[lastChain streamURL] 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])
|
if ([unixPathNext isEqualToString:unixPathPrev])
|
||||||
pathsEqual = YES;
|
pathsEqual = YES;
|
||||||
|
@ -473,10 +446,14 @@
|
||||||
[semaphore signal];
|
[semaphore signal];
|
||||||
}
|
}
|
||||||
|
|
||||||
[self notifyStreamChanged:[bufferChain userInfo]];
|
|
||||||
[output setEndOfStream:NO];
|
[output setEndOfStream:NO];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)endOfInputPlayedOut
|
||||||
|
{
|
||||||
|
[self notifyStreamChanged:[bufferChain userInfo]];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj waitUntilDone:(BOOL)wait
|
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj waitUntilDone:(BOOL)wait
|
||||||
{
|
{
|
||||||
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:selector]];
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:selector]];
|
||||||
|
|
|
@ -231,6 +231,9 @@
|
||||||
- (double)secondsBuffered
|
- (double)secondsBuffered
|
||||||
{
|
{
|
||||||
double duration = 0.0;
|
double duration = 0.0;
|
||||||
|
OutputNode * outputNode = [controller output];
|
||||||
|
duration += [outputNode secondsBuffered];
|
||||||
|
|
||||||
Node * node = [self finalNode];
|
Node * node = [self finalNode];
|
||||||
while (node) {
|
while (node) {
|
||||||
duration += [node secondsBuffered];
|
duration += [node secondsBuffered];
|
||||||
|
|
|
@ -23,10 +23,19 @@
|
||||||
OutputCoreAudio *output;
|
OutputCoreAudio *output;
|
||||||
|
|
||||||
BOOL paused;
|
BOOL paused;
|
||||||
|
BOOL started;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (double)amountPlayed;
|
- (double)amountPlayed;
|
||||||
|
|
||||||
|
- (void)incrementAmountPlayed:(long)count;
|
||||||
|
- (void)resetAmountPlayed;
|
||||||
|
|
||||||
|
- (void)endOfInputPlayed;
|
||||||
|
- (void)endOfInputPlayedOut;
|
||||||
|
|
||||||
|
- (double)secondsBuffered;
|
||||||
|
|
||||||
- (void)setup;
|
- (void)setup;
|
||||||
- (void)process;
|
- (void)process;
|
||||||
- (void)close;
|
- (void)close;
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
sampleRatio = 0.0;
|
sampleRatio = 0.0;
|
||||||
|
|
||||||
paused = YES;
|
paused = YES;
|
||||||
|
started = NO;
|
||||||
|
|
||||||
output = [[OutputCoreAudio alloc] initWithController:self];
|
output = [[OutputCoreAudio alloc] initWithController:self];
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
- (void)seek:(double)time
|
- (void)seek:(double)time
|
||||||
{
|
{
|
||||||
// [output pause];
|
// [output pause];
|
||||||
|
[self resetBuffer];
|
||||||
|
|
||||||
amountPlayed = time;
|
amountPlayed = time;
|
||||||
}
|
}
|
||||||
|
@ -37,7 +39,7 @@
|
||||||
- (void)process
|
- (void)process
|
||||||
{
|
{
|
||||||
paused = NO;
|
paused = NO;
|
||||||
[output start];
|
[output start];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)pause
|
- (void)pause
|
||||||
|
@ -52,6 +54,31 @@
|
||||||
[output resume];
|
[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
|
- (int)readData:(void *)ptr amount:(int)amount
|
||||||
{
|
{
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
|
@ -59,14 +86,7 @@
|
||||||
[self setPreviousNode:[[controller bufferChain] finalNode]];
|
[self setPreviousNode:[[controller bufferChain] finalNode]];
|
||||||
|
|
||||||
n = [super readData:ptr amount:amount];
|
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) {
|
/* if (n == 0) {
|
||||||
DLog(@"Output Buffer dry!");
|
DLog(@"Output Buffer dry!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,21 +8,27 @@
|
||||||
|
|
||||||
#import <AssertMacros.h>
|
#import <AssertMacros.h>
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import <dispatch/dispatch.h>
|
|
||||||
|
|
||||||
#import <CoreAudio/AudioHardware.h>
|
#import <CoreAudio/AudioHardware.h>
|
||||||
#import <AudioToolbox/AudioToolbox.h>
|
#import <AudioToolbox/AudioToolbox.h>
|
||||||
#import <AudioUnit/AudioUnit.h>
|
#import <AudioUnit/AudioUnit.h>
|
||||||
#import <AVFoundation/AVFoundation.h>
|
#import <AVFoundation/AVFoundation.h>
|
||||||
|
|
||||||
|
#import "Semaphore.h"
|
||||||
|
|
||||||
@class OutputNode;
|
@class OutputNode;
|
||||||
|
|
||||||
@interface OutputCoreAudio : NSObject {
|
@interface OutputCoreAudio : NSObject {
|
||||||
OutputNode * outputController;
|
OutputNode * outputController;
|
||||||
|
|
||||||
|
Semaphore * writeSemaphore;
|
||||||
|
Semaphore * readSemaphore;
|
||||||
|
|
||||||
BOOL running;
|
BOOL running;
|
||||||
BOOL stopping;
|
BOOL stopping;
|
||||||
BOOL stopped;
|
BOOL stopped;
|
||||||
|
BOOL started;
|
||||||
|
BOOL paused;
|
||||||
|
|
||||||
BOOL listenerapplied;
|
BOOL listenerapplied;
|
||||||
|
|
||||||
|
@ -35,8 +41,6 @@
|
||||||
|
|
||||||
AUAudioUnit *_au;
|
AUAudioUnit *_au;
|
||||||
size_t _bufferSize;
|
size_t _bufferSize;
|
||||||
|
|
||||||
dispatch_semaphore_t _sema;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)initWithController:(OutputNode *)c;
|
- (id)initWithController:(OutputNode *)c;
|
||||||
|
|
|
@ -27,8 +27,10 @@ extern void scale_by_volume(float * buffer, size_t count, float volume);
|
||||||
outputDeviceID = -1;
|
outputDeviceID = -1;
|
||||||
listenerapplied = NO;
|
listenerapplied = NO;
|
||||||
running = 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];
|
[[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
|
- (void)threadEntry:(id)arg
|
||||||
{
|
{
|
||||||
running = YES;
|
running = YES;
|
||||||
|
started = NO;
|
||||||
size_t eventCount = 0;
|
size_t eventCount = 0;
|
||||||
|
ssize_t delayedEvent = -1;
|
||||||
while (!stopping) {
|
while (!stopping) {
|
||||||
dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER);
|
|
||||||
if (++eventCount == 128) {
|
if (++eventCount == 128) {
|
||||||
[self updateDeviceFormat];
|
[self updateDeviceFormat];
|
||||||
eventCount = 0;
|
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;
|
stopped = YES;
|
||||||
[self stop];
|
[self stop];
|
||||||
|
@ -142,7 +209,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
^(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop) {
|
^(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop) {
|
||||||
if ([deviceName isEqualToString:userDeviceName]) {
|
if ([deviceName isEqualToString:userDeviceName]) {
|
||||||
err = [self setOutputDeviceByID:deviceID];
|
err = [self setOutputDeviceByID:deviceID];
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
// Disable. Would cause loop by triggering `-observeValueForKeyPath:ofObject:change:context:` above.
|
// Disable. Would cause loop by triggering `-observeValueForKeyPath:ofObject:change:context:` above.
|
||||||
// Update `outputDevice`, in case the ID has changed.
|
// Update `outputDevice`, in case the ID has changed.
|
||||||
|
@ -305,6 +372,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
running = NO;
|
running = NO;
|
||||||
stopping = NO;
|
stopping = NO;
|
||||||
stopped = NO;
|
stopped = NO;
|
||||||
|
paused = NO;
|
||||||
outputDeviceID = -1;
|
outputDeviceID = -1;
|
||||||
|
|
||||||
AudioComponentDescription desc;
|
AudioComponentDescription desc;
|
||||||
|
@ -338,8 +406,9 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
_deviceFormat = nil;
|
_deviceFormat = nil;
|
||||||
|
|
||||||
[self updateDeviceFormat];
|
[self updateDeviceFormat];
|
||||||
|
|
||||||
__block dispatch_semaphore_t sema = _sema;
|
__block Semaphore * writeSemaphore = self->writeSemaphore;
|
||||||
|
__block Semaphore * readSemaphore = self->readSemaphore;
|
||||||
__block OutputNode * outputController = self->outputController;
|
__block OutputNode * outputController = self->outputController;
|
||||||
__block float * volume = &self->volume;
|
__block float * volume = &self->volume;
|
||||||
|
|
||||||
|
@ -347,7 +416,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
{
|
{
|
||||||
void *readPointer = inputData->mBuffers[0].mData;
|
void *readPointer = inputData->mBuffers[0].mData;
|
||||||
|
|
||||||
int amountToRead, amountRead;
|
int amountToRead, amountRead = 0;
|
||||||
|
|
||||||
amountToRead = inputData->mBuffers[0].mDataByteSize;
|
amountToRead = inputData->mBuffers[0].mDataByteSize;
|
||||||
|
|
||||||
|
@ -355,20 +424,43 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
{
|
{
|
||||||
memset(readPointer, 0, amountToRead);
|
memset(readPointer, 0, amountToRead);
|
||||||
self->stopping = YES;
|
self->stopping = YES;
|
||||||
dispatch_semaphore_signal(sema);
|
|
||||||
return 0;
|
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
|
// Try repeatedly! Buffer wraps can cause a slight data shortage, as can
|
||||||
// unexpected track changes.
|
// unexpected track changes.
|
||||||
while ((amountRead < amountToRead) && [outputController shouldContinue] == YES)
|
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
|
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];
|
amountRead2 = [[outputController buffer] lengthAvailableToReadReturningPointer:&readPtr];
|
||||||
amountRead += amountRead2;
|
if (amountRead2 > (amountToRead - amountRead))
|
||||||
usleep(500);
|
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);
|
int framesRead = amountRead / sizeof(float);
|
||||||
|
@ -382,15 +474,11 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
memset(readPointer + amountRead, 0, amountToRead - amountRead);
|
memset(readPointer + amountRead, 0, amountToRead - amountRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch_semaphore_signal(sema);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
[_au allocateRenderResourcesAndReturnError:&err];
|
[_au allocateRenderResourcesAndReturnError:&err];
|
||||||
|
|
||||||
[NSThread detachNewThreadSelector:@selector(threadEntry:) toTarget:self withObject:nil];
|
|
||||||
|
|
||||||
return (err == nil);
|
return (err == nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,13 +489,15 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
|
|
||||||
- (void)start
|
- (void)start
|
||||||
{
|
{
|
||||||
NSError *err;
|
[self threadEntry:nil];
|
||||||
[_au startHardwareAndReturnError:&err];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)stop
|
- (void)stop
|
||||||
{
|
{
|
||||||
stopping = YES;
|
stopping = YES;
|
||||||
|
paused = NO;
|
||||||
|
[writeSemaphore signal];
|
||||||
|
[readSemaphore signal];
|
||||||
if (listenerapplied) {
|
if (listenerapplied) {
|
||||||
AudioObjectPropertyAddress theAddress = {
|
AudioObjectPropertyAddress theAddress = {
|
||||||
.mSelector = kAudioHardwarePropertyDefaultOutputDevice,
|
.mSelector = kAudioHardwarePropertyDefaultOutputDevice,
|
||||||
|
@ -425,8 +515,8 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
while (!stopped)
|
while (!stopped)
|
||||||
{
|
{
|
||||||
stopping = YES;
|
stopping = YES;
|
||||||
dispatch_semaphore_signal(_sema);
|
[readSemaphore signal];
|
||||||
usleep(500);
|
[writeSemaphore timedWait:5000];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,6 +529,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
|
|
||||||
- (void)pause
|
- (void)pause
|
||||||
{
|
{
|
||||||
|
paused = YES;
|
||||||
[_au stopHardware];
|
[_au stopHardware];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -446,6 +537,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
{
|
{
|
||||||
NSError *err;
|
NSError *err;
|
||||||
[_au startHardwareAndReturnError:&err];
|
[_au startHardwareAndReturnError:&err];
|
||||||
|
paused = NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
Loading…
Reference in New Issue