Added support for gapless multitrack files.

Modified cue sheet plugin to use it.
CQTexperiment
vspader 2007-10-11 02:08:29 +00:00
parent 4bcf22ced2
commit b57bcc121c
10 changed files with 208 additions and 46 deletions

View File

@ -65,7 +65,7 @@
- (void)notifyStreamChanged:(id)userInfo;
- (void)notifyStreamChangedMainThread:(id)userInfo;
- (void)endOfInputReached:(BufferChain *)sender;
- (BOOL)endOfInputReached:(BufferChain *)sender;
- (void)setShouldContinue:(BOOL)s;
- (BufferChain *)bufferChain;
- (void)launchOutputThread;

View File

@ -134,6 +134,7 @@
[output setVolume:v];
}
//Note: This is called
- (void)setNextStream:(NSURL *)url
{
[self setNextStream:url withUserInfo:nil];
@ -176,8 +177,6 @@
}
}
- (void)requestNextStream:(id)userInfo
{
[self sendDelegateMethod:@selector(audioPlayer:requestNextStream:) withObject:userInfo waitUntilDone:YES];
@ -188,35 +187,68 @@
[self sendDelegateMethod:@selector(audioPlayer:streamChanged:) withObject:userInfo waitUntilDone:NO];
}
- (void)endOfInputReached:(BufferChain *)sender //Sender is a BufferChain
- (void)addChainToQueue:(BufferChain *)newChain
{
BufferChain *newChain = nil;
nextStreamUserInfo = [sender userInfo];
[nextStreamUserInfo retain]; //Retained because when setNextStream is called, it will be released!!!
do {
[newChain release];
[self requestNextStream: nextStreamUserInfo];
if (nextStream == nil)
{
return;
}
newChain = [[BufferChain alloc] initWithController:self];
} while (![newChain open:nextStream withOutputFormat:[output format]]);
[newChain setUserInfo: nextStreamUserInfo];
[newChain setShouldContinue:YES];
[newChain launchThreads];
[chainQueue insertObject:newChain atIndex:[chainQueue count]];
}
- (BOOL)endOfInputReached:(BufferChain *)sender //Sender is a BufferChain
{
BufferChain *newChain = nil;
nextStreamUserInfo = [sender userInfo];
[nextStreamUserInfo retain]; //Retained because when setNextStream is called, it will be released!!!
[self requestNextStream: nextStreamUserInfo];
newChain = [[BufferChain alloc] initWithController:self];
BufferChain *lastChain = [chainQueue lastObject];
if (lastChain == nil) {
lastChain = bufferChain;
}
if ([[nextStream scheme] isEqualToString:[[lastChain streamURL] scheme]]
&& [[nextStream host] isEqualToString:[[lastChain streamURL] host]]
&& [[nextStream path] isEqualToString:[[lastChain streamURL] path]])
{
if ([lastChain setTrack:nextStream]) {
[newChain openWithInput:[lastChain inputNode] withOutputFormat:[output format]];
[newChain setStreamURL:nextStream];
[newChain setUserInfo:nextStreamUserInfo];
[self addChainToQueue:newChain];
NSLog(@"TRACK SET!!! %@", newChain);
//Keep on-playin
[newChain release];
return NO;
}
}
while (![newChain open:nextStream withOutputFormat:[output format]])
{
if (nextStream == nil)
{
return YES;
}
[newChain release];
[self requestNextStream: nextStreamUserInfo];
newChain = [[BufferChain alloc] initWithController:self];
}
[self addChainToQueue:newChain];
[newChain release];
return YES;
}
- (void)endOfInputPlayed
@ -233,6 +265,8 @@
bufferChain = [chainQueue objectAtIndex:0];
[bufferChain retain];
NSLog(@"New!!! %@ %@", bufferChain, [[bufferChain inputNode] decoder]);
[chainQueue removeObjectAtIndex:0];
[self notifyStreamChanged:[bufferChain userInfo]];

View File

@ -26,11 +26,18 @@
- (id)initWithController:(id)c;
- (void)buildChain;
- (BOOL)open:(NSURL *)url withOutputFormat:(AudioStreamBasicDescription)outputFormat;
//Used when changing tracks to reuse the same decoder
- (BOOL)openWithInput:(InputNode *)i withOutputFormat:(AudioStreamBasicDescription)outputFormat;
- (void)seek:(double)time;
- (void)launchThreads;
- (InputNode *)inputNode;
- (id)finalNode;
- (id)userInfo;
@ -43,7 +50,7 @@
- (void)initialBufferFilled;
- (void)endOfInputReached;
- (BOOL)endOfInputReached;
- (BOOL)setTrack:(NSURL *)track;
@end

View File

@ -55,7 +55,7 @@
}
if (![inputNode openURL:url withSource:source outputFormat:outputFormat])
if (![inputNode openURL:url withSource:source])
return NO;
if (![converterNode setupWithInputFormat:propertiesToASBD([inputNode properties]) outputFormat:outputFormat])
@ -64,8 +64,25 @@
return YES;
}
- (BOOL)openWithInput:(InputNode *)i withOutputFormat:(AudioStreamBasicDescription)outputFormat
{
NSLog(@"New buffer chain!");
[self buildChain];
if (![inputNode openWithDecoder:[i decoder]])
return NO;
if (![converterNode setupWithInputFormat:propertiesToASBD([inputNode properties]) outputFormat:outputFormat])
return NO;
NSLog(@"Buffer chain made");
return YES;
}
- (void)launchThreads
{
NSLog(@"Properties: %@", [inputNode properties]);
[inputNode launchThread];
[converterNode launchThread];
}
@ -97,9 +114,14 @@
[inputNode seek:time];
}
- (void)endOfInputReached
- (BOOL)endOfInputReached
{
[controller endOfInputReached:self];
return [controller endOfInputReached:self];
}
- (BOOL)setTrack: (NSURL *)track
{
return [inputNode setTrack:track];
}
- (void)initialBufferFilled
@ -107,6 +129,11 @@
[controller launchOutputThread];
}
- (InputNode *)inputNode
{
return inputNode;
}
- (id)finalNode
{
return finalNode;

View File

@ -134,6 +134,9 @@ static OSStatus ACInputProc(AudioConverterRef inAudioConverter, UInt32* ioNumber
}
}
PrintStreamDesc(&inf);
PrintStreamDesc(&outf);
return YES;
}

View File

@ -19,17 +19,21 @@
@interface InputNode : Node {
id<CogDecoder> decoder;
AudioStreamBasicDescription outputFormat;
BOOL shouldSeek;
double seekTime;
}
- (BOOL)openURL:(NSURL *)url withSource:(id<CogSource>)source outputFormat:(AudioStreamBasicDescription)of;
- (BOOL)openURL:(NSURL *)url withSource:(id<CogSource>)source;
- (BOOL)openWithDecoder:(id<CogDecoder>) d;
- (void)process;
- (NSDictionary *) properties;
- (void)seek:(double)time;
- (void)registerObservers;
- (BOOL)setTrack:(NSURL *)track;
- (id<CogDecoder>) decoder;
@end

View File

@ -13,18 +13,16 @@
@implementation InputNode
- (BOOL)openURL:(NSURL *)url withSource:(id<CogSource>)source outputFormat:(AudioStreamBasicDescription)of
- (BOOL)openURL:(NSURL *)url withSource:(id<CogSource>)source
{
outputFormat = of;
decoder = [AudioDecoder audioDecoderForURL:url];
[decoder retain];
[self registerObservers];
if (decoder == nil)
return NO;
[self registerObservers];
if (![decoder open:source])
{
NSLog(@"Couldn't open decoder...");
@ -37,6 +35,22 @@
return YES;
}
- (BOOL)openWithDecoder:(id<CogDecoder>) d
{
NSLog(@"Opening with old decoder: %@", d);
decoder = d;
[decoder retain];
[self registerObservers];
shouldContinue = YES;
shouldSeek = NO;
NSLog(@"DONES: %@", decoder);
return YES;
}
- (void)registerObservers
{
[decoder addObserver:self
@ -69,13 +83,14 @@
int amountRead = 0, amountInBuffer = 0;
void *inputBuffer = malloc(CHUNK_SIZE);
BOOL shouldClose = YES;
while ([self shouldContinue] == YES && [self endOfStream] == NO)
{
if (shouldSeek == YES)
{
NSLog(@"SEEKING!");
[decoder seekToTime:seekTime];
NSLog(@"Har");
shouldSeek = NO;
NSLog(@"Seeked! Resetting Buffer");
@ -95,17 +110,19 @@
[controller initialBufferFilled];
}
NSLog(@"End of stream?");
endOfStream = YES;
[controller endOfInputReached];
break; //eof
shouldClose = [controller endOfInputReached]; //Lets us know if we should keep going or not (occassionally, for track changes within a file)
NSLog(@"closing? is %i", shouldClose);
break;
}
[self writeData:inputBuffer amount:amountInBuffer];
amountInBuffer = 0;
}
}
[decoder close];
if (shouldClose)
[decoder close];
free(inputBuffer);
}
@ -118,8 +135,21 @@
[semaphore signal];
}
- (BOOL)setTrack:(NSURL *)track
{
if ([decoder respondsToSelector:@selector(setTrack:)] && [decoder setTrack:track]) {
NSLog(@"SET TRACK!");
return YES;
}
return NO;
}
- (void)dealloc
{
NSLog(@"DEALLOCATING");
[decoder removeObserver:self forKeyPath:@"properties"];
[decoder removeObserver:self forKeyPath:@"metadata"];
@ -133,4 +163,9 @@
return [decoder properties];
}
- (id<CogDecoder>) decoder
{
return decoder;
}
@end

View File

@ -284,14 +284,14 @@
isa = PBXContainerItemProxy;
containerPortal = 17F3BB830CBC565100864489 /* CueSheet.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 8D5B49B6048680CD000E48DA /* CueSheet.bundle */;
remoteGlobalIDString = 8D5B49B6048680CD000E48DA;
remoteInfo = CueSheet;
};
17F3BB8A0CBC566200864489 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 17F3BB830CBC565100864489 /* CueSheet.xcodeproj */;
proxyType = 1;
remoteGlobalIDString = 8D5B49AC048680CD000E48DA /* CueSheet */;
remoteGlobalIDString = 8D5B49AC048680CD000E48DA;
remoteInfo = CueSheet;
};
17F561320C3BD4DC0019975C /* PBXContainerItemProxy */ = {

View File

@ -10,6 +10,7 @@
#import "Plugin.h"
@class CueSheet;
@class CueSheetTrack;
@interface CueSheetDecoder : NSObject <CogDecoder> {
@ -20,6 +21,7 @@
int bytePosition;
double trackEnd;
CueSheet *cuesheet;
CueSheetTrack *track;
}

View File

@ -38,7 +38,8 @@
NSURL *url = [s url];
[s close];
CueSheet *cuesheet = [CueSheet cueSheetWithFile:[url path]];
cuesheet = [CueSheet cueSheetWithFile:[url path]];
[cuesheet retain];
NSArray *tracks = [cuesheet tracks];
int i;
@ -46,6 +47,7 @@
{
if ([[[tracks objectAtIndex:i] track] isEqualToString:[url fragment]]){
track = [tracks objectAtIndex:i];
[track retain];
//Kind of a hackish way of accessing outside classes.
source = [NSClassFromString(@"AudioSource") audioSourceForURL:[track url]];
@ -103,6 +105,47 @@
[source release];
source = nil;
}
if (cuesheet) {
[cuesheet release];
cuesheet = nil;
}
if (track) {
[track release];
track = nil;
}
}
- (BOOL)setTrack:(NSURL *)url
{
if ([[url fragment] intValue] == [[track track] intValue] + 1) {
NSArray *tracks = [cuesheet tracks];
int i;
for (i = 0; i < [tracks count]; i++) {
if ([[[tracks objectAtIndex:i] track] isEqualToString:[url fragment]]){
[track release];
track = [tracks objectAtIndex:i];
[track retain];
CueSheetTrack *nextTrack = nil;
if (i + 1 < [tracks count]) {
nextTrack = [tracks objectAtIndex:i + 1];
}
if (nextTrack && [[[nextTrack url] absoluteString] isEqualToString:[[track url] absoluteString]]) {
trackEnd = [nextTrack time];
}
else {
trackEnd = [[[decoder properties] objectForKey:@"length"] doubleValue]/1000.0;
}
NSLog(@"CHANGING TRACK!");
return YES;
}
}
}
return NO;
}
- (double)seekToTime:(double)time //milliseconds
@ -119,6 +162,13 @@
bytePosition = (time/1000.0) * bytesPerSecond;
int bitsPerSample = [[[decoder properties] objectForKey:@"bitsPerSample"] intValue];
int channels = [[[decoder properties] objectForKey:@"channels"] intValue];
NSLog(@"Before: %li", bytePosition);
bytePosition -= bytePosition % (bitsPerSample/8 * channels);
NSLog(@"After: %li", bytePosition);
return [decoder seekToTime:time];
}