Difference between revisions of "Movie cocoaqt"

From The Foundry MODO SDK wiki
Jump to: navigation, search
Line 30: Line 30:
 
We want to have our class create a movie object, so we have our class inherit from CLxImpl_Movie.
 
We want to have our class create a movie object, so we have our class inherit from CLxImpl_Movie.
  
===Server Tags===
+
===[[Server_Tags|Server Tags]]===
  
 
  LXtTagInfoDesc CCocoaQuickTimeMovie::descInfo[] = {
 
  LXtTagInfoDesc CCocoaQuickTimeMovie::descInfo[] = {
Line 42: Line 42:
 
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
 
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
  
===Initialization===
+
===[[Initialize_(index)|Initialize]]===
  
 
  void
 
  void
Line 171: Line 171:
  
 
This function creates a temporary audio file with the audio object.
 
This function creates a temporary audio file with the audio object.
 +
 +
===Implementations===
 +
 +
        LxResult
 +
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",
 +
                  QTAddImageCodecType,
 +
                  [NSNumber numberWithLong:codecHighQuality],
 +
                  QTAddImageCodecQuality,
 +
                  nil];
 +
 +
        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;
 +
}
 +
 +
Creates a movie file and starts writing to it.
 +
 +
LxResult
 +
CCocoaQuickTimeMovie::mov_SetFramerate (
 +
        int frate)
 +
{
 +
        frameRate = frate;
 +
 +
        return LXe_OK;
 +
}
 +
 +
This function sets the framerate for the movie.
 +
 +
LxResult
 +
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 -
 +
                * http://www.mailinglistarchive.com/html/quartz-dev@lists.apple.com/2008-07/msg00552.html
 +
                * 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
 +
                        forDuration:duration
 +
                        withAttributes:myDict];
 +
               
 +
                // Free our image object.
 +
                [anImage release];
 +
                [pool release];
 +
        }
 +
        free (lineBuffer);
 +
 +
        return LXe_OK;
 +
}
 +
 +
This functions adds another frame to the movie.
 +
 +
LxResult
 +
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 adds the temp audio file to the movie.
 +
 +
LxResult
 +
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;
 +
}
 +
 +
This function stops the writing to the movie file and cleans up the temp files.

Revision as of 17:11, 9 September 2013

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.


Functionality

Movie_CocoaQT is a Plug-in Cocoa QuickTime saver for movies.

Code Walkthrough

Class Declaration

class CCocoaQuickTimeMovie : public CLxImpl_Movie
{
    public:
        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;
};

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

Server Tags

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

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

Initialize

void
initialize ()
{
       LXx_ADD_SERVER (Movie, CCocoaQuickTimeMovie, "quicktime");
}

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

Helper Functions

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]
                                     graphicsPort];
       CGContextDrawImage(imageContext, *(CGRect*)&imageRect, image);
       [newImage unlockFocus];
      
       return newImage;
}

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

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, 
                                       audioFileType,
                                       &outFormat,
                                       NULL,
                                       kAudioFileFlags_EraseFile,
                                       &outfile);
       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)
                       break;
   
               err = ExtAudioFileWrite (outfile, (UInt32) readFrames, &audioBufferList);
               if (err != noErr)
                       return LXe_FAILED;
       }
  
       free (buffer);
       ExtAudioFileDispose (outfile);
       return LXe_OK;
} 

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

Implementations

       LxResult
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",
                 QTAddImageCodecType,
                 [NSNumber numberWithLong:codecHighQuality],
                 QTAddImageCodecQuality,
                 nil];

       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;
}	

Creates a movie file and starts writing to it.

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

       return LXe_OK;
}	

This function sets the framerate for the movie.

LxResult
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 - 
                * http://www.mailinglistarchive.com/html/quartz-dev@lists.apple.com/2008-07/msg00552.html
                * 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 
                       forDuration:duration
                       withAttributes:myDict];
                
               // Free our image object.
               [anImage release];
               [pool release];
       }
       free (lineBuffer);

       return LXe_OK;
}

This functions adds another frame to the movie.

LxResult
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 adds the temp audio file to the movie.

LxResult
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;
}

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