Movie cocoaqt

From The Foundry MODO SDK wiki
Revision as of 21:13, 10 September 2013 by Adissid (Talk | contribs)

Jump to: navigation, search

Movie_CocoaQT is a basic example plugin. This wiki page is intended as a walkthrough of the code in order to help you better understand the SDK.

When installed, the Movie_CocoaQT plugin adds Cocoa QuickTime saver utility for movies.

Code Walkthrough

Class Declaration

We want to have our class create a movie object, so we have our class inherit from CLxImpl_Movie.

class CCocoaQuickTimeMovie : public CLxImpl_Movie
        LxResult		 mov_BeginMovie (const char*,int, int, int);
        LxResult		 mov_SetFramerate (int);
        LxResult		 mov_AddImage (ILxUnknownID);
        LxResult		 mov_EndMovie (void);
        LxResult		 mov_AddAudio (ILxUnknownID);
        static LXtTagInfoDesc	 descInfo[];
        int			 frameRate;
        NSString		*nsStringPath;
        QTMovie			*mMovie;
        NSDictionary		*myDict;
        NSString		*audioFileName;

Server Tags

The tags here indicate that the main class will be a server of the movie type that will have the extension .mov named Express Quicktime

LXtTagInfoDesc	 CCocoaQuickTimeMovie::descInfo[] = {
       { LXsLOD_CLASSLIST,	LXa_MOVIE         },
       { LXsLOD_DOSPATTERN,	"*.mov"           },
       { LXsSAV_DOSTYPE,	"mov"             },
       { LXsSRV_USERNAME,	"Express Quicktime" },
       { 0 }


We export a server of the movie type dependent on the CCocoaQuickTimeMovie class with the "quicktime" name.

initialize ()
       LXx_ADD_SERVER (Movie, CCocoaQuickTimeMovie, "quicktime");

Helper Functions

This function takes a cg image and transforms it into a ns image, the apple format.

static NSImage *
cgImageToNSImage (
        CGImageRef	 image)
       NSRect		 imageRect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
       CGContextRef	 imageContext = nil;
       NSImage		*newImage = nil;
       // Get the image dimensions.
       imageRect.size.height = CGImageGetHeight(image);
       imageRect.size.width = CGImageGetWidth(image);
       // Create a new image to receive the Quartz image data.
       newImage = [[NSImage alloc] initWithSize:imageRect.size];
       [newImage lockFocus];
       // Get the Quartz context and draw.
       imageContext = (CGContextRef)[[NSGraphicsContext currentContext]
       CGContextDrawImage(imageContext, *(CGRect*)&imageRect, image);
       [newImage unlockFocus];
       return newImage;

This function creates a temporary audio file with the audio object.

static LxResult
CreateAudioTempFile (
       NSString		*filePath,
       CLxUser_Audio		*audio)
       LXtAudioMetrics		 m;
       LxResult		 res;
       OSStatus		 err;
       NSURL			*toURL;
       AudioBufferList		 audioBufferList;
       UInt32			 bufferSize;
       char			*buffer;
       ExtAudioFileRef		 outfile;
       int			 eos;
       unsigned int		 readFrames;
       toURL = [NSURL fileURLWithPath: filePath];
       audio->Metrics (&m);
       AudioStreamBasicDescription	 outFormat = {0};
       AudioFileTypeID			 audioFileType;
       outFormat.mBitsPerChannel   = m.type;
       outFormat.mSampleRate       = (Float64) m.frequency;
       outFormat.mFormatID         = kAudioFormatLinearPCM;
       outFormat.mChannelsPerFrame = m.channels;
       outFormat.mFramesPerPacket  = 1;
       outFormat.mBytesPerFrame    = outFormat.mBitsPerChannel / 8 * outFormat.mChannelsPerFrame;
       outFormat.mBytesPerPacket   = outFormat.mBytesPerFrame * outFormat.mFramesPerPacket;
       audioFileType = kAudioFileWAVEType;
       outFormat.mFormatFlags      = kAudioFormatFlagIsPacked;
       if (m.type == 16)
               outFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
       else if (m.type == 32)
               outFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
       // Create a temp audio file.
       err = ExtAudioFileCreateWithURL ((CFURLRef) toURL, 
       if (err != noErr) {
               NSLog (@"[ExtAudioFileCreateWithURL] err %ld\n", err);
               NSLog (@"[toURL] %@\n", toURL);
               NSLog (@"mSampleRate %f\n", outFormat.mSampleRate);
               NSLog (@"mFormatID %s\n", &outFormat.mFormatID);
               NSLog (@"mFormatFlags %x\n", outFormat.mFormatFlags);
               NSLog (@"mBytesPerPacket %u\n", outFormat.mBytesPerPacket);
               NSLog (@"mFramesPerPacket %u\n", outFormat.mFramesPerPacket);
               NSLog (@"mBytesPerFrame %u\n", outFormat.mBytesPerFrame);
               NSLog (@"mChannelsPerFrame %u\n", outFormat.mChannelsPerFrame);
               NSLog (@"mBitsPerChannel %u\n", outFormat.mBitsPerChannel);
               return LXe_FAILED;
       readFrames = 1024;
       bufferSize = sizeof (char) * readFrames * outFormat.mBytesPerPacket;
       buffer = (char *) malloc (bufferSize);
       audioBufferList.mNumberBuffers = 1;
       audioBufferList.mBuffers[0].mNumberChannels = outFormat.mChannelsPerFrame;
       audioBufferList.mBuffers[0].mDataByteSize = bufferSize;
       audioBufferList.mBuffers[0].mData = buffer;
       // Read audio data from audio object and write it into the temp file.
       audio->Seek (0);
       while (1) {
               readFrames = 1024;
               res = audio->Read (&readFrames, buffer, &eos);
               if (LXx_FAIL (res))
                       return LXe_FAILED;
               if (readFrames == 0)
               err = ExtAudioFileWrite (outfile, (UInt32) readFrames, &audioBufferList);
               if (err != noErr)
                       return LXe_FAILED;
       free (buffer);
       ExtAudioFileDispose (outfile);
       return LXe_OK;


Creates a movie file and starts writing to it.

CCocoaQuickTimeMovie::mov_BeginMovie (
       const char              *fname,
       int                      w,
       int                      h,
       int                      flags)
        * Set up a dictionary with the codec attributes to be used when
        * creating our movie. For now, we always use "mp4v" for MPEG4.
       myDict = [NSDictionary dictionaryWithObjectsAndKeys:@"mp4v",
                 [NSNumber numberWithLong:codecHighQuality],

       nsStringPath = [NSString stringWithUTF8String: fname];
       [nsStringPath retain];
       if (!nsStringPath)
               return LXe_FAILED;

        * If we made it this far, we already have permission to overwrite
        * an existing file.
	if ([[NSFileManager defaultManager] fileExistsAtPath: nsStringPath]) {
               if ([[NSFileManager defaultManager] removeFileAtPath:nsStringPath handler:nil]) {
                       NSLog(@"existing file - removed successfully");
               else {
                       // We can't continue with the previous file.
                       return LXe_FAILED;

       // Create a QTMovie with a writable data reference.
       NSError *error = nil;
       mMovie = [[QTMovie alloc] initToWritableFile:nsStringPath error:&error];

       // Mark the movie as editable.
       [mMovie setAttribute:[NSNumber numberWithBool:YES] forKey:QTMovieEditableAttribute];
       // Keep it around until we are done with it.
       [mMovie retain];

       // Reset audio filename.
       audioFileName = nil;

       return LXe_OK;

This function sets the framerate for the movie.

CCocoaQuickTimeMovie::mov_SetFramerate (
       int			 frate)
       frameRate = frate;

       return LXe_OK;

This functions adds another frame to the movie.

CCocoaQuickTimeMovie::mov_AddImage (
       ILxUnknownID		 img)
       CLxUser_Image		 image (img);
       static int		 releaseTime = 0;
       NSAutoreleasePool	*pool = NULL;

       // Set up our dimensions.
       unsigned int	 iw, ih;
       image.Size (&iw, &ih);
       unsigned int rowBytes = (iw * 3 + 3) & ~3;

       // Create the line and frame buffers.
       LXtImageByte		*line, *lineBuffer, *frameBuffer, *frameLine;
       lineBuffer = (LXtImageByte *) malloc (rowBytes);
       frameBuffer = (LXtImageByte *)malloc (ih * rowBytes);
       if (frameBuffer) {
                * There is apparently a bug in NSImage and NOT QTKit - 
                * which doesn't free the images passed into addImage - suspect that addImage add-ref's the NSImage
                * or some similar interplay.  Adding autorelease pool here is probably heavy-handed but
                * doesn't really affect performance.
               pool = [[NSAutoreleasePool alloc] init];

               // Copy the scanlines into a raw frame buffer.
               frameLine = frameBuffer;
               for (unsigned y = 0; y < ih; ++y) {
                       line = (LXtImageByte *) image.GetLine (
                               y, LXiIMP_RGB24, lineBuffer);
                       memcpy (frameLine, line, rowBytes);
                       frameLine += rowBytes;

               // Construct a CGImage from the raw frame buffer.
               CGDataProviderRef	 dataProvider;
               CGImageRef		 cgImageRef;
               CGColorSpaceRef		 colorSpace;

               dataProvider = CGDataProviderCreateWithData (
                       NULL, frameBuffer, ih * rowBytes, NULL);
               colorSpace = CGColorSpaceCreateDeviceRGB ();
               cgImageRef = CGImageCreate (iw, ih, 8, 24, rowBytes, colorSpace, 
                       kCGImageAlphaNone, dataProvider, NULL, 1, kCGRenderingIntentDefault);
               CGColorSpaceRelease (colorSpace);
               CGDataProviderRelease (dataProvider);
               // Convert the CGImage to an NSImage.
               NSImage *anImage = cgImageToNSImage (cgImageRef);

               // Free both the CGImage and the raw image buffer.
               CGImageRelease (cgImageRef);
               free (frameBuffer);

               // Calculate the duration of the image during the movie.
               long long timeValue = 1;
               long timeScale      = frameRate;
               QTTime duration     = QTMakeTime(timeValue, timeScale);

               // Add the NSImage for the specified duration to the QTMovie.
               [mMovie addImage:anImage 
               // Free our image object.
               [anImage release];
               [pool release];
       free (lineBuffer);

       return LXe_OK;

This function adds the temp audio file to the movie.

CCocoaQuickTimeMovie::mov_AddAudio (
       ILxUnknownID		 au)
       CLxUser_Audio		 audio (au);
       QTMovie			*audioTmpMovie = nil;
       NSFileManager		*fileManager;
       NSError			 *error;
       char			 tempName[1024];

       strcpy (tempName, tmpnam (nil));
       strcat (tempName, ".wav");		// Append the suffix for some reason
       audioFileName = [NSString stringWithUTF8String: tempName];
       [audioFileName retain];

       if (LXx_FAIL (CreateAudioTempFile (audioFileName, &audio)))
               return LXe_FAILED;

       audioTmpMovie = [QTMovie movieWithFile:audioFileName error:&error];
       if (audioTmpMovie) {
               NSArray *audioTracks = [audioTmpMovie tracksOfMediaType:QTMediaTypeSound];
               QTTrack *audioTrack  = nil;
               if ([audioTracks count] > 0)
                       audioTrack = [audioTracks objectAtIndex:0];
               if (audioTrack) {
                       QTTimeRange totalRange;
                       totalRange.time = QTZeroTime;
                       totalRange.duration = [[audioTmpMovie attributeForKey:QTMovieDurationAttribute] QTTimeValue];
                       [mMovie insertSegmentOfTrack:audioTrack timeRange:totalRange atTime:QTZeroTime];

       return LXe_OK;

This function stops the writing to the movie file and cleans up the temp files.

CCocoaQuickTimeMovie::mov_EndMovie (void)
       if (!audioFileName) {
               BOOL success = [mMovie updateMovieFile];
               [nsStringPath release];
               [mMovie release];
       } else {
               NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:QTMovieFlatten];
               NSString *tempMovie = [NSString stringWithUTF8String: tmpnam (nil)];
//		NSLog (@"tempMovie %@\n", tempMovie);
//		NSLog (@"audioFileName %@\n", audioFileName);
               // Write QT movie to a temp file.
               [mMovie writeToFile:tempMovie withAttributes:dict];
               [mMovie release];

               NSFileManager *fileManager = [[NSFileManager alloc] init];
               NSError       *error;
               // Delete the temp audio file and destination movie file.
               [fileManager removeItemAtPath:audioFileName error:&error];
               [fileManager removeItemAtPath:nsStringPath error:&error];
               // Move the temp file to the destination.
               [fileManager moveItemAtPath:tempMovie toPath:nsStringPath error:&error];
               [fileManager release];
               [audioFileName release];
               [nsStringPath release];

       return LXe_OK;