2
Common/Screen/PsychMovieWritingSupportQuickTime.c
6
MacOS/X and MS-Windows, but only if GStreamer support is disabled.
8
This is the movie editing and writing/creation engine based on the
9
Apple QuickTime API. It works on Apple MacOS/X and MS-Windows.
13
Mario Kleiner mk mario.kleiner@tuebingen.mpg.de
21
Psychtoolbox functions for dealing with Quicktime movie editing. This implementation uses
22
Quicktimes API's, therefore it works on Operating systems with Quicktime support,
23
currently OS/X and Windows.
31
// No Quicktime support for GNU/Linux:
32
#if PSYCH_SYSTEM != PSYCH_LINUX
33
#ifndef PTB_USE_GSTREAMER
35
#if PSYCH_SYSTEM == PSYCH_OSX
36
#include <Quicktime/QuickTimeComponents.h>
39
#if PSYCH_SYSTEM == PSYCH_WINDOWS
41
#include <QuickTimeComponents.h>
44
// Forward declaration of internal helper function:
47
// Record which defines all state for a capture device:
62
ImageDescriptionHandle ImageDesc;
69
} PsychMovieWriterRecordType;
71
static PsychMovieWriterRecordType moviewriterRecordBANK[PSYCH_MAX_MOVIEWRITERDEVICES];
72
static int moviewritercount = 0;
73
static psych_bool firsttime = TRUE;
75
void PsychMovieWritingInit(void)
79
for (i = 0; i < PSYCH_MAX_MOVIEWRITERDEVICES; i++) {
80
memset(&(moviewriterRecordBANK[i]), 0, sizeof(PsychMovieWriterRecordType));
88
void PsychDeleteAllMovieWriters(void)
92
for (i = 0; i < PSYCH_MAX_MOVIEWRITERDEVICES; i++) {
93
if (moviewriterRecordBANK[i].Movie) PsychFinalizeNewMovieFile(i);
97
void PsychExitMovieWriting(void)
99
PsychDeleteAllMovieWriters();
104
PsychMovieWriterRecordType* PsychGetMovieWriter(int moviehandle, psych_bool unsafe)
106
if (moviehandle < 0 || moviehandle >= PSYCH_MAX_MOVIEWRITERDEVICES) PsychErrorExitMsg(PsychError_user, "Invalid handle for moviewriter provided!");
107
if (!unsafe && (NULL == moviewriterRecordBANK[moviehandle].Movie)) PsychErrorExitMsg(PsychError_user, "Invalid handle for moviewriter provided! No such writer open.");
108
return(&(moviewriterRecordBANK[moviehandle]));
111
unsigned char* PsychGetVideoFrameForMoviePtr(int moviehandle, unsigned int* twidth, unsigned int* theight)
113
PsychMovieWriterRecordType* pwriterRec = PsychGetMovieWriter(moviehandle, FALSE);
114
if (NULL == pwriterRec->PixMap) return(NULL);
116
*twidth = pwriterRec->width;
117
*theight = pwriterRec->height - pwriterRec->padding; // Hack hack pwriterRec->padding !
118
return((unsigned char*) GetPixBaseAddr(pwriterRec->PixMap));
121
int PsychAddVideoFrameToMovie(int moviehandle, int frameDurationUnits, psych_bool isUpsideDown)
123
PsychMovieWriterRecordType* pwriterRec = PsychGetMovieWriter(moviehandle, FALSE);
127
unsigned char* pixptr = (unsigned char*) GetPixBaseAddr(pwriterRec->PixMap);
128
unsigned int* wordptr = (unsigned int*) GetPixBaseAddr(pwriterRec->PixMap);
129
unsigned int *wordptr2, *wordptr1;
132
if (NULL == pwriterRec->Media) return(0);
134
if ((frameDurationUnits < 1) && (PsychPrefStateGet_Verbosity() > 1)) printf("PTB-WARNING:In AddFrameToMovie: Negative or zero 'frameduration' %i units for moviehandle %i provided! Sounds like trouble ahead.\n", frameDurationUnits, moviehandle);
136
// Draw testpattern: Disabled at compile-time by default:
138
for (y = 0; y < pwriterRec->height; y++) {
139
for (x = 0; x < pwriterRec->width; x++) {
140
*(pixptr++) = (unsigned char) 255; // alpha
141
*(pixptr++) = (unsigned char) y; // Red
142
*(pixptr++) = (unsigned char) x; // Green
143
*(pixptr++) = (unsigned char) 0; // Blue
148
// Imagebuffer is upside-down: Need to flip it vertically:
150
h = pwriterRec->height - pwriterRec->padding; // Hack pwriterRec->padding !
151
w = pwriterRec->width;
153
for (y = 0; y < h/2; y++) {
155
wordptr2 += ((h - 1 - y) * w);
156
for (x = 0; x < w; x++) {
158
*(wordptr1++) = *wordptr2;
159
*(wordptr2++) = dummy;
164
// Apply codec to compress image:
165
myErr = CompressImage( pwriterRec->PixMap,
167
pwriterRec->CodecQuality,
168
pwriterRec->CodecType,
169
pwriterRec->ImageDesc,
170
pwriterRec->ComprDataPtr);
171
if (myErr != noErr) {
172
if (PsychPrefStateGet_Verbosity() > 0) printf("PTB-ERROR:In AddFrameToMovie: Video compression on current frame for moviehandle %i failed [CompessImage() returned QT error code %i]!\n", moviehandle, (int) myErr);
176
// Add encoded buffer to movie:
177
myErr = AddMediaSample( pwriterRec->Media,
178
pwriterRec->ComprDataHdl,
179
0, // no offset in data
180
(**(pwriterRec->ImageDesc)).dataSize,
181
frameDurationUnits, // frame duration
182
(SampleDescriptionHandle) pwriterRec->ImageDesc,
184
0, // self-contained samples
186
if (myErr != noErr) {
187
if (PsychPrefStateGet_Verbosity() > 0) printf("PTB-ERROR:In AddFrameToMovie: Adding current frame to moviehandle %i failed [AddMediaSample() returned QT error code %i]!\n", moviehandle, (int) myErr);
191
if (PsychPrefStateGet_Verbosity() > 5) printf("PTB-DEBUG:In AddFrameToMovie: Added new videoframe with %i units duration and upsidedown = %i to moviehandle %i.\n", frameDurationUnits, (int) isUpsideDown, moviehandle);
197
int PsychCreateNewMovieFile(char* moviefile, int width, int height, double framerate, char* movieoptions)
199
PsychMovieWriterRecordType* pwriterRec = NULL;
201
long myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
208
// Still capacity left?
209
if (moviewritercount >= PSYCH_MAX_MOVIEWRITERDEVICES) PsychErrorExitMsg(PsychError_user, "Maximum number of movie writers exceeded. Please close some first!");
211
// Find first free (i.e., NULL) slot and assign moviehandle:
212
while ((pwriterRec = PsychGetMovieWriter(moviehandle, TRUE)) && pwriterRec->Movie) moviehandle++;
215
#if PSYCH_SYSTEM == PSYCH_WINDOWS
216
// Initialize Quicktime for Windows compatibility layer: This will fail if
217
// QT isn't installed on the Windows machine...
218
myErr = InitializeQTML(0);
220
printf("PTB-ERROR: Quicktime Media Layer initialization failed with error code %i in call to InitializeQTML(0): Quicktime not properly installed?!?", (int) myErr);
221
PsychErrorExitMsg(PsychError_system, "Quicktime Media Layer initialization failed. Quicktime not properly installed or not installed at all?!?");
225
// Initialize Quicktime-Subsystem:
226
myErr = EnterMovies();
228
printf("PTB-ERROR: Quicktime initialization failed with error code %i in call to EnterMovies().", (int) myErr);
229
PsychErrorExitMsg(PsychError_system, "Quicktime initialization in EnterMovies() failed!!!");
234
// Translate char string with movie name to FSSpec:
235
NativePathNameToFSSpec(moviefile, &pwriterRec->File, 0);
237
pwriterRec->CodecType = kH264CodecType;
238
pwriterRec->CodecQuality = codecNormalQuality;
239
pwriterRec->ResID = movieInDataForkResID;
240
pwriterRec->height = height;
241
pwriterRec->width = width;
242
pwriterRec->padding = (PSYCH_SYSTEM == PSYCH_OSX) ? 1 : 0;
244
// Assign numeric 32-bit FOURCC equivalent code to select codec:
245
// This is optional. We default to kH264CodecType:
246
if ((poption = strstr(movieoptions, "CodecFOURCCId="))) {
247
if (sscanf(poption, "CodecFOURCCId=%i", &dummyInt) == 1) {
248
pwriterRec->CodecType = (CodecType) dummyInt;
249
if (PsychPrefStateGet_Verbosity() > 3) printf("PTB-INFO: Codec with FOURCC numeric id %i selected for encoding of movie %i [%s].\n", dummyInt, moviehandle, moviefile);
251
else PsychErrorExitMsg(PsychError_user, "Invalid CodecFOURCCId= parameter provided in movieoptions parameter. Parse error!");
254
// Assign 4 character string FOURCC code to select codec:
255
if ((poption = strstr(movieoptions, "CodecFOURCC="))) {
256
if (sscanf(poption, "CodecFOURCC=%c%c%c%c", &myfourcc[3], &myfourcc[2], &myfourcc[1], &myfourcc[0]) == 4) {
257
pwriterRec->CodecType = (CodecType) (*((unsigned int*) (&myfourcc[0])));
258
if (PsychPrefStateGet_Verbosity() > 3) printf("PTB-INFO: Codec with FOURCC '%c%c%c%c' = numeric id %i selected for encoding of movie %i [%s].\n", myfourcc[3], myfourcc[2], myfourcc[1], myfourcc[0], (int) pwriterRec->CodecType, moviehandle, moviefile);
260
else PsychErrorExitMsg(PsychError_user, "Invalid CodecFOURCC= parameter provided in movieoptions parameter. Must be exactly 4 characters! Parse error!");
263
// Assign numeric encoding quality level:
264
// This is optional. We default to "normal quality":
265
if ((poption = strstr(movieoptions, "EncodingQuality="))) {
266
if (sscanf(poption, "EncodingQuality=%f", &dummyFloat) == 1) {
267
// Map floating point quality level between 0.0 and 1.0 to 10 discrete levels:
268
dummyInt = (int)(10.0 * dummyFloat + 0.5);
269
dummyInt = (dummyInt < 0) ? 0 : dummyInt;
270
dummyInt = (dummyInt > 10) ? 10 : dummyInt;
272
// Assign one of Quicktime's 6 code quality setting to the 10 levels:
275
pwriterRec->CodecQuality = codecMinQuality;
279
pwriterRec->CodecQuality = codecMinQuality;
283
pwriterRec->CodecQuality = codecLowQuality;
287
pwriterRec->CodecQuality = codecLowQuality;
291
pwriterRec->CodecQuality = codecNormalQuality;
295
pwriterRec->CodecQuality = codecNormalQuality;
299
pwriterRec->CodecQuality = codecNormalQuality;
303
pwriterRec->CodecQuality = codecHighQuality;
307
pwriterRec->CodecQuality = codecHighQuality;
311
pwriterRec->CodecQuality = codecMaxQuality;
315
pwriterRec->CodecQuality = codecLosslessQuality;
320
if (PsychPrefStateGet_Verbosity() > 3) printf("PTB-INFO: Encoding quality level %i selected for encoding of movie %i [%s].\n", dummyInt, moviehandle, moviefile);
322
else PsychErrorExitMsg(PsychError_user, "Invalid EncodingQuality= parameter provided in movieoptions parameter. Parse error!");
325
// Check for valid parameters. Also warn if some parameters are borderline for certain codecs:
326
if ((framerate < 1) && (PsychPrefStateGet_Verbosity() > 1)) printf("PTB-WARNING:In CreateMovie: Negative or zero 'framerate' %f units for moviehandle %i provided! Sounds like trouble ahead.\n", (float) framerate, moviehandle);
327
if (width < 1) PsychErrorExitMsg(PsychError_user, "In CreateMovie: Invalid zero or negative 'width' for video frame size provided!");
328
if ((width < 4) && (PsychPrefStateGet_Verbosity() > 1)) printf("PTB-WARNING:In CreateMovie: 'width' of %i pixels for moviehandle %i provided! Some video codecs may malfunction with such a small width.\n", width, moviehandle);
329
if ((width % 4 != 0) && (PsychPrefStateGet_Verbosity() > 1)) printf("PTB-WARNING:In CreateMovie: 'width' of %i pixels for moviehandle %i provided! Some video codecs may malfunction with a width which is not a multiple of 4 or 16.\n", width, moviehandle);
330
if (height < 1) PsychErrorExitMsg(PsychError_user, "In CreateMovie: Invalid zero or negative 'height' for video frame size provided!");
331
if ((height < 4) && (PsychPrefStateGet_Verbosity() > 1)) printf("PTB-WARNING:In CreateMovie: 'height' of %i pixels for moviehandle %i provided! Some video codecs may malfunction with such a small height.\n", height, moviehandle);
333
// Create a movie file for the destination movie:
334
myErr = CreateMovieFile(&pwriterRec->File, FOUR_CHAR_CODE('TVOD'), smCurrentScript, myFlags, &pwriterRec->ResRefNum, &pwriterRec->Movie);
335
if (myErr != noErr) {
336
if (PsychPrefStateGet_Verbosity() > 0) printf("PTB-ERROR:In CreateMovie: Creating movie file with handle %i [%s] failed [CreateMovieFile() returned QT error code %i]!\n", moviehandle, moviefile, (int) myErr);
337
if ((myErr == -47) && (PsychPrefStateGet_Verbosity() > 0)) printf("PTB-ERROR:In CreateMovie: Do you have this file open or highlighted in some other application like Quicktime player or Finder?!?\n");
341
// Create the movie track and media:
342
pwriterRec->Track = NewMovieTrack(pwriterRec->Movie, FixRatio(width, 1), FixRatio(height, 1), kNoVolume);
343
myErr = GetMoviesError();
344
if (myErr != noErr) {
345
if (PsychPrefStateGet_Verbosity() > 0) printf("PTB-ERROR:In CreateMovie: Creating track with videoframes of size %i x %i pixels for movie file %i [%s] failed [NewMovieTrack() returned QT error code %i]!\n", width, height, moviehandle, moviefile, (int) myErr);
349
pwriterRec->Media = NewTrackMedia(pwriterRec->Track, VideoMediaType, (int) framerate, NULL, 0);
350
myErr = GetMoviesError();
351
if (myErr != noErr) {
352
if (PsychPrefStateGet_Verbosity() > 0) printf("PTB-ERROR:In CreateMovie: Creating video media stream with framerate %i for file with handle %i [%s] failed [NewTrackMedia() returned QT error code %i]!\n", (int) framerate, moviehandle, moviefile, (int) myErr);
356
// Create the media samples:
357
myErr = BeginMediaEdits(pwriterRec->Media);
358
if (myErr != noErr) {
359
if (PsychPrefStateGet_Verbosity() > 0) printf("PTB-ERROR:In CreateMovie: Creating movie file with handle %i [%s] failed [BeginMediaEdits() returned QT error code %i]!\n", moviehandle, moviefile, (int) myErr);
363
// Create a GWorld as target with a pixedepth of 32 bpp:
364
// Hack hack pwriterRec->padding!!
365
MacSetRect(&pwriterRec->Rect, 0, 0, width - pwriterRec->padding, height - pwriterRec->padding);
366
myErr = NewGWorld(&pwriterRec->GWorld, 32, &pwriterRec->Rect, NULL, NULL, (GWorldFlags)0);
367
if (myErr != noErr) {
368
if (PsychPrefStateGet_Verbosity() > 0) printf("PTB-ERROR:In CreateMovie: Creating movie file with handle %i [%s] failed [NewGWorld(rectsize = %i,%i and padding %i) returned QT error code %i]!\n", moviehandle, moviefile, width - pwriterRec->padding, height - pwriterRec->padding, pwriterRec->padding, (int) myErr);
372
pwriterRec->PixMap = GetGWorldPixMap(pwriterRec->GWorld);
373
if (pwriterRec->PixMap == NULL) {
374
if (PsychPrefStateGet_Verbosity() > 0) printf("PTB-ERROR:In CreateMovie: Creating movie file with handle %i [%s] failed [GetGWorldPixMap() returned NULL-Ptr]!\n", moviehandle, moviefile);
378
LockPixels(pwriterRec->PixMap);
379
myErr = GetMaxCompressionSize( pwriterRec->PixMap,
381
0, // let ICM choose depth
382
pwriterRec->CodecQuality,
383
pwriterRec->CodecType,
384
(CompressorComponent) anyCodec,
385
&pwriterRec->MaxComprSize);
386
if (myErr != noErr) {
387
if (PsychPrefStateGet_Verbosity() > 0) {
388
printf("PTB-ERROR:In CreateMovie: Creating movie file with handle %i [%s] failed [GetMaxCompressionSize() returned QT error code %i]!\n", moviehandle, moviefile, (int) myErr);
389
if (myErr == noCodecErr) printf("PTB-ERROR:In CreateMovie: The system doesn't know or support the requested type of video movie codec.\n");
394
pwriterRec->ComprDataHdl = NewHandle(pwriterRec->MaxComprSize);
395
if (pwriterRec->ComprDataHdl == NULL) {
396
if (PsychPrefStateGet_Verbosity() > 0) printf("PTB-ERROR:In CreateMovie: Creating movie file with handle %i [%s] failed [NewHandle(for compressor) returned NULL-Ptr]!\n", moviehandle, moviefile);
400
HLockHi(pwriterRec->ComprDataHdl);
401
pwriterRec->ComprDataPtr = *(pwriterRec->ComprDataHdl);
403
pwriterRec->ImageDesc = (ImageDescriptionHandle)NewHandle(4);
404
if (pwriterRec->ImageDesc == NULL) {
405
if (PsychPrefStateGet_Verbosity() > 0) printf("PTB-ERROR:In CreateMovie: Creating movie file with handle %i [%s] failed [NewHandle(4) returned NULL-Ptr]!\n", moviehandle, moviefile);
409
SetGWorld(pwriterRec->GWorld, NULL);
411
// Increment count of open movie writers:
414
if (PsychPrefStateGet_Verbosity() > 3) printf("PTB-INFO: Moviehandle %i successfully opened for movie writing into file '%s'.\n", moviehandle, moviefile);
416
// Return new handle:
421
if (pwriterRec->ComprDataHdl != NULL)
422
DisposeHandle(pwriterRec->ComprDataHdl);
424
if (pwriterRec->GWorld != NULL)
425
DisposeGWorld(pwriterRec->GWorld);
427
if (pwriterRec->ResRefNum != 0)
428
CloseMovieFile(pwriterRec->ResRefNum);
430
if (pwriterRec->Movie != NULL)
431
DisposeMovie(pwriterRec->Movie);
433
pwriterRec->Movie = NULL;
434
pwriterRec->Track = NULL;
435
pwriterRec->Media = NULL;
436
pwriterRec->ResRefNum = 0;
437
pwriterRec->GWorld = NULL;
438
pwriterRec->PixMap = NULL;
439
pwriterRec->MaxComprSize = 0L;
440
pwriterRec->ComprDataHdl = NULL;
441
pwriterRec->ComprDataPtr = NULL;
442
pwriterRec->ImageDesc = NULL;
443
pwriterRec->SavedPort = NULL;
444
pwriterRec->SavedDevice = NULL;
450
int PsychFinalizeNewMovieFile(int movieHandle)
454
PsychMovieWriterRecordType* pwriterRec = PsychGetMovieWriter(movieHandle, FALSE);
456
if (NULL == pwriterRec->Media) return(0);
458
myErr = EndMediaEdits(pwriterRec->Media);
459
if (myErr != noErr) {
460
if (PsychPrefStateGet_Verbosity() > 0) printf("PTB-ERROR:In FinalizeMovie: Finalizing/Closing movie file with handle %i failed [EndMediaEdits() returned QT error code %i]!\n", movieHandle, (int) myErr);
464
// add the media to the track
465
myErr = InsertMediaIntoTrack(pwriterRec->Track, 0, 0, GetMediaDuration(pwriterRec->Media), fixed1);
466
if (myErr != noErr) {
467
if (PsychPrefStateGet_Verbosity() > 0) printf("PTB-ERROR:In FinalizeMovie: Finalizing/Closing movie file with handle %i failed [InsertMediaIntoTrack() returned QT error code %i]!\n", movieHandle, (int) myErr);
471
// add the movie atom to the movie file
472
myErr = AddMovieResource(pwriterRec->Movie, pwriterRec->ResRefNum, &pwriterRec->ResID, NULL);
473
if (myErr != noErr) {
474
if (PsychPrefStateGet_Verbosity() > 0) printf("PTB-ERROR:In FinalizeMovie: Finalizing/Closing movie file with handle %i failed [AddMovieResource() returned QT error code %i]!\n", movieHandle, (int) myErr);
479
if (pwriterRec->ImageDesc != NULL)
480
DisposeHandle((Handle)pwriterRec->ImageDesc);
482
if (pwriterRec->ComprDataHdl != NULL)
483
DisposeHandle(pwriterRec->ComprDataHdl);
485
if (pwriterRec->GWorld != NULL)
486
DisposeGWorld(pwriterRec->GWorld);
488
if (pwriterRec->ResRefNum != 0)
489
CloseMovieFile(pwriterRec->ResRefNum);
491
if (pwriterRec->Movie != NULL)
492
DisposeMovie(pwriterRec->Movie);
494
myErr = GetMoviesError();
495
if (myErr != noErr) {
496
if (PsychPrefStateGet_Verbosity() > 0) printf("PTB-ERROR:In FinalizeMovie: Finalizing/Closing movie file with handle %i failed [CloseMovieFile() or DisposeMovie() returned QT error code %i]!\n", movieHandle, (int) myErr);
499
pwriterRec->Movie = NULL;
500
pwriterRec->Track = NULL;
501
pwriterRec->Media = NULL;
502
pwriterRec->ResRefNum = 0;
503
pwriterRec->GWorld = NULL;
504
pwriterRec->PixMap = NULL;
505
pwriterRec->MaxComprSize = 0L;
506
pwriterRec->ComprDataHdl = NULL;
507
pwriterRec->ComprDataPtr = NULL;
508
pwriterRec->ImageDesc = NULL;
509
pwriterRec->SavedPort = NULL;
510
pwriterRec->SavedDevice = NULL;
512
// Decrement count of active writers:
515
// Return success/fail status:
516
if ((myErr == noErr) && (PsychPrefStateGet_Verbosity() > 3)) printf("PTB-INFO: Moviehandle %i successfully closed and movie written to filesystem.\n", movieHandle);
520
psych_bool PsychAddAudioBufferToMovie(int moviehandle, unsigned int nrChannels, unsigned int nrSamples, double* buffer)
522
PsychErrorExitMsg(PsychError_unimplemented, "Sorry, storing audio tracks in movies is not supported by the Quicktime based movie writing functions.");