The changes include no longer leaving the workgroup for seeking or for converter format changes, and also still leaving the workgroup on thread termination if there was an error with intervals starting or finishing. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
289 lines
7.4 KiB
Objective-C
289 lines
7.4 KiB
Objective-C
//
|
|
// InputNode.m
|
|
// Cog
|
|
//
|
|
// Created by Vincent Spader on 8/2/05.
|
|
// Copyright 2005 Vincent Spader. All rights reserved.
|
|
//
|
|
|
|
#import "InputNode.h"
|
|
#import "AudioPlayer.h"
|
|
#import "BufferChain.h"
|
|
#import "CoreAudioUtils.h"
|
|
#import "OutputNode.h"
|
|
#import "Plugin.h"
|
|
|
|
#import "NSDictionary+Merge.h"
|
|
|
|
#import "Logging.h"
|
|
|
|
@implementation InputNode
|
|
|
|
static void *kInputNodeContext = &kInputNodeContext;
|
|
|
|
@synthesize exitAtTheEndOfTheStream;
|
|
|
|
- (id)initWithController:(id)c previous:(id)p {
|
|
self = [super initWithController:c previous:p];
|
|
if(self) {
|
|
exitAtTheEndOfTheStream = [[Semaphore alloc] init];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)openWithSource:(id<CogSource>)source {
|
|
decoder = [AudioDecoder audioDecoderForSource:source];
|
|
|
|
if(decoder == nil)
|
|
return NO;
|
|
|
|
[self registerObservers];
|
|
|
|
if(![decoder open:source]) {
|
|
ALog(@"Couldn't open decoder...");
|
|
return NO;
|
|
}
|
|
|
|
NSDictionary *properties = [decoder properties];
|
|
int bitsPerSample = [[properties objectForKey:@"bitsPerSample"] intValue];
|
|
int channels = [[properties objectForKey:@"channels"] intValue];
|
|
|
|
bytesPerFrame = ((bitsPerSample + 7) / 8) * channels;
|
|
|
|
nodeFormat = propertiesToASBD(properties);
|
|
if([properties valueForKey:@"channelConfig"])
|
|
nodeChannelConfig = [[properties valueForKey:@"channelConfig"] unsignedIntValue];
|
|
nodeLossless = [[properties valueForKey:@"encoding"] isEqualToString:@"lossless"];
|
|
|
|
shouldContinue = YES;
|
|
shouldSeek = NO;
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)openWithDecoder:(id<CogDecoder>)d {
|
|
DLog(@"Opening with old decoder: %@", d);
|
|
decoder = d;
|
|
|
|
NSDictionary *properties = [decoder properties];
|
|
int bitsPerSample = [[properties objectForKey:@"bitsPerSample"] intValue];
|
|
int channels = [[properties objectForKey:@"channels"] intValue];
|
|
|
|
bytesPerFrame = ((bitsPerSample + 7) / 8) * channels;
|
|
|
|
nodeFormat = propertiesToASBD(properties);
|
|
if([properties valueForKey:@"channelConfig"])
|
|
nodeChannelConfig = [[properties valueForKey:@"channelConfig"] unsignedIntValue];
|
|
nodeLossless = [[properties valueForKey:@"encoding"] isEqualToString:@"lossless"];
|
|
|
|
[self registerObservers];
|
|
|
|
shouldContinue = YES;
|
|
shouldSeek = NO;
|
|
|
|
DLog(@"DONES: %@", decoder);
|
|
return YES;
|
|
}
|
|
|
|
- (void)registerObservers {
|
|
if(!observersAdded) {
|
|
DLog(@"REGISTERING OBSERVERS");
|
|
[decoder addObserver:self
|
|
forKeyPath:@"properties"
|
|
options:(NSKeyValueObservingOptionNew)
|
|
context:kInputNodeContext];
|
|
|
|
[decoder addObserver:self
|
|
forKeyPath:@"metadata"
|
|
options:(NSKeyValueObservingOptionNew)
|
|
context:kInputNodeContext];
|
|
|
|
observersAdded = YES;
|
|
}
|
|
}
|
|
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath
|
|
ofObject:(id)object
|
|
change:(NSDictionary *)change
|
|
context:(void *)context {
|
|
if(context == kInputNodeContext) {
|
|
DLog(@"SOMETHING CHANGED!");
|
|
if([keyPath isEqual:@"properties"]) {
|
|
DLog(@"Input format changed");
|
|
// Converter may need resetting, it'll do that when it reaches the new chunks
|
|
NSDictionary *properties = [decoder properties];
|
|
|
|
int bitsPerSample = [[properties objectForKey:@"bitsPerSample"] intValue];
|
|
int channels = [[properties objectForKey:@"channels"] intValue];
|
|
|
|
bytesPerFrame = ((bitsPerSample + 7) / 8) * channels;
|
|
|
|
nodeFormat = propertiesToASBD(properties);
|
|
nodeChannelConfig = [[properties valueForKey:@"channelConfig"] unsignedIntValue];
|
|
nodeLossless = [[properties valueForKey:@"encoding"] isEqualToString:@"lossless"];
|
|
} else if([keyPath isEqual:@"metadata"]) {
|
|
// Inform something of metadata change
|
|
NSDictionary *entryProperties = [decoder properties];
|
|
if(entryProperties == nil)
|
|
return;
|
|
|
|
NSDictionary *entryInfo = [NSDictionary dictionaryByMerging:entryProperties with:[decoder metadata]];
|
|
|
|
[controller pushInfo:entryInfo];
|
|
}
|
|
} else {
|
|
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
|
}
|
|
}
|
|
|
|
- (void)process {
|
|
int amountInBuffer = 0;
|
|
int bytesInBuffer = 0;
|
|
void *inputBuffer = malloc(CHUNK_SIZE * 8 * 18); // Maximum 18 channels, dunno what we'll receive
|
|
|
|
BOOL shouldClose = YES;
|
|
BOOL seekError = NO;
|
|
|
|
BOOL isError = NO;
|
|
|
|
if([decoder respondsToSelector:@selector(isSilence)]) {
|
|
if([decoder isSilence]) {
|
|
isError = YES;
|
|
}
|
|
}
|
|
|
|
[controller setError:isError];
|
|
|
|
while([self shouldContinue] == YES && [self endOfStream] == NO) {
|
|
if(shouldSeek == YES) {
|
|
BufferChain *bufferChain = [[controller controller] bufferChain];
|
|
ConverterNode *converter = [bufferChain converter];
|
|
DLog(@"SEEKING! Resetting Buffer");
|
|
|
|
amountInBuffer = 0;
|
|
// This resets the converter's buffer
|
|
[self resetBuffer];
|
|
[converter resetBuffer];
|
|
[converter inputFormatDidChange:[bufferChain inputFormat] inputConfig:[bufferChain inputConfig]];
|
|
|
|
DLog(@"Reset buffer!");
|
|
|
|
DLog(@"SEEKING!");
|
|
seekError = [decoder seek:seekFrame] < 0;
|
|
|
|
shouldSeek = NO;
|
|
DLog(@"Seeked! Resetting Buffer");
|
|
initialBufferFilled = NO;
|
|
|
|
if(seekError) {
|
|
[controller setError:YES];
|
|
}
|
|
}
|
|
|
|
if(amountInBuffer < CHUNK_SIZE) {
|
|
int framesToRead = CHUNK_SIZE - amountInBuffer;
|
|
int framesRead;
|
|
@autoreleasepool {
|
|
[self startWorkslice];
|
|
framesRead = [decoder readAudio:((char *)inputBuffer) + bytesInBuffer frames:framesToRead];
|
|
[self endWorkslice];
|
|
}
|
|
|
|
if(framesRead > 0 && !seekError) {
|
|
amountInBuffer += framesRead;
|
|
bytesInBuffer += framesRead * bytesPerFrame;
|
|
[self writeData:inputBuffer amount:bytesInBuffer];
|
|
amountInBuffer = 0;
|
|
bytesInBuffer = 0;
|
|
} else {
|
|
DLog(@"End of stream? %@", [self properties]);
|
|
|
|
endOfStream = YES;
|
|
shouldClose = [controller endOfInputReached]; // Lets us know if we should keep going or not (occassionally, for track changes within a file)
|
|
DLog(@"closing? is %i", shouldClose);
|
|
|
|
// Move this here, so that the above endOfInputReached has a chance to queue another track before starting output
|
|
// Technically, the output should still play out its buffer first before checking if it should stop
|
|
if(initialBufferFilled == NO) {
|
|
[controller initialBufferFilled:self];
|
|
}
|
|
|
|
// wait before exiting, as we might still get seeking request
|
|
DLog("InputNode: Before wait")
|
|
[exitAtTheEndOfTheStream waitIndefinitely];
|
|
DLog("InputNode: After wait, should seek = %d", shouldSeek) if(shouldSeek) {
|
|
endOfStream = NO;
|
|
shouldClose = NO;
|
|
continue;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(shouldClose)
|
|
[decoder close];
|
|
|
|
free(inputBuffer);
|
|
|
|
[exitAtTheEndOfTheStream signal];
|
|
|
|
DLog("Input node thread stopping");
|
|
}
|
|
|
|
- (void)seek:(long)frame {
|
|
seekFrame = frame;
|
|
shouldSeek = YES;
|
|
DLog(@"Should seek!");
|
|
[semaphore signal];
|
|
|
|
if(endOfStream) {
|
|
[exitAtTheEndOfTheStream signal];
|
|
}
|
|
}
|
|
|
|
- (BOOL)setTrack:(NSURL *)track {
|
|
if([decoder respondsToSelector:@selector(setTrack:)] && [decoder setTrack:track]) {
|
|
DLog(@"SET TRACK!");
|
|
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (void)removeObservers {
|
|
if(observersAdded) {
|
|
[decoder removeObserver:self forKeyPath:@"properties" context:kInputNodeContext];
|
|
[decoder removeObserver:self forKeyPath:@"metadata" context:kInputNodeContext];
|
|
observersAdded = NO;
|
|
}
|
|
}
|
|
|
|
- (void)setShouldContinue:(BOOL)s {
|
|
[super setShouldContinue:s];
|
|
if(!s)
|
|
[self removeObservers];
|
|
}
|
|
|
|
- (void)dealloc {
|
|
DLog(@"Input Node dealloc");
|
|
[self removeObservers];
|
|
}
|
|
|
|
- (NSDictionary *)properties {
|
|
return [decoder properties];
|
|
}
|
|
|
|
- (id<CogDecoder>)decoder {
|
|
return decoder;
|
|
}
|
|
|
|
- (double)secondsBuffered {
|
|
return [buffer listDuration];
|
|
}
|
|
|
|
@end
|