From 63fba5930e899adb406d0d8ef7c824b18e03a1ad Mon Sep 17 00:00:00 2001 From: Chris Moeller Date: Mon, 21 Oct 2013 10:54:11 -0700 Subject: [PATCH] Implemented support for multiple decoders per file name extension, with a floating point priority control per interface. In the event that more than one input is registered to a given extension, and we match that extension, it will be passed off to an instance of the multi-decoder wrapper, which will try opening the file with all of the decoders in order of priority, until either one of them accepts it, or all of them have failed. This paves the way for adding a VGMSTREAM input, so I can give it a very low priority, since it has several formats that are verified by file name extension only. All current inputs have been given a priority of 1.0, except for CoreAudio, which was given a priority of 0.5, because it contains an MP3 and AC3 decoders that I'd rather not use if I don't have to. --- Audio/CogAudio.xcodeproj/project.pbxproj | 8 ++ Audio/CogDecoderMulti.h | 20 +++ Audio/CogDecoderMulti.m | 129 ++++++++++++++++++ Audio/Plugin.h | 1 + Audio/PluginController.m | 25 +++- Plugins/APL/APLDecoder.m | 4 + Plugins/CoreAudio/CoreAudioDecoder.m | 5 + Plugins/CueSheet/CueSheetDecoder.m | 5 + Plugins/Dumb/DumbDecoder.m | 5 + Plugins/FFMPEG/FFMPEGDecoder.m | 4 + Plugins/Flac/FlacDecoder.m | 5 + Plugins/GME/GameDecoder.m | 5 + .../HighlyComplete/HCDecoder.mm | 4 + Plugins/MIDI/MIDI/MIDIDecoder.mm | 5 + Plugins/Musepack/MusepackDecoder.m | 5 + Plugins/Opus/Opus/OpusDecoder.m | 5 + Plugins/Shorten/ShortenDecoder.mm | 5 + Plugins/Vorbis/VorbisDecoder.m | 5 + Plugins/WavPack/WavPackDecoder.m | 5 + 19 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 Audio/CogDecoderMulti.h create mode 100644 Audio/CogDecoderMulti.m diff --git a/Audio/CogAudio.xcodeproj/project.pbxproj b/Audio/CogAudio.xcodeproj/project.pbxproj index 07edef90d..656d6472d 100644 --- a/Audio/CogAudio.xcodeproj/project.pbxproj +++ b/Audio/CogAudio.xcodeproj/project.pbxproj @@ -44,6 +44,8 @@ 17F94DD60B8D0F7000A34E87 /* PluginController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17F94DD40B8D0F7000A34E87 /* PluginController.m */; }; 17F94DDD0B8D101100A34E87 /* Plugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 17F94DDC0B8D101100A34E87 /* Plugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8384912718080FF100E7332D /* Logging.h in Headers */ = {isa = PBXBuildFile; fileRef = 8384912618080FF100E7332D /* Logging.h */; }; + 839366671815923C006DD712 /* CogDecoderMulti.h in Headers */ = {isa = PBXBuildFile; fileRef = 839366651815923C006DD712 /* CogDecoderMulti.h */; }; + 839366681815923C006DD712 /* CogDecoderMulti.m in Sources */ = {isa = PBXBuildFile; fileRef = 839366661815923C006DD712 /* CogDecoderMulti.m */; }; 8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; 8E8D3D2F0CBAEE6E00135C1B /* AudioContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E8D3D2D0CBAEE6E00135C1B /* AudioContainer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8E8D3D300CBAEE6E00135C1B /* AudioContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E8D3D2E0CBAEE6E00135C1B /* AudioContainer.m */; }; @@ -107,6 +109,8 @@ 17F94DDC0B8D101100A34E87 /* Plugin.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Plugin.h; sourceTree = ""; }; 32DBCF5E0370ADEE00C91783 /* CogAudio_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CogAudio_Prefix.pch; sourceTree = ""; }; 8384912618080FF100E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = ""; }; + 839366651815923C006DD712 /* CogDecoderMulti.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CogDecoderMulti.h; sourceTree = ""; }; + 839366661815923C006DD712 /* CogDecoderMulti.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CogDecoderMulti.m; sourceTree = ""; }; 8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 8DC2EF5B0486A6940098B216 /* CogAudio.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CogAudio.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8E8D3D2D0CBAEE6E00135C1B /* AudioContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioContainer.h; sourceTree = ""; }; @@ -189,6 +193,8 @@ 17B6192F0B909BC300BC003F /* AudioPropertiesReader.m */, 17ADB13A0B97926D00257CA2 /* AudioSource.h */, 17ADB13B0B97926D00257CA2 /* AudioSource.m */, + 839366651815923C006DD712 /* CogDecoderMulti.h */, + 839366661815923C006DD712 /* CogDecoderMulti.m */, 17F94DD30B8D0F7000A34E87 /* PluginController.h */, 17F94DD40B8D0F7000A34E87 /* PluginController.m */, 17D21C750B8BE4BA00D1EBDE /* Chain */, @@ -317,6 +323,7 @@ 17A2D3C50B8D1D37000778C4 /* AudioDecoder.h in Headers */, 17C940230B900909008627D6 /* AudioMetadataReader.h in Headers */, 17B619300B909BC300BC003F /* AudioPropertiesReader.h in Headers */, + 839366671815923C006DD712 /* CogDecoderMulti.h in Headers */, 17ADB13C0B97926D00257CA2 /* AudioSource.h in Headers */, 8EC1225F0B993BD500C5B3AD /* ConverterNode.h in Headers */, 8384912718080FF100E7332D /* Logging.h in Headers */, @@ -397,6 +404,7 @@ 17D21CE00B8BE5B400D1EBDE /* VirtualRingBuffer.m in Sources */, 17D21CF40B8BE5EF00D1EBDE /* Semaphore.m in Sources */, 17D21DC80B8BE79700D1EBDE /* CoreAudioUtils.m in Sources */, + 839366681815923C006DD712 /* CogDecoderMulti.m in Sources */, 17D21EBE0B8BF44000D1EBDE /* AudioPlayer.m in Sources */, 17F94DD60B8D0F7000A34E87 /* PluginController.m in Sources */, 17A2D3C60B8D1D37000778C4 /* AudioDecoder.m in Sources */, diff --git a/Audio/CogDecoderMulti.h b/Audio/CogDecoderMulti.h new file mode 100644 index 000000000..650463ad6 --- /dev/null +++ b/Audio/CogDecoderMulti.h @@ -0,0 +1,20 @@ +// +// CogDecoderMulti.h +// CogAudio +// +// Created by Christopher Snowhill on 10/21/13. +// +// + +#import +#import "Plugin.h" + +@interface CogDecoderMulti : NSObject { + NSArray *theDecoders; + id theDecoder; + NSMutableArray *cachedObservers; +} + +-(id)initWithDecoders:(NSArray *)decoders; + +@end diff --git a/Audio/CogDecoderMulti.m b/Audio/CogDecoderMulti.m new file mode 100644 index 000000000..4ccfeb1b8 --- /dev/null +++ b/Audio/CogDecoderMulti.m @@ -0,0 +1,129 @@ +// +// CogDecoderMulti.m +// CogAudio +// +// Created by Christopher Snowhill on 10/21/13. +// +// + +#import "CogDecoderMulti.h" + +@implementation CogDecoderMulti + ++ (NSArray *)mimeTypes +{ + return nil; +} + ++ (NSArray *)fileTypes +{ + return nil; +} + ++ (float)priority +{ + return -1.0; +} + +- (id)initWithDecoders:(NSArray *)decoders +{ + self = [super init]; + if ( self ) + { + NSMutableArray *sortedDecoders = [NSMutableArray arrayWithArray:decoders]; + [sortedDecoders sortUsingComparator: + ^NSComparisonResult(id obj1, id obj2) + { + NSString *classString1 = (NSString *)obj1; + NSString *classString2 = (NSString *)obj2; + + Class decoder1 = NSClassFromString(classString1); + Class decoder2 = NSClassFromString(classString2); + + float priority1 = [decoder1 priority]; + float priority2 = [decoder2 priority]; + + if (priority1 == priority2) return NSOrderedSame; + else if (priority1 > priority2) return NSOrderedAscending; + else return NSOrderedDescending; + }]; + theDecoders = sortedDecoders; + theDecoder = nil; + cachedObservers = [[[NSMutableArray alloc] init] autorelease]; + } + return self; +} + +- (NSDictionary *)properties +{ + if ( theDecoder != nil ) return [theDecoder properties]; + return nil; +} + +- (int)readAudio:(void *)buffer frames:(UInt32)frames +{ + if ( theDecoder != nil ) return [theDecoder readAudio:buffer frames:frames]; + return 0; +} + +- (BOOL)open:(id)source +{ + for (NSString *classString in theDecoders) + { + Class decoder = NSClassFromString(classString); + theDecoder = [[decoder alloc] init]; + for (NSDictionary *obsItem in cachedObservers) { + [theDecoder addObserver:[obsItem objectForKey:@"observer"] forKeyPath:[obsItem objectForKey:@"keyPath"] options:[obsItem objectForKey:@"options"] context:[obsItem objectForKey:@"context"]]; + } + if ([theDecoder open:source]) + return YES; + for (NSDictionary *obsItem in cachedObservers) { + [theDecoder removeObserver:[obsItem objectForKey:@"observer"] forKeyPath:[obsItem objectForKey:@"keyPath"]]; + } + [theDecoder release]; + [source seek:0 whence:SEEK_SET]; + } + theDecoder = nil; + return NO; +} + +- (long)seek:(long)frame +{ + if ( theDecoder != nil ) return [theDecoder seek:frame]; + return -1; +} + +- (void)close +{ + if ( theDecoder != nil ) { + [theDecoder close]; + [theDecoder release]; + theDecoder = nil; + } +} + +- (BOOL)setTrack:(NSURL *)track +{ + if ( theDecoder != nil && [theDecoder respondsToSelector: @selector(setTrack:)] ) return [theDecoder setTrack:track]; + return NO; +} + +/* By the current design, the core adds its observers to decoders before they are opened */ +- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context +{ + [cachedObservers addObject:[NSDictionary dictionaryWithObjectsAndKeys:observer, @"observer", keyPath, @"keyPath", options, @"options", context, @"context", nil]]; +} + +- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath +{ + if ( theDecoder != nil ) + [theDecoder removeObserver:observer forKeyPath:keyPath]; + for (NSDictionary *obsItem in cachedObservers) + { + if ([obsItem objectForKey:@"observer"] == observer && [keyPath isEqualToString:[obsItem objectForKey:@"keyPath"]]) { + [cachedObservers removeObject:obsItem]; + } + } +} + +@end diff --git a/Audio/Plugin.h b/Audio/Plugin.h index a8add97a1..d1c5b3ddd 100644 --- a/Audio/Plugin.h +++ b/Audio/Plugin.h @@ -25,6 +25,7 @@ @required + (NSArray *)mimeTypes; + (NSArray *)fileTypes; //mp3, ogg, etc ++ (float)priority; // should be 0.0 ... 1.0, higher means you get selected first, should default to 1.0 unless you know a reason why any of your extensions may behave badly, ie. greedily taking over some file type extension without performing any header validation on it //For KVO //- (void)setProperties:(NSDictionary *)p; diff --git a/Audio/PluginController.m b/Audio/PluginController.m index 5f0ec6cbd..86e7c624d 100644 --- a/Audio/PluginController.m +++ b/Audio/PluginController.m @@ -1,5 +1,6 @@ #import "PluginController.h" #import "Plugin.h" +#import "CogDecoderMulti.h" #import "Logging.h" @@ -129,7 +130,16 @@ static PluginController *sharedPluginController = nil; if (decoder && [decoder respondsToSelector:@selector(fileTypes)]) { for (id fileType in [decoder fileTypes]) { - [decodersByExtension setObject:className forKey:[fileType lowercaseString]]; + NSString *ext = [fileType lowercaseString]; + NSMutableArray *decoders; + if (![decodersByExtension objectForKey:ext]) + { + decoders = [[[NSMutableArray alloc] init] autorelease]; + [decodersByExtension setObject:decoders forKey:ext]; + } + else + decoders = [decodersByExtension objectForKey:ext]; + [decoders addObject:className]; } } @@ -216,8 +226,17 @@ static PluginController *sharedPluginController = nil; - (id) audioDecoderForSource:(id )source { NSString *ext = [[[source url] path] pathExtension]; - NSString *classString = [decodersByExtension objectForKey:[ext lowercaseString]]; - if (!classString) { + NSArray *decoders = [decodersByExtension objectForKey:[ext lowercaseString]]; + NSString *classString; + if (decoders) { + if ( [decoders count] > 1 ) { + return [[[CogDecoderMulti alloc] initWithDecoders:decoders] autorelease]; + } + else { + classString = [decoders objectAtIndex:0]; + } + } + else { classString = [decodersByMimeType objectForKey:[[source mimeType] lowercaseString]]; } diff --git a/Plugins/APL/APLDecoder.m b/Plugins/APL/APLDecoder.m index ba4a3adc0..e00324831 100644 --- a/Plugins/APL/APLDecoder.m +++ b/Plugins/APL/APLDecoder.m @@ -13,6 +13,10 @@ return [NSArray arrayWithObjects:@"application/x-apl", nil]; } ++ (float)priority { + return 1.0; +} + - (NSDictionary *)properties { NSMutableDictionary *properties = [[decoder properties] mutableCopy]; diff --git a/Plugins/CoreAudio/CoreAudioDecoder.m b/Plugins/CoreAudio/CoreAudioDecoder.m index 3653e7de7..34a9c487c 100644 --- a/Plugins/CoreAudio/CoreAudioDecoder.m +++ b/Plugins/CoreAudio/CoreAudioDecoder.m @@ -181,6 +181,11 @@ return nil; } ++ (float)priority +{ + return 0.5; +} + - (NSDictionary *)properties { return [NSDictionary dictionaryWithObjectsAndKeys: diff --git a/Plugins/CueSheet/CueSheetDecoder.m b/Plugins/CueSheet/CueSheetDecoder.m index 0df2ff463..0791dbc46 100644 --- a/Plugins/CueSheet/CueSheetDecoder.m +++ b/Plugins/CueSheet/CueSheetDecoder.m @@ -26,6 +26,11 @@ return [CueSheetContainer mimeTypes]; } ++ (float)priority +{ + return 1.0; +} + - (NSDictionary *)properties { NSMutableDictionary *properties = [[decoder properties] mutableCopy]; diff --git a/Plugins/Dumb/DumbDecoder.m b/Plugins/Dumb/DumbDecoder.m index 24b01142f..af52f3c6a 100755 --- a/Plugins/Dumb/DumbDecoder.m +++ b/Plugins/Dumb/DumbDecoder.m @@ -327,4 +327,9 @@ int callbackLoop(void *data) return [NSArray arrayWithObjects:@"audio/x-it", @"audio/x-xm", @"audio/x-s3m", @"audio/x-mod", nil]; } ++ (float)priority +{ + return 1.0; +} + @end diff --git a/Plugins/FFMPEG/FFMPEGDecoder.m b/Plugins/FFMPEG/FFMPEGDecoder.m index f329eaedd..d5422e7dd 100644 --- a/Plugins/FFMPEG/FFMPEGDecoder.m +++ b/Plugins/FFMPEG/FFMPEGDecoder.m @@ -327,6 +327,10 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op) return [NSArray arrayWithObjects:@"application/wma", @"application/x-wma", @"audio/x-wma", @"audio/x-ms-wma", @"audio/x-tak", @"audio/mpeg", @"audio/x-mp3", @"audio/x-mp2", @"audio/x-ape", @"audio/x-ac3", @"audio/x-dts", @"audio/x-dtshd", @"audio/x-at3", @"audio/wav", @"tta", nil]; } ++ (float)priority +{ + return 1.0; +} diff --git a/Plugins/Flac/FlacDecoder.m b/Plugins/Flac/FlacDecoder.m index 9430fac3f..ffce7ba4f 100644 --- a/Plugins/Flac/FlacDecoder.m +++ b/Plugins/Flac/FlacDecoder.m @@ -329,4 +329,9 @@ void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorS return [NSArray arrayWithObjects:@"audio/x-flac", nil]; } ++ (float)priority +{ + return 1.0; +} + @end diff --git a/Plugins/GME/GameDecoder.m b/Plugins/GME/GameDecoder.m index 7e343b09b..8a374e866 100755 --- a/Plugins/GME/GameDecoder.m +++ b/Plugins/GME/GameDecoder.m @@ -175,6 +175,11 @@ gme_err_t readCallback( void* data, void* out, long count ) return nil; } ++ (float)priority +{ + return 1.0; +} + - (void)setSource:(id)s { [s retain]; diff --git a/Plugins/HighlyComplete/HighlyComplete/HCDecoder.mm b/Plugins/HighlyComplete/HighlyComplete/HCDecoder.mm index 186da860a..ef4ff17d9 100644 --- a/Plugins/HighlyComplete/HighlyComplete/HCDecoder.mm +++ b/Plugins/HighlyComplete/HighlyComplete/HCDecoder.mm @@ -1531,6 +1531,10 @@ static int twosf_info(void * context, const char * name, const char * value) return [NSArray arrayWithObjects:@"audio/x-psf", nil]; } ++ (float)priority +{ + return 1.0; +} @end diff --git a/Plugins/MIDI/MIDI/MIDIDecoder.mm b/Plugins/MIDI/MIDI/MIDIDecoder.mm index c38341184..19c083c8f 100755 --- a/Plugins/MIDI/MIDI/MIDIDecoder.mm +++ b/Plugins/MIDI/MIDI/MIDIDecoder.mm @@ -175,4 +175,9 @@ return [NSArray arrayWithObjects:@"audio/midi", @"audio/x-midi", nil]; } ++ (float)priority +{ + return 1.0; +} + @end diff --git a/Plugins/Musepack/MusepackDecoder.m b/Plugins/Musepack/MusepackDecoder.m index 622f862ba..4a775642e 100644 --- a/Plugins/Musepack/MusepackDecoder.m +++ b/Plugins/Musepack/MusepackDecoder.m @@ -228,4 +228,9 @@ mpc_bool_t CanSeekProc(void *data) return [NSArray arrayWithObjects:@"audio/x-musepack", nil]; } ++ (float)priority +{ + return 1.0; +} + @end diff --git a/Plugins/Opus/Opus/OpusDecoder.m b/Plugins/Opus/Opus/OpusDecoder.m index b0511f476..e0f6d91ca 100644 --- a/Plugins/Opus/Opus/OpusDecoder.m +++ b/Plugins/Opus/Opus/OpusDecoder.m @@ -160,4 +160,9 @@ opus_int64 sourceTell(void *_stream) return [NSArray arrayWithObjects:@"audio/x-opus+ogg", nil]; } ++ (float)priority +{ + return 1.0; +} + @end diff --git a/Plugins/Shorten/ShortenDecoder.mm b/Plugins/Shorten/ShortenDecoder.mm index 213bf4679..e236f0af9 100644 --- a/Plugins/Shorten/ShortenDecoder.mm +++ b/Plugins/Shorten/ShortenDecoder.mm @@ -103,5 +103,10 @@ return [NSArray arrayWithObjects:@"application/x-shorten", nil]; //This is basically useless, since we cant stream shorten yet } ++ (float)priority +{ + return 1.0; +} + @end diff --git a/Plugins/Vorbis/VorbisDecoder.m b/Plugins/Vorbis/VorbisDecoder.m index fe731889f..3ae176879 100644 --- a/Plugins/Vorbis/VorbisDecoder.m +++ b/Plugins/Vorbis/VorbisDecoder.m @@ -154,4 +154,9 @@ long sourceTell(void *datasource) return [NSArray arrayWithObjects:@"application/ogg", @"application/x-ogg", @"audio/x-vorbis+ogg", nil]; } ++ (float)priority +{ + return 1.0; +} + @end diff --git a/Plugins/WavPack/WavPackDecoder.m b/Plugins/WavPack/WavPackDecoder.m index 4a2a77cad..ec3371594 100644 --- a/Plugins/WavPack/WavPackDecoder.m +++ b/Plugins/WavPack/WavPackDecoder.m @@ -264,5 +264,10 @@ int32_t WriteBytesProc(void *ds, void *data, int32_t bcount) return [NSArray arrayWithObjects:@"audio/x-wavpack", nil]; } ++ (float)priority +{ + return 1.0; +} + @end