1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
|
/* FreeJ
* (c) Copyright 2009 Andrea Guzzo <xant@dyne.org>
*
* This source code is free software; you can redistribute it and/or
* modify it under the terms of the GNU Public License as published
* by the Free Software Foundation; either version 3 of the License,
* or (at your option) any later version.
*
* This source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* Please refer to the GNU Public License for more details.
*
* You should have received a copy of the GNU Public License along with
* this source code; if not, write to:
* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#import <CVScreenView.h>
#import <QTExporter.h>
#include <fps.h>
@implementation QTExporter
- (id)initWithScreen:(CVScreenView *)cvscreen
{
mDataHandlerRef = nil;
mMovie = nil;
outputFile = nil;
screen = cvscreen;
savedMovieAttributes = [NSDictionary
dictionaryWithObjects:
[NSArray arrayWithObjects:
[NSNumber numberWithBool:YES],
[NSNumber numberWithBool:YES],
[NSNumber numberWithLong:'mpg4'], // TODO - allow to select the codec
nil]
forKeys:
[NSArray arrayWithObjects:
QTMovieFlatten, QTMovieExport, QTMovieExportType, nil]];
// when adding images we must provide a dictionary
// specifying the codec attributes
encodingProperties = [[NSDictionary dictionaryWithObjectsAndKeys:@"mp4v",
QTAddImageCodecType,
[NSNumber numberWithLong:codecNormalQuality],
QTAddImageCodecQuality,
nil] retain];
lock = [[NSRecursiveLock alloc] init];
memset(&lastTimestamp, 0, sizeof(lastTimestamp));
lastImage = nil;
return [super init];
}
- (void)dealloc
{
[savedMovieAttributes release];
[encodingProperties release];
[lock release];
[super dealloc];
}
- (Movie)quicktimeMovieFromTempFile:(DataHandler *)outDataHandler error:(OSErr *)outErr
{
*outErr = -1;
// generate a name for our movie file
NSString *tempName = [NSString stringWithCString:tmpnam(nil)
encoding:[NSString defaultCStringEncoding]];
if (!tempName)
return nil;
Handle dataRefH = nil;
OSType dataRefType;
// create a file data reference for our movie
*outErr = QTNewDataReferenceFromFullPathCFString((CFStringRef)tempName,
kQTPOSIXPathStyle,
0,
&dataRefH,
&dataRefType);
if (*outErr != noErr)
return nil;
// create a QuickTime movie from our file data reference
Movie qtMovie = nil;
CreateMovieStorage (dataRefH,
dataRefType,
'TVOD',
smSystemScript,
newMovieActive,
outDataHandler,
&qtMovie);
*outErr = GetMoviesError();
if (*outErr != noErr) goto cantcreatemovstorage;
return qtMovie;
// error handling
cantcreatemovstorage:
DisposeHandle(dataRefH);
return nil;
}
- (BOOL)pathExists:(NSString *)aFilePath
{
NSFileManager *defaultMgr = [NSFileManager defaultManager];
return [defaultMgr fileExistsAtPath:aFilePath];
}
//
// writeSafelyToURL
//
// Write the document to a movie file
//
//
- (BOOL)writeSafelyToURL:(NSURL *)absoluteURL
{
BOOL success = NO;
if ([self pathExists:[absoluteURL path]] == YES)
{
// movie file already exists, so we'll just update
// the movie resource
success = [mMovie updateMovieFile];
}
else
{
success = [mMovie writeToFile:outputFile withAttributes:savedMovieAttributes];
// movie file does not exist, so we'll flatten our in-memory
// movie to the file
// now we can release our old in-memory movie
[mMovie release];
mMovie = nil;
// ...and re-acquire our movie from the new movie file
mMovie = [QTMovie movieWithFile:[absoluteURL path] error:nil];
[mMovie retain];
// set the new movie to the view
//[[mWinController movieView] setMovie:mMovie];
}
return success;
}
//
// addImage
//
// given an array a CIImage pointer, convert it to NSImage *
// and add the resulting image to the movie as a new MPEG4 frame
//
- (void)addImage:(CIImage *)image
{
NSImage *nsImage = [[NSImage alloc] initWithSize:NSMakeSize([image extent].size.width, [image extent].size.height)];
[nsImage addRepresentation:[NSCIImageRep imageRepWithCIImage:image]];
// create a QTTime value to be used as a duration when adding
// the image to the movie
//long timeScale = [[mMovie attributeForKey:@"QTMovieTimeScaleAttribute"] longValue];
//long long timeValue = timeScale/25;
QTTime duration;// = QTMakeTime(timeValue, timeScale);
// iterate over all the images in the array and add
// them to our movie one-by-one
//NSLog(@"%d\n", [images count]);
CVTimeStamp now;
memset(&now, 0 , sizeof(now));
if (CVDisplayLinkGetCurrentTime((CVDisplayLinkRef)[screen getDisplayLink], &now) == kCVReturnSuccess) {
if (lastImage) {
int64_t timedelta = lastTimestamp.videoTime?now.videoTime-lastTimestamp.videoTime:now.videoTimeScale/25;
duration = QTMakeTime(timedelta, now.videoTimeScale);
memcpy(&lastTimestamp, &now, sizeof(lastTimestamp));
[QTMovie enterQTKitOnThread];
// Adds an image for the specified duration to the QTMovie
[mMovie addImage:lastImage
forDuration:duration
withAttributes:encodingProperties];
// free up our image object
if ([self isRunning])
[self writeSafelyToURL:[NSURL fileURLWithPath:outputFile]];
[QTMovie exitQTKitOnThread];
}
if (lastImage)
[lastImage release];
lastImage = nsImage;
} else {
/* TODO - Error Messages */
}
bail:
return;
}
- (BOOL)openOutputMovie
{
/*
NOTES ABOUT CREATING A NEW ("EMPTY") MOVIE AND ADDING IMAGE FRAMES TO IT
In order to compose a new movie from a series of image frames with QTKit
it is of course necessary to first create an "empty" movie to which these
frames can be added. Actually, the real requirements (in QuickTime terminology)
for such an "empty" movie are that it contain a writable data reference. A
movie with a writable data reference can then accept the addition of image
frames via the -addImage method.
Prior to QuickTime 7.2.1, QTKit did not provide a QTMovie method for creating a
QTMovie with a writable data reference. In this case, we can use the native
QuickTime API CreateMovieStorage() to create a QuickTime movie with a writable
data reference (in our example below we use a data reference to a file). We then
use the QTKit movieWithQuickTimeMovie: method to instantiate a QTMovie from this
native QuickTime movie.
Finally, images are added to the movie as movie frames using -addImage.
NEW IN QUICKTIME 7.2.1
QuickTime 7.2.1 now provides a new method:
- (id)initToWritableFile:(NSString *)filename error:(NSError **)errorPtr;
to create a QTMovie with a writable data reference. This eliminates the need to
use the native QuickTime API CreateMovieStorage() as described above.
The code below checks first to see if this new method initToWritableFile: is
available, and if so it will use it rather than use the native API.
*/
//[QTMovie enterQTKitOnThread];
// Check first if the new QuickTime 7.2.1 initToWritableFile: method is available
if ([[[[QTMovie alloc] init] autorelease] respondsToSelector:@selector(initToWritableFile:error:)] == YES)
{
NSError *error;
NSFileHandle *ofile;
ofile = [NSFileHandle fileHandleForUpdatingAtPath:outputFile];
if (ofile) {
unsigned long ticks;
[ofile truncateFileAtOffset:0];
[ofile synchronizeFile];
[ofile closeFile];
// let the os really sync the filesystem before trying to reopen the file
Delay(5, &ticks);
}
// Create a QTMovie with a writable data reference
mMovie = [[QTMovie alloc] initToWritableFile:outputFile error:&error];
if (!mMovie) {
NSLog(@"Can't open output file: %@ : %@", outputFile, [error localizedFailureReason]);
return NO;
}
}
else
{
// The QuickTime 7.2.1 initToWritableFile: method is not available, so use the native
// QuickTime API CreateMovieStorage() to create a QuickTime movie with a writable
// data reference
OSErr err;
// create a native QuickTime movie
Movie qtMovie = [self quicktimeMovieFromTempFile:&mDataHandlerRef error:&err];
if (nil == qtMovie) {
[lock unlock];
return NO;
}
// instantiate a QTMovie from our native QuickTime movie
mMovie = [QTMovie movieWithQuickTimeMovie:qtMovie disposeWhenDone:YES error:nil];
if (!mMovie || err) {
[lock unlock];
return NO;
}
}
// mark the movie as editable
[mMovie setAttribute:[NSNumber numberWithBool:YES] forKey:QTMovieEditableAttribute];
// keep it around until we are done with it...
[mMovie retain];
//[mMovie setIdling:NO];
//[mMovie setAttribute:[NSNumber numberWithLong:1000000000] forKey:QTMovieTimeScaleAttribute];
//[QTMovie exitQTKitOnThread];
return YES;
}
- (void) exporterThread:(id)arg
{
NSAutoreleasePool *pool;
FPS fps;
fps.init(60); // XXX
if (!encodingProperties)
;// TODO - Handle error condition
while ([self isRunning]) {
pool = [[NSAutoreleasePool alloc] init];
[self addImage:[screen exportSurface]];
fps.calc();
fps.delay();
[pool release];
}
}
//
// buildQTKitMovie
//
// Build a QTKit movie from a series of image frames
//
//
- (BOOL)startExport
{
if (!outputFile)
outputFile = [[NSString stringWithCString:DEFAULT_OUTPUT_FILE encoding:NSUTF8StringEncoding] retain];
if ([self openOutputMovie]) {
[NSThread detachNewThreadSelector:@selector(exporterThread:)
toTarget:self withObject:nil];
return YES;
}
return NO;
}
- (BOOL)setOutputFile:(NSString *)path
{
if (outputFile)
[outputFile release];
outputFile = [path retain];
return YES;
}
- (void)stopExport
{
[lock lock];
if (mMovie)
[mMovie release];
mMovie = nil;
if (mDataHandlerRef) {
CFRelease(mDataHandlerRef);
mDataHandlerRef = nil;
}
[lock unlock];
}
- (BOOL)isRunning
{
return mMovie?YES:NO;
}
@end
|