Difference between revisions of "Movie cocoaqt"
Line 6: | Line 6: | ||
===Class Declaration=== | ===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 | class CCocoaQuickTimeMovie : public CLxImpl_Movie | ||
Line 24: | Line 26: | ||
NSString *audioFileName; | NSString *audioFileName; | ||
}; | }; | ||
− | |||
− | |||
===[[Server_Tags|Server Tags]]=== | ===[[Server_Tags|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[] = { | LXtTagInfoDesc CCocoaQuickTimeMovie::descInfo[] = { | ||
Line 36: | Line 38: | ||
{ 0 } | { 0 } | ||
}; | }; | ||
− | |||
− | |||
===[[Initialize_(index)|Initialize]]=== | ===[[Initialize_(index)|Initialize]]=== | ||
+ | |||
+ | We export a server of the movie type dependent on the CCocoaQuickTimeMovie class with the "quicktime" name. | ||
void | void | ||
Line 46: | Line 48: | ||
LXx_ADD_SERVER (Movie, CCocoaQuickTimeMovie, "quicktime"); | LXx_ADD_SERVER (Movie, CCocoaQuickTimeMovie, "quicktime"); | ||
} | } | ||
− | |||
− | |||
===Helper Functions=== | ===Helper Functions=== | ||
+ | |||
+ | This function takes a cg image and transforms it into a ns image, the apple format. | ||
static NSImage * | static NSImage * | ||
Line 76: | Line 78: | ||
} | } | ||
− | This function | + | This function creates a temporary audio file with the audio object. |
static LxResult | static LxResult | ||
Line 166: | Line 168: | ||
return LXe_OK; | return LXe_OK; | ||
} | } | ||
− | |||
− | |||
===Implementations=== | ===Implementations=== | ||
+ | |||
+ | Creates a movie file and starts writing to it. | ||
LxResult | LxResult | ||
Line 224: | Line 226: | ||
} | } | ||
− | + | This function sets the framerate for the movie. | |
LxResult | LxResult | ||
Line 235: | Line 237: | ||
} | } | ||
− | This | + | This functions adds another frame to the movie. |
LxResult | LxResult | ||
Line 314: | Line 316: | ||
} | } | ||
− | This | + | This function adds the temp audio file to the movie. |
LxResult | LxResult | ||
Line 352: | Line 354: | ||
} | } | ||
− | This function | + | This function stops the writing to the movie file and cleans up the temp files. |
LxResult | LxResult | ||
Line 384: | Line 386: | ||
return LXe_OK; | return LXe_OK; | ||
} | } | ||
− | |||
− |
Revision as of 21:13, 10 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.
When installed, the Movie_CocoaQT plugin adds Cocoa QuickTime saver utility for movies.
Contents
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 { 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; };
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 } };
Initialize
We export a server of the movie type dependent on the CCocoaQuickTimeMovie class with the "quicktime" name.
void 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] graphicsPort]; 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, 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; }
Implementations
Creates a movie file and starts writing to it.
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; }
This function sets the framerate for the movie.
LxResult CCocoaQuickTimeMovie::mov_SetFramerate ( int frate) { frameRate = frate; return LXe_OK; }
This functions adds another frame to 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 function adds the temp audio file 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 stops the writing to the movie file and cleans up the temp files.
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; }