Difference between revisions of "Movie cocoaqt"
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 | ||
− | === | + | ===[[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.
Contents
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.