Cog Audio: Enhance playback queue handler, so it always halts buffering when there are at least 30 seconds worth of buffers filled, possibly spanning multiple files. Also improve the chain reset function so that playlist changes and playback order control reset the queue properly when the queue refill function is currently entered in another thread.
parent
7cc89c9f92
commit
c8d2864862
|
@ -8,6 +8,10 @@
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
#import <CogAudio/Semaphore.h>
|
||||||
|
|
||||||
|
#import <stdatomic.h>
|
||||||
|
|
||||||
@class BufferChain;
|
@class BufferChain;
|
||||||
@class OutputNode;
|
@class OutputNode;
|
||||||
|
|
||||||
|
@ -30,6 +34,11 @@
|
||||||
BOOL endOfInputReached;
|
BOOL endOfInputReached;
|
||||||
BOOL startedPaused;
|
BOOL startedPaused;
|
||||||
BOOL initialBufferFilled;
|
BOOL initialBufferFilled;
|
||||||
|
|
||||||
|
Semaphore *semaphore;
|
||||||
|
|
||||||
|
atomic_bool resettingNow;
|
||||||
|
atomic_int refCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)init;
|
- (id)init;
|
||||||
|
|
|
@ -29,6 +29,11 @@
|
||||||
endOfInputReached = NO;
|
endOfInputReached = NO;
|
||||||
|
|
||||||
chainQueue = [[NSMutableArray alloc] init];
|
chainQueue = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
|
semaphore = [[Semaphore alloc] init];
|
||||||
|
|
||||||
|
atomic_init(&resettingNow, false);
|
||||||
|
atomic_init(&refCount, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
|
@ -189,6 +194,21 @@
|
||||||
// Called when the playlist changed before we actually started playing a requested stream. We will re-request.
|
// Called when the playlist changed before we actually started playing a requested stream. We will re-request.
|
||||||
- (void)resetNextStreams
|
- (void)resetNextStreams
|
||||||
{
|
{
|
||||||
|
// This sucks! And since the thread that's inside the function can be calling
|
||||||
|
// event dispatches, we have to pump the message queue if we're on the main
|
||||||
|
// thread. Damn.
|
||||||
|
if (atomic_load_explicit(&refCount, memory_order_relaxed) != 0) {
|
||||||
|
BOOL mainThread = (dispatch_queue_get_label(dispatch_get_main_queue()) == dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
|
||||||
|
atomic_store(&resettingNow, true);
|
||||||
|
while (atomic_load_explicit(&refCount, memory_order_relaxed) != 0) {
|
||||||
|
[semaphore signal]; // Gotta poke this periodically
|
||||||
|
if (mainThread)
|
||||||
|
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.001]];
|
||||||
|
else
|
||||||
|
usleep(500);
|
||||||
|
}
|
||||||
|
atomic_store(&resettingNow, false);
|
||||||
|
}
|
||||||
@synchronized (chainQueue) {
|
@synchronized (chainQueue) {
|
||||||
for (id anObject in chainQueue) {
|
for (id anObject in chainQueue) {
|
||||||
[anObject setShouldContinue:NO];
|
[anObject setShouldContinue:NO];
|
||||||
|
@ -246,31 +266,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)endOfInputReached:(BufferChain *)sender //Sender is a BufferChain
|
- (BOOL)endOfInputReached:(BufferChain *)sender //Sender is a BufferChain
|
||||||
{
|
|
||||||
// Stop single or series of short tracks from queueing forever
|
|
||||||
{
|
|
||||||
unsigned long queueCount;
|
|
||||||
|
|
||||||
@synchronized (chainQueue) {
|
|
||||||
queueCount = [chainQueue count];
|
|
||||||
}
|
|
||||||
|
|
||||||
while (queueCount >= 5)
|
|
||||||
{
|
|
||||||
usleep(10000);
|
|
||||||
@synchronized (chainQueue) {
|
|
||||||
queueCount = [chainQueue count];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [self endOfInputReachedInternal:sender];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)endOfInputReachedInternal:(BufferChain *)sender //Sender is a BufferChain
|
|
||||||
{
|
{
|
||||||
BufferChain *newChain = nil;
|
BufferChain *newChain = nil;
|
||||||
|
|
||||||
|
if (atomic_load_explicit(&resettingNow, memory_order_relaxed))
|
||||||
|
return YES;
|
||||||
|
|
||||||
|
atomic_fetch_add(&refCount, 1);
|
||||||
|
|
||||||
@synchronized (chainQueue) {
|
@synchronized (chainQueue) {
|
||||||
// No point in constructing new chain for the next playlist entry
|
// No point in constructing new chain for the next playlist entry
|
||||||
// if there's already one at the head of chainQueue... r-r-right?
|
// if there's already one at the head of chainQueue... r-r-right?
|
||||||
|
@ -278,6 +281,7 @@
|
||||||
{
|
{
|
||||||
if ([chain isRunning])
|
if ([chain isRunning])
|
||||||
{
|
{
|
||||||
|
atomic_fetch_sub(&refCount, 1);
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -287,17 +291,42 @@
|
||||||
//{
|
//{
|
||||||
// return YES;
|
// return YES;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
nextStreamUserInfo = [sender userInfo];
|
|
||||||
|
|
||||||
nextStreamRGInfo = [sender rgInfo];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double duration = 0.0;
|
||||||
|
|
||||||
|
@synchronized (chainQueue) {
|
||||||
|
for (BufferChain *chain in chainQueue) {
|
||||||
|
duration += [chain secondsBuffered];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (duration >= 30.0)
|
||||||
|
{
|
||||||
|
[semaphore wait];
|
||||||
|
if (atomic_load_explicit(&resettingNow, memory_order_relaxed)) {
|
||||||
|
atomic_fetch_sub(&refCount, 1);
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
@synchronized (chainQueue) {
|
||||||
|
duration = 0.0;
|
||||||
|
for (BufferChain *chain in chainQueue) {
|
||||||
|
duration += [chain secondsBuffered];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextStreamUserInfo = [sender userInfo];
|
||||||
|
|
||||||
|
nextStreamRGInfo = [sender rgInfo];
|
||||||
|
|
||||||
// This call can sometimes lead to invoking a chainQueue block on another thread
|
// This call can sometimes lead to invoking a chainQueue block on another thread
|
||||||
[self requestNextStream: nextStreamUserInfo];
|
[self requestNextStream: nextStreamUserInfo];
|
||||||
|
|
||||||
if (!nextStream)
|
if (!nextStream) {
|
||||||
|
atomic_fetch_sub(&refCount, 1);
|
||||||
return YES;
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
@synchronized (chainQueue) {
|
@synchronized (chainQueue) {
|
||||||
newChain = [[BufferChain alloc] initWithController:self];
|
newChain = [[BufferChain alloc] initWithController:self];
|
||||||
|
@ -326,6 +355,7 @@
|
||||||
//Keep on-playin
|
//Keep on-playin
|
||||||
newChain = nil;
|
newChain = nil;
|
||||||
|
|
||||||
|
atomic_fetch_sub(&refCount, 1);
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -337,6 +367,7 @@
|
||||||
if (nextStream == nil)
|
if (nextStream == nil)
|
||||||
{
|
{
|
||||||
newChain = nil;
|
newChain = nil;
|
||||||
|
atomic_fetch_sub(&refCount, 1);
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,6 +393,7 @@
|
||||||
// - head of chainQueue is the buffer chain for the next entry (which has launched its threads already)
|
// - head of chainQueue is the buffer chain for the next entry (which has launched its threads already)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
atomic_fetch_sub(&refCount, 1);
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,6 +422,8 @@
|
||||||
|
|
||||||
[chainQueue removeObjectAtIndex:0];
|
[chainQueue removeObjectAtIndex:0];
|
||||||
DLog(@"New!!! %@ %@", bufferChain, [[bufferChain inputNode] decoder]);
|
DLog(@"New!!! %@ %@", bufferChain, [[bufferChain inputNode] decoder]);
|
||||||
|
|
||||||
|
[semaphore signal];
|
||||||
}
|
}
|
||||||
|
|
||||||
[self notifyStreamChanged:[bufferChain userInfo]];
|
[self notifyStreamChanged:[bufferChain userInfo]];
|
||||||
|
|
|
@ -73,4 +73,6 @@
|
||||||
- (ConverterNode *)converter;
|
- (ConverterNode *)converter;
|
||||||
- (AudioStreamBasicDescription)inputFormat;
|
- (AudioStreamBasicDescription)inputFormat;
|
||||||
|
|
||||||
|
- (double)secondsBuffered;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -228,4 +228,15 @@
|
||||||
return inputFormat;
|
return inputFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (double)secondsBuffered
|
||||||
|
{
|
||||||
|
double duration = 0.0;
|
||||||
|
Node * node = [self finalNode];
|
||||||
|
while (node) {
|
||||||
|
duration += [node secondsBuffered];
|
||||||
|
node = [node previousNode];
|
||||||
|
}
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -1000,4 +1000,9 @@ static float db_to_scale(float db)
|
||||||
floatSize = 0;
|
floatSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (double) secondsBuffered
|
||||||
|
{
|
||||||
|
return ((double)[buffer bufferedLength] / (outputFormat.mSampleRate * outputFormat.mBytesPerPacket));
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -236,4 +236,10 @@
|
||||||
return decoder;
|
return decoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (double) secondsBuffered
|
||||||
|
{
|
||||||
|
AudioStreamBasicDescription inputFormat = [[[controller controller] bufferChain] inputFormat];
|
||||||
|
return ((double)[buffer bufferedLength] / (inputFormat.mSampleRate * inputFormat.mBytesPerPacket));
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -60,4 +60,6 @@
|
||||||
- (BOOL)endOfStream;
|
- (BOOL)endOfStream;
|
||||||
- (void)setEndOfStream:(BOOL)e;
|
- (void)setEndOfStream:(BOOL)e;
|
||||||
|
|
||||||
|
- (double)secondsBuffered;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -219,5 +219,11 @@
|
||||||
return shouldReset;
|
return shouldReset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Buffering nodes should implement this
|
||||||
|
- (double)secondsBuffered
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
Loading…
Reference in New Issue