File: programming/cocoa/UKTurboExport.zip/UKTurboExport/UKTurbo264.m


//
//  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

This code uses the PclZip Zip File reading code, which is subject to the GNU LGPL. It also uses the GeSHi syntax highlighter, subject to the GPL. Ask if you want this for your own web site, it's free.