// // UKTurbo264.m // UKTurboExport // // Created by Uli Kusterer on 09.02.08. // Copyright 2008 M. Uli Kusterer. All rights reserved. // // ----------------------------------------------------------------------------- // Headers: // ----------------------------------------------------------------------------- #import "UKTurbo264.h" #import <QTKit/QTKit.h> #import <IOKit/IOKitLib.h> #include <IOKit/IOCFPlugIn.h> #include <IOKit/usb/IOUSBLib.h> // ----------------------------------------------------------------------------- // Constants: // ----------------------------------------------------------------------------- // Keys for our presets dictionary: NSString* UKTurbo264PresetIdentifierKey = @"UKTurbo264PresetIdentifier"; NSString* UKTurbo264PresetDisplayNameKey = @"UKTurbo264PresetDisplayName"; NSString* UKTurbo264PresetInternalIDKey = @"UKTurbo264PresetInternalID"; // Keys for our presets dictionary: NSString* UKTurbo264DeviceCountChangedNotification = @"UKTurbo264DeviceCountChangedNotification"; // Keys for notifications' userInfo dictionaries: NSString* UKTurbo264DeviceHasFirmwareKey = @"UKTurbo264DeviceHasFirmware"; NSString* UKTurbo264DeviceVendorIDKey = @"UKTurbo264DeviceVendorID"; NSString* UKTurbo264DeviceProductIDKey = @"UKTurbo264DeviceProductID"; // The type of the Turbo.264 settings atom: #define kTurboAtomPreset 'TPst' // The different presets: #define kTurboPresetIPodBest 'IpdB' #define kTurboPresetIPodSmallest 'IpdS' #define kTurboPresetPSP 'PspH' #define kTurboPresetAppleTV 'ApTV' #define kTurboPresetIPhone 'iPhn' // Turbo device IDs: #define kNumDifferentTurboDevices 3 #define kTurboVendorID 0x0FD9 #define kTurboProductIDV1_NoFW 0x0004 #define kTurboProductIDV1 0x0005 #define kTurboProductIDV2 0x0006 // ----------------------------------------------------------------------------- // Private methods: // ----------------------------------------------------------------------------- @interface UKTurbo264 (PrivateMethods) -(OSStatus) registerForDeviceNotifications; -(void) deviceAdded: (io_service_t)usbDevice; -(void) deviceRemoved: (io_service_t)usbDevice; -(OSStatus) addNotificationsForVendor: (UInt32)usbVendor product: (UInt32)usbProduct atIndex: (int)idx; -(Component) turboComponent; @end void DeviceAddedCallback(void *refCon, io_iterator_t iter); void DeviceRemovedCallback(void *refCon, io_iterator_t iter); // ----------------------------------------------------------------------------- // Private structs: // ----------------------------------------------------------------------------- struct UKTurbo264IVars { IONotificationPortRef notifyPort; io_iterator_t deviceAddedIterator[kNumDifferentTurboDevices]; io_iterator_t deviceRemovedIterator[kNumDifferentTurboDevices]; unsigned int numDevices; }; // ----------------------------------------------------------------------------- // Globals: // ----------------------------------------------------------------------------- static UKTurbo264* sSharedTurbo264 = nil; @implementation UKTurbo264 // ----------------------------------------------------------------------------- // Return the shared instance of the Turbo-264 singleton: // ----------------------------------------------------------------------------- +(id) sharedTurbo264 { if( !sSharedTurbo264 ) sSharedTurbo264 = [[UKTurbo264 alloc] init]; return sSharedTurbo264; } // ----------------------------------------------------------------------------- // init: // Allocate our ivars struct. This struct is used so we can safely export // this class to plug-ins or whatever without having to worry that things // will break if they subclass this class and we update it. // Unlikely, but better safe than sorry. // ----------------------------------------------------------------------------- -(id) init { self = [super init]; if( self ) { _ivars = calloc( 1, sizeof(struct UKTurbo264IVars) ); [self registerForDeviceNotifications]; } return self; } // ----------------------------------------------------------------------------- // dealloc: // Get rid of our ivars struct. // ----------------------------------------------------------------------------- -(void) dealloc { if( _ivars ) { // Since this is a singleton, we'll just leak the IOKit iterators etc. // for now. free( _ivars ); _ivars = NULL; } [super dealloc]; } // ----------------------------------------------------------------------------- // isAvailable: // Returns YES if any Turbo devices are attached and the Turbo Component // is installed. // ----------------------------------------------------------------------------- -(BOOL) isAvailable { if( [self turboComponent] == NULL ) // Turbo Component not installed. return false; return _ivars->numDevices > 0; } // ----------------------------------------------------------------------------- // exportPresets: // Returns a list of all available export presets. You can use this to // populate a menu or whatever. // ----------------------------------------------------------------------------- -(NSArray*) exportPresets { static NSArray* sExportPresets = nil; if( !sExportPresets ) { if( [self turboComponent] == NULL ) // No component installed. sExportPresets = [[NSArray alloc] init]; // Can't export to anything. else { sExportPresets = [[NSArray alloc] initWithObjects: [NSDictionary dictionaryWithObjectsAndKeys: @"com.elgato.turbo.preset.ipod-best", UKTurbo264PresetIdentifierKey, NSLocalizedString(@"iPod High",@"iPod export format"), UKTurbo264PresetDisplayNameKey, [NSNumber numberWithLong: EndianU32_NtoB(kTurboPresetIPodBest)], UKTurbo264PresetInternalIDKey, nil ], [NSDictionary dictionaryWithObjectsAndKeys: @"com.elgato.turbo.preset.ipod-smallest", UKTurbo264PresetIdentifierKey, NSLocalizedString(@"iPod Standard",@"iPod export format"), UKTurbo264PresetDisplayNameKey, [NSNumber numberWithLong: EndianU32_NtoB(kTurboPresetIPodSmallest)], UKTurbo264PresetInternalIDKey, nil ], [NSDictionary dictionaryWithObjectsAndKeys: @"com.elgato.turbo.preset.psp", UKTurbo264PresetIdentifierKey, NSLocalizedString(@"Sony PSP",@"PSP export format"), UKTurbo264PresetDisplayNameKey, [NSNumber numberWithLong: EndianU32_NtoB(kTurboPresetPSP)], UKTurbo264PresetInternalIDKey, nil ], [NSDictionary dictionaryWithObjectsAndKeys: @"com.elgato.turbo.preset.apple-tv", UKTurbo264PresetIdentifierKey, NSLocalizedString(@"Apple TV",@"Apple TV export format"), UKTurbo264PresetDisplayNameKey, [NSNumber numberWithLong: EndianU32_NtoB(kTurboPresetAppleTV)], UKTurbo264PresetInternalIDKey, nil ], [NSDictionary dictionaryWithObjectsAndKeys: @"com.elgato.turbo.preset.iphone", UKTurbo264PresetIdentifierKey, NSLocalizedString(@"iPhone",@"iPhone export format"), UKTurbo264PresetDisplayNameKey, [NSNumber numberWithLong: EndianU32_NtoB(kTurboPresetIPhone)], UKTurbo264PresetInternalIDKey, nil ], nil]; } } return sExportPresets; } // ----------------------------------------------------------------------------- // exportPresetForID: // Returns the preset associated with the specified identifier, or NIL if // there is none available. You can use this to find the last preset your // app used with Turbo by saving the UKTurbo264PresetIdentifierKey from // the preset dictionary to prefs and then using this method to recover // the full and current dictionary corresponding to it. // ----------------------------------------------------------------------------- -(NSDictionary*) exportPresetForID: (NSString*)presetIdentifier { NSArray* presets = [self exportPresets]; int x = 0, count = [presets count]; for( x = 0; x < count; x++ ) { NSDictionary* currPreset = [presets objectAtIndex: x]; if( [[currPreset objectForKey: UKTurbo264PresetIdentifierKey] isEqualToString: presetIdentifier] ) return currPreset; } return nil; } // ----------------------------------------------------------------------------- // exportSettingsFromPreset: // Returns an NSData block suitable for use as the QTMovieExportSettings // entry in the attributes dictionary of QTMovie's writeToFile: method. // Use this to tell QTKit what preset to actually use when exporting to // Turbo. // ----------------------------------------------------------------------------- -(NSData*) exportSettingsFromPreset: (NSDictionary*)preset { // Find the Turbo component 'class': Component turboClass = [self turboComponent]; if( !turboClass ) { NSLog(@"Could not find the Turbo component."); return nil; } // Show settings dialog to user: MovieExportComponent exporter = OpenComponent(turboClass); QTAtomContainer exporterSettings = NULL; ComponentResult err = noErr; err = MovieExportGetSettingsAsAtomContainer( exporter, &exporterSettings ); if( err ) { NSLog(@"MovieExportGetSettingsAsAtomContainer returned %d.",err); CloseComponent( exporter ); return nil; } QTAtom atom = QTFindChildByID( exporterSettings, kParentAtomIsContainer, kTurboAtomPreset, 1, NULL ); if( atom != 0 ) { UInt32 presetType = [[preset objectForKey: UKTurbo264PresetInternalIDKey] longValue]; err = QTSetAtomData( exporterSettings, atom, sizeof(presetType), &presetType ); if( err ) { NSLog(@"Error %d trying to apply export settings.",err); DisposeHandle( exporterSettings ); CloseComponent( exporter ); return nil; } } err = MovieExportSetSettingsFromAtomContainer( exporter, exporterSettings ); if( err ) { NSLog(@"Error %d calling MovieExportSetSettingsFromAtomContainer.",err); DisposeHandle( exporterSettings ); CloseComponent( exporter ); return nil; } NSData *data = [NSData dataWithBytes: *exporterSettings length: GetHandleSize(exporterSettings)]; DisposeHandle(exporterSettings); CloseComponent(exporter); return data; } // ----------------------------------------------------------------------------- // writeToFileAttributesForPreset: // Returns an attributes dictionary ready to be passed to QTMovie's // -writeToFile: method that sets up everything for a Turbo export using // the specified preset. // ----------------------------------------------------------------------------- -(NSDictionary*) writeToFileAttributesForPreset: (NSDictionary*)preset { return [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], QTMovieExport, [NSNumber numberWithLong: kTurboExporterType], QTMovieExportType, [NSNumber numberWithLong: kElgatoManufacturer], QTMovieExportManufacturer, [self exportSettingsFromPreset: preset], QTMovieExportSettings, nil]; } @end @implementation UKTurbo264 (PrivateMethods) -(OSStatus) registerForDeviceNotifications { if( [self turboComponent] == NULL ) // Turbo Component not installed. return unimpErr; // No need to listen for devices, couldn't use them anyway. OSStatus err; mach_port_t masterPort = MACH_PORT_NULL; err = IOMasterPort(bootstrap_port, &masterPort); if (err == noErr) { // Create a notification port attached to the current run loop so // IOKit notifications can be sent through it: _ivars->notifyPort = IONotificationPortCreate(masterPort); CFRunLoopSourceRef runLoopSource = IONotificationPortGetRunLoopSource(_ivars->notifyPort); CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode); // Match our device: err = [self addNotificationsForVendor: kTurboVendorID product: kTurboProductIDV1 atIndex: 0]; if( err == noErr ) err = [self addNotificationsForVendor: kTurboVendorID product: kTurboProductIDV1_NoFW atIndex: 1]; if( err == noErr ) err = [self addNotificationsForVendor: kTurboVendorID product: kTurboProductIDV2 atIndex: 2]; } return err; } -(OSStatus) addNotificationsForVendor: (UInt32)usbVendor product: (UInt32)usbProduct atIndex:(int)idx { OSStatus err = noErr; CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOUSBDeviceClassName); CFDictionarySetValue( matchingDict, CFSTR(kUSBVendorID), CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usbVendor)); CFDictionarySetValue( matchingDict, CFSTR(kUSBProductID), CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usbProduct)); err = IOServiceAddMatchingNotification(_ivars->notifyPort, kIOMatchedNotification, matchingDict, &DeviceAddedCallback, self, &_ivars->deviceAddedIterator[idx]); if (err == noErr) DeviceAddedCallback(self,_ivars->deviceAddedIterator[idx]); matchingDict = IOServiceMatching(kIOUSBDeviceClassName); CFDictionarySetValue( matchingDict, CFSTR(kUSBVendorID), CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usbVendor)); CFDictionarySetValue( matchingDict, CFSTR(kUSBProductID), CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usbProduct)); err = IOServiceAddMatchingNotification(_ivars->notifyPort, kIOTerminatedNotification, matchingDict, &DeviceRemovedCallback, self, &_ivars->deviceRemovedIterator[idx]); if (err == noErr) DeviceRemovedCallback(self,_ivars->deviceRemovedIterator[idx]); return err; } -(void) deviceAdded: (io_service_t)usbDevice { _ivars->numDevices ++; NSLog(@"%d Turbo devices attached.", _ivars->numDevices); [[NSNotificationCenter defaultCenter] postNotificationName: UKTurbo264DeviceCountChangedNotification object: self]; } -(void) deviceRemoved: (io_service_t)usbDevice { _ivars->numDevices --; NSLog(@"%d Turbo devices attached.", _ivars->numDevices); [[NSNotificationCenter defaultCenter] postNotificationName: UKTurbo264DeviceCountChangedNotification object: self]; } void DeviceAddedCallback(void *refCon, io_iterator_t iter) { io_service_t usbDevice; while ( (usbDevice = IOIteratorNext(iter)) ) { [(UKTurbo264*)refCon deviceAdded: usbDevice]; IOObjectRelease( usbDevice ); } } void DeviceRemovedCallback(void *refCon, io_iterator_t iter) { io_service_t usbDevice; while ( (usbDevice = IOIteratorNext(iter)) ) { [(UKTurbo264*)refCon deviceRemoved: usbDevice]; IOObjectRelease( usbDevice ); } } -(Component) turboComponent { ComponentDescription turboDescription = { 0 }; Component turboClass = NULL; turboDescription.componentType = MovieExportType; turboDescription.componentSubType = kTurboExporterType; turboDescription.componentManufacturer = kElgatoManufacturer; turboDescription.componentFlags = canMovieExportFiles; turboDescription.componentFlagsMask = canMovieExportFiles; turboClass = FindNextComponent( NULL, &turboDescription ); return turboClass; } @end |