Cog Audio: Further overhaul output buffering and track queue code
This commit is contained in:
parent
1a7e7a4b70
commit
fbef034903
8 changed files with 93 additions and 35 deletions
|
@ -95,9 +95,10 @@
|
||||||
//- (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;
|
||||||
|
|
||||||
|
- (BOOL)chainQueueHasTracks;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@protocol AudioPlayerDelegate
|
@protocol AudioPlayerDelegate
|
||||||
|
|
|
@ -261,7 +261,7 @@
|
||||||
|
|
||||||
- (void)notifyStreamChanged:(id)userInfo
|
- (void)notifyStreamChanged:(id)userInfo
|
||||||
{
|
{
|
||||||
[self sendDelegateMethod:@selector(audioPlayer:didBeginStream:) withObject:userInfo waitUntilDone:NO];
|
[self sendDelegateMethod:@selector(audioPlayer:didBeginStream:) withObject:userInfo waitUntilDone:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)addChainToQueue:(BufferChain *)newChain
|
- (void)addChainToQueue:(BufferChain *)newChain
|
||||||
|
@ -446,12 +446,16 @@
|
||||||
[semaphore signal];
|
[semaphore signal];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[self notifyStreamChanged:[bufferChain userInfo]];
|
||||||
[output setEndOfStream:NO];
|
[output setEndOfStream:NO];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)endOfInputPlayedOut
|
- (BOOL)chainQueueHasTracks
|
||||||
{
|
{
|
||||||
[self notifyStreamChanged:[bufferChain userInfo]];
|
@synchronized (chainQueue) {
|
||||||
|
return [chainQueue count] > 0;
|
||||||
|
}
|
||||||
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj waitUntilDone:(BOOL)wait
|
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj waitUntilDone:(BOOL)wait
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
|
|
||||||
#import "ConverterNode.h"
|
#import "ConverterNode.h"
|
||||||
|
|
||||||
|
#import "BufferChain.h"
|
||||||
|
#import "OutputNode.h"
|
||||||
|
|
||||||
#import "Logging.h"
|
#import "Logging.h"
|
||||||
|
|
||||||
#import <audio/conversion/s16_to_float.h>
|
#import <audio/conversion/s16_to_float.h>
|
||||||
|
@ -1243,6 +1246,21 @@ static float db_to_scale(float db)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
void * ptr;
|
||||||
|
BufferChain * bufferChain = controller;
|
||||||
|
AudioPlayer * audioPlayer = [bufferChain controller];
|
||||||
|
VirtualRingBuffer * buffer = [[audioPlayer output] buffer];
|
||||||
|
dataRead = [buffer lengthAvailableToReadReturningPointer:&ptr];
|
||||||
|
if (dataRead) {
|
||||||
|
[refillNode writeData:(float*)ptr floatCount:dataRead / sizeof(float)];
|
||||||
|
[buffer didReadLength:dataRead];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
[self setupWithInputFormat:previousOutputFormat outputFormat:outputFormat];
|
[self setupWithInputFormat:previousOutputFormat outputFormat:outputFormat];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -32,7 +32,8 @@
|
||||||
- (void)resetAmountPlayed;
|
- (void)resetAmountPlayed;
|
||||||
|
|
||||||
- (void)endOfInputPlayed;
|
- (void)endOfInputPlayed;
|
||||||
- (void)endOfInputPlayedOut;
|
|
||||||
|
- (BOOL)chainQueueHasTracks;
|
||||||
|
|
||||||
- (double)secondsBuffered;
|
- (double)secondsBuffered;
|
||||||
|
|
||||||
|
|
|
@ -69,9 +69,9 @@
|
||||||
[controller endOfInputPlayed];
|
[controller endOfInputPlayed];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)endOfInputPlayedOut
|
- (BOOL)chainQueueHasTracks
|
||||||
{
|
{
|
||||||
[controller endOfInputPlayedOut];
|
return [controller chainQueueHasTracks];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (double)secondsBuffered
|
- (double)secondsBuffered
|
||||||
|
@ -96,7 +96,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (double)amountPlayed
|
- (double)amountPlayed
|
||||||
{
|
{
|
||||||
return amountPlayed;
|
return amountPlayed;
|
||||||
|
|
|
@ -13,13 +13,29 @@
|
||||||
|
|
||||||
@implementation RefillNode
|
@implementation RefillNode
|
||||||
|
|
||||||
- (id)initWithController:(id)c previous:(id)p {
|
- (id)initWithController:(id)c previous:(id)p
|
||||||
self = [super initWithController:c previous:p];
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (self)
|
||||||
|
{
|
||||||
|
// This special node should be able to handle up to four buffers
|
||||||
|
buffer = [[VirtualRingBuffer alloc] initWithLength:BUFFER_SIZE * 4];
|
||||||
|
semaphore = [[Semaphore alloc] init];
|
||||||
|
readLock = [[NSLock alloc] init];
|
||||||
|
writeLock = [[NSLock alloc] init];
|
||||||
|
|
||||||
|
initialBufferFilled = NO;
|
||||||
|
|
||||||
|
controller = c;
|
||||||
|
endOfStream = NO;
|
||||||
|
shouldContinue = YES;
|
||||||
|
|
||||||
|
[self setPreviousNode:p];
|
||||||
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (void)writeData:(float *)data floatCount:(size_t)count
|
- (void)writeData:(float *)data floatCount:(size_t)count
|
||||||
{
|
{
|
||||||
[self writeData:data amount:(int)(count * sizeof(float))];
|
[self writeData:data amount:(int)(count * sizeof(float))];
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
#import <AudioUnit/AudioUnit.h>
|
#import <AudioUnit/AudioUnit.h>
|
||||||
#import <AVFoundation/AVFoundation.h>
|
#import <AVFoundation/AVFoundation.h>
|
||||||
|
|
||||||
|
#import <stdatomic.h>
|
||||||
|
|
||||||
#import "Semaphore.h"
|
#import "Semaphore.h"
|
||||||
|
|
||||||
@class OutputNode;
|
@class OutputNode;
|
||||||
|
@ -30,6 +32,8 @@
|
||||||
BOOL started;
|
BOOL started;
|
||||||
BOOL paused;
|
BOOL paused;
|
||||||
|
|
||||||
|
atomic_long bytesRendered;
|
||||||
|
|
||||||
BOOL listenerapplied;
|
BOOL listenerapplied;
|
||||||
|
|
||||||
float volume;
|
float volume;
|
||||||
|
|
|
@ -29,6 +29,8 @@ extern void scale_by_volume(float * buffer, size_t count, float volume);
|
||||||
running = NO;
|
running = NO;
|
||||||
started = NO;
|
started = NO;
|
||||||
|
|
||||||
|
atomic_init(&bytesRendered, 0);
|
||||||
|
|
||||||
writeSemaphore = [[Semaphore alloc] init];
|
writeSemaphore = [[Semaphore alloc] init];
|
||||||
readSemaphore = [[Semaphore alloc] init];
|
readSemaphore = [[Semaphore alloc] init];
|
||||||
|
|
||||||
|
@ -56,14 +58,9 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)signalEndOfStream
|
- (void)signalEndOfStream
|
||||||
{
|
|
||||||
[outputController endOfInputPlayed];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)signalEndOfStreamBuffered
|
|
||||||
{
|
{
|
||||||
[outputController resetAmountPlayed];
|
[outputController resetAmountPlayed];
|
||||||
[outputController endOfInputPlayedOut];
|
[outputController endOfInputPlayed];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)threadEntry:(id)arg
|
- (void)threadEntry:(id)arg
|
||||||
|
@ -71,7 +68,9 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
running = YES;
|
running = YES;
|
||||||
started = NO;
|
started = NO;
|
||||||
size_t eventCount = 0;
|
size_t eventCount = 0;
|
||||||
ssize_t delayedEvent = -1;
|
atomic_store(&bytesRendered, 0);
|
||||||
|
NSMutableArray *delayedEvents = [[NSMutableArray alloc] init];
|
||||||
|
BOOL delayedEventsPopped = YES;
|
||||||
while (!stopping) {
|
while (!stopping) {
|
||||||
if (++eventCount == 128) {
|
if (++eventCount == 128) {
|
||||||
[self updateDeviceFormat];
|
[self updateDeviceFormat];
|
||||||
|
@ -83,6 +82,20 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
[outputController setShouldReset:NO];
|
[outputController setShouldReset:NO];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while ([delayedEvents count]) {
|
||||||
|
size_t localBytesRendered = atomic_load_explicit(&bytesRendered, memory_order_relaxed);
|
||||||
|
if (localBytesRendered >= [[delayedEvents objectAtIndex:0] longValue]) {
|
||||||
|
if ([outputController chainQueueHasTracks])
|
||||||
|
delayedEventsPopped = YES;
|
||||||
|
[self signalEndOfStream];
|
||||||
|
[delayedEvents removeObjectAtIndex:0];
|
||||||
|
}
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stopping)
|
||||||
|
break;
|
||||||
|
|
||||||
void *writePtr;
|
void *writePtr;
|
||||||
int toWrite = [[outputController buffer] lengthAvailableToWriteReturningPointer:&writePtr];
|
int toWrite = [[outputController buffer] lengthAvailableToWriteReturningPointer:&writePtr];
|
||||||
int bytesRead = 0;
|
int bytesRead = 0;
|
||||||
|
@ -93,15 +106,6 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
if (bytesRead) {
|
if (bytesRead) {
|
||||||
[[outputController buffer] didWriteLength:bytesRead];
|
[[outputController buffer] didWriteLength:bytesRead];
|
||||||
[readSemaphore signal];
|
[readSemaphore signal];
|
||||||
if (delayedEvent >= 0) {
|
|
||||||
if (bytesRead >= delayedEvent) {
|
|
||||||
delayedEvent = -1;
|
|
||||||
[self signalEndOfStreamBuffered];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
delayedEvent -= bytesRead;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if ([outputController shouldContinue] == NO)
|
else if ([outputController shouldContinue] == NO)
|
||||||
|
@ -117,15 +121,19 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// End of input possibly reached
|
// End of input possibly reached
|
||||||
if ([outputController endOfStream] == YES)
|
if (delayedEventsPopped && [outputController endOfStream] == YES)
|
||||||
{
|
{
|
||||||
[self signalEndOfStream];
|
long bytesBuffered = [[outputController buffer] bufferedLength];
|
||||||
if (delayedEvent >= 0) {
|
bytesBuffered += atomic_load_explicit(&bytesRendered, memory_order_relaxed);
|
||||||
[self signalEndOfStreamBuffered];
|
[delayedEvents addObject:[NSNumber numberWithLong:bytesBuffered]];
|
||||||
delayedEvent = -1;
|
delayedEventsPopped = NO;
|
||||||
|
if (!started) {
|
||||||
|
started = YES;
|
||||||
|
if (!paused) {
|
||||||
|
NSError *err;
|
||||||
|
[_au startHardwareAndReturnError:&err];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
delayedEvent = [[outputController buffer] bufferedLength];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[readSemaphore signal];
|
[readSemaphore signal];
|
||||||
|
@ -411,6 +419,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
__block Semaphore * readSemaphore = self->readSemaphore;
|
__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;
|
||||||
|
__block atomic_long * bytesRendered = &self->bytesRendered;
|
||||||
|
|
||||||
_au.outputProvider = ^AUAudioUnitStatus(AudioUnitRenderActionFlags * actionFlags, const AudioTimeStamp * timestamp, AUAudioFrameCount frameCount, NSInteger inputBusNumber, AudioBufferList * inputData)
|
_au.outputProvider = ^AUAudioUnitStatus(AudioUnitRenderActionFlags * actionFlags, const AudioTimeStamp * timestamp, AUAudioFrameCount frameCount, NSInteger inputBusNumber, AudioBufferList * inputData)
|
||||||
{
|
{
|
||||||
|
@ -420,9 +429,12 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
|
|
||||||
amountToRead = inputData->mBuffers[0].mDataByteSize;
|
amountToRead = inputData->mBuffers[0].mDataByteSize;
|
||||||
|
|
||||||
if (self->stopping == YES || [outputController shouldContinue] == NO)
|
if (self->stopping == YES || [outputController shouldContinue] == NO ||
|
||||||
|
([[outputController buffer] isEmpty] && ![outputController chainQueueHasTracks]))
|
||||||
{
|
{
|
||||||
|
// Chain is dead, fill out the serial number pointer forever with silence
|
||||||
memset(readPointer, 0, amountToRead);
|
memset(readPointer, 0, amountToRead);
|
||||||
|
atomic_fetch_add(bytesRendered, amountToRead);
|
||||||
self->stopping = YES;
|
self->stopping = YES;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -438,6 +450,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
amountRead = toRead;
|
amountRead = toRead;
|
||||||
[[outputController buffer] didReadLength:toRead];
|
[[outputController buffer] didReadLength:toRead];
|
||||||
[outputController incrementAmountPlayed:amountRead];
|
[outputController incrementAmountPlayed:amountRead];
|
||||||
|
atomic_fetch_add(bytesRendered, amountRead);
|
||||||
[writeSemaphore signal];
|
[writeSemaphore signal];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -450,6 +463,8 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
if (amountRead2 > (amountToRead - amountRead))
|
if (amountRead2 > (amountToRead - amountRead))
|
||||||
amountRead2 = amountToRead - amountRead;
|
amountRead2 = amountToRead - amountRead;
|
||||||
if (amountRead2) {
|
if (amountRead2) {
|
||||||
|
atomic_fetch_add(bytesRendered, amountRead2);
|
||||||
|
|
||||||
memcpy(readPointer + amountRead, readPtr, amountRead2);
|
memcpy(readPointer + amountRead, readPtr, amountRead2);
|
||||||
[[outputController buffer] didReadLength:amountRead2];
|
[[outputController buffer] didReadLength:amountRead2];
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue