File: QTExporter.mm

package info (click to toggle)
freej 0.10git20100110-1
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 32,080 kB
  • ctags: 22,705
  • sloc: cpp: 156,254; ansic: 25,531; sh: 13,538; perl: 4,624; makefile: 3,278; python: 2,889; objc: 1,284; asm: 1,125; ruby: 126
file content (355 lines) | stat: -rw-r--r-- 11,312 bytes parent folder | download
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