Move the sources to trunk
[opencv] / otherlibs / highgui / cvcap_qt.cpp
1 /*M///////////////////////////////////////////////////////////////////////////////////////
2 //
3 //  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
4 //
5 //  By downloading, copying, installing or using the software you agree to this license.
6 //  If you do not agree to this license, do not download, install,
7 //  copy or use the software.
8 //
9 //
10 //                        Intel License Agreement
11 //                For Open Source Computer Vision Library
12 //
13 // Copyright (C) 2000, Intel Corporation, all rights reserved.
14 // Third party copyrights are property of their respective owners.
15 //
16 // Redistribution and use in source and binary forms, with or without modification,
17 // are permitted provided that the following conditions are met:
18 //
19 //   * Redistribution's of source code must retain the above copyright notice,
20 //     this list of conditions and the following disclaimer.
21 //
22 //   * Redistribution's in binary form must reproduce the above copyright notice,
23 //     this list of conditions and the following disclaimer in the documentation
24 //     and/or other materials provided with the distribution.
25 //
26 //   * The name of Intel Corporation may not be used to endorse or promote products
27 //     derived from this software without specific prior written permission.
28 //
29 // This software is provided by the copyright holders and contributors "as is" and
30 // any express or implied warranties, including, but not limited to, the implied
31 // warranties of merchantability and fitness for a particular purpose are disclaimed.
32 // In no event shall the Intel Corporation or contributors be liable for any direct,
33 // indirect, incidental, special, exemplary, or consequential damages
34 // (including, but not limited to, procurement of substitute goods or services;
35 // loss of use, data, or profits; or business interruption) however caused
36 // and on any theory of liability, whether in contract, strict liability,
37 // or tort (including negligence or otherwise) arising in any way out of
38 // the use of this software, even if advised of the possibility of such damage.
39 //
40 //M*/
41
42
43 #include "_highgui.h"
44 #include "cv.h"
45
46 // Original implementation by   Mark Asbach
47 //                              Institute of Communications Engineering
48 //                              RWTH Aachen University
49 //
50 // For implementation details and background see:
51 // http://developer.apple.com/samplecode/qtframestepper.win/listing1.html
52 //
53 // Please note that timing will only be correct for videos that contain a visual track
54 // that has full length (compared to other tracks)
55
56
57 // standard includes
58 #include <cstdio>
59 #include <cassert>
60
61 // Mac OS includes
62 #include <Carbon/Carbon.h>
63 #include <CoreFoundation/CoreFoundation.h>
64 #include <QuickTime/QuickTime.h>
65
66
67 // Global state (did we call EnterMovies?)
68 static int did_enter_movies = 0;
69
70 // ----------------------------------------------------------------------------------------
71 #pragma mark Reading Video Files
72
73 /// Movie state structure for QuickTime movies
74 typedef struct CvCapture_QT_Movie
75 {
76         CvCaptureVTable * vtable; 
77
78         Movie      myMovie;            // movie handle
79         GWorldPtr  myGWorld;           // we render into an offscreen GWorld
80         
81         CvSize     size;               // dimensions of the movie
82         TimeValue  movie_start_time;   // movies can start at arbitrary times
83         long       number_of_frames;   // duration in frames
84         long       next_frame_time;
85         long       next_frame_number; 
86         
87         IplImage * image_rgb;          // will point to the PixMap of myGWorld
88         IplImage * image_bgr;          // will be returned by icvRetrieveFrame_QT()
89
90 } CvCapture_QT_Movie;
91
92
93
94 static       int         icvOpenFile_QT_Movie      (CvCapture_QT_Movie * capture, const char  * filename);
95 static       int         icvClose_QT_Movie         (CvCapture_QT_Movie * capture);
96 static       double      icvGetProperty_QT_Movie   (CvCapture_QT_Movie * capture, int property_id);
97 static       int         icvSetProperty_QT_Movie   (CvCapture_QT_Movie * capture, int property_id, double value);
98 static       int         icvGrabFrame_QT_Movie     (CvCapture_QT_Movie * capture);
99 static const void      * icvRetrieveFrame_QT_Movie (CvCapture_QT_Movie * capture);
100
101
102 static CvCaptureVTable capture_QT_Movie_vtable = 
103 {
104     6,
105     (CvCaptureCloseFunc)           icvClose_QT_Movie,
106     (CvCaptureGrabFrameFunc)       icvGrabFrame_QT_Movie,
107     (CvCaptureRetrieveFrameFunc)   icvRetrieveFrame_QT_Movie,
108     (CvCaptureGetPropertyFunc)     icvGetProperty_QT_Movie,
109     (CvCaptureSetPropertyFunc)     icvSetProperty_QT_Movie,
110     (CvCaptureGetDescriptionFunc)  0
111 };
112
113
114 CvCapture * cvCaptureFromFile_QT (const char * filename)
115 {
116         static int did_enter_movies = 0;
117         if (! did_enter_movies)
118         {
119                 EnterMovies();
120                 did_enter_movies = 1;
121         }
122         
123     CvCapture_QT_Movie * capture = 0;
124         
125     if (filename)
126     {
127         capture = (CvCapture_QT_Movie *) cvAlloc (sizeof (*capture));
128         memset (capture, 0, sizeof(*capture));
129                 
130         capture->vtable = &capture_QT_Movie_vtable;
131                 
132         if (!icvOpenFile_QT_Movie (capture, filename))
133             cvReleaseCapture ((CvCapture**) & capture);
134     }
135         
136     return (CvCapture*)capture;
137 }
138
139
140
141 /**
142  * convert full path to CFStringRef and open corresponding Movie. Then
143  * step over 'interesting frame times' to count total number of frames
144  * for video material with varying frame durations and create offscreen
145  * GWorld for rendering the movie frames.
146  * 
147  * @author Mark Asbach <asbach@ient.rwth-aachen.de>
148  * @date   2005-11-04
149  */
150 static int icvOpenFile_QT_Movie (CvCapture_QT_Movie * capture, const char * filename)
151 {
152         Rect          myRect;
153         short         myResID        = 0;
154         Handle        myDataRef      = nil;
155         OSType        myDataRefType  = 0;
156         OSErr         myErr          = noErr;
157         
158         
159         // no old errors please
160         ClearMoviesStickyError ();
161         
162         // initialize pointers to zero
163         capture->myMovie  = 0;
164         capture->myGWorld = nil;
165         
166         // initialize numbers with invalid values
167         capture->next_frame_time   = -1;
168         capture->next_frame_number = -1;
169         capture->number_of_frames  = -1;
170         capture->movie_start_time  = -1;
171         capture->size              = cvSize (-1,-1);
172         
173         
174         // we would use CFStringCreateWithFileSystemRepresentation (kCFAllocatorDefault, filename) on Mac OS X 10.4
175         CFStringRef   inPath = CFStringCreateWithCString (kCFAllocatorDefault, filename, kCFStringEncodingISOLatin1);
176         OPENCV_ASSERT ((inPath != nil), "icvOpenFile_QT_Movie", "couldnt create CFString from a string");
177         
178         // create the data reference
179         myErr = QTNewDataReferenceFromFullPathCFString (inPath, kQTPOSIXPathStyle, 0, & myDataRef, & myDataRefType);
180         if (myErr != noErr)
181         {
182                 fprintf (stderr, "Couldn't create QTNewDataReferenceFromFullPathCFString().\n");
183                 return 0;
184         }
185         
186         // get the Movie
187         myErr = NewMovieFromDataRef(& capture->myMovie, newMovieActive | newMovieAsyncOK /* | newMovieIdleImportOK */,
188                                                                 & myResID, myDataRef, myDataRefType);
189         
190         // dispose of the data reference handle - we no longer need it
191         DisposeHandle (myDataRef);
192         
193         // if NewMovieFromDataRef failed, we already disposed the DataRef, so just return with an error
194         if (myErr != noErr)
195         {
196                 fprintf (stderr, "Couldn't create a NewMovieFromDataRef() - error is %d.\n",  myErr);
197                 return 0;
198         }
199         
200         // count the number of video 'frames' in the movie by stepping through all of the
201         // video 'interesting times', or in other words, the places where the movie displays
202         // a new video sample. The time between these interesting times is not necessarily constant.
203         {
204                 OSType      whichMediaType = VisualMediaCharacteristic;
205                 TimeValue   theTime        = -1;
206                 
207                 // find out movie start time
208                 GetMovieNextInterestingTime (capture->myMovie, short (nextTimeMediaSample + nextTimeEdgeOK), 
209                                              1, & whichMediaType, TimeValue (0), 0, & theTime, NULL);
210                 if (theTime == -1)
211                 {
212                         fprintf (stderr, "Couldn't inquire first frame time\n");
213                         return 0;
214                 }
215                 capture->movie_start_time  = theTime;
216                 capture->next_frame_time   = theTime;
217                 capture->next_frame_number = 0;
218                 
219                 // count all 'interesting times' of the movie
220                 capture->number_of_frames  = 0;
221                 while (theTime >= 0) 
222                 {
223                         GetMovieNextInterestingTime (capture->myMovie, short (nextTimeMediaSample), 
224                                                      1, & whichMediaType, theTime, 0, & theTime, NULL);
225                         capture->number_of_frames++;
226                 }
227         }
228         
229         // get the bounding rectangle of the movie
230         GetMoviesError ();
231         GetMovieBox (capture->myMovie, & myRect);
232         capture->size = cvSize (myRect.right - myRect.left, myRect.bottom - myRect.top);
233         
234         // create gworld for decompressed image
235         myErr = QTNewGWorld (& capture->myGWorld, k32ARGBPixelFormat /* k24BGRPixelFormat geht leider nicht */, 
236                              & myRect, nil, nil, 0);
237         OPENCV_ASSERT (myErr == noErr, "icvOpenFile_QT_Movie", "couldnt create QTNewGWorld() for output image");
238         SetMovieGWorld (capture->myMovie, capture->myGWorld, nil);
239         
240         // build IplImage header that will point to the PixMap of the Movie's GWorld later on
241         capture->image_rgb = cvCreateImageHeader (capture->size, IPL_DEPTH_8U, 4);
242         
243         // create IplImage that hold correctly formatted result
244         capture->image_bgr = cvCreateImage (capture->size, IPL_DEPTH_8U, 3);
245         
246         // okay, that's it - should we wait until the Movie is playable?
247         return 1;
248 }
249
250 /**
251  * dispose of QuickTime Movie and free memory buffers
252  * 
253  * @author Mark Asbach <asbach@ient.rwth-aachen.de>
254  * @date   2005-11-04
255  */
256 static int icvClose_QT_Movie (CvCapture_QT_Movie * capture)
257 {
258         OPENCV_ASSERT (capture,          "icvClose_QT_Movie", "'capture' is a NULL-pointer");
259         
260         // deallocate and free resources
261         if (capture->myMovie)
262         {
263                 cvReleaseImage       (& capture->image_bgr);
264                 cvReleaseImageHeader (& capture->image_rgb);
265                 DisposeGWorld        (capture->myGWorld);
266                 DisposeMovie         (capture->myMovie);
267         }
268         
269         // okay, that's it
270         return 1;
271 }
272
273 /**
274  * get a capture property
275  * 
276  * @author Mark Asbach <asbach@ient.rwth-aachen.de>
277  * @date   2005-11-05
278  */
279 static double icvGetProperty_QT_Movie (CvCapture_QT_Movie * capture, int property_id)
280 {
281         OPENCV_ASSERT (capture,                        "icvGetProperty_QT_Movie", "'capture' is a NULL-pointer");
282         OPENCV_ASSERT (capture->myMovie,               "icvGetProperty_QT_Movie", "invalid Movie handle");
283         OPENCV_ASSERT (capture->number_of_frames >  0, "icvGetProperty_QT_Movie", "movie has invalid number of frames");
284         OPENCV_ASSERT (capture->movie_start_time >= 0, "icvGetProperty_QT_Movie", "movie has invalid start time");
285         
286     // inquire desired property
287     switch (property_id)
288     {
289                 case CV_CAP_PROP_POS_FRAMES:
290                         return (capture->next_frame_number);
291                 
292                 case CV_CAP_PROP_POS_MSEC:
293                 case CV_CAP_PROP_POS_AVI_RATIO:
294                         {
295                                 TimeValue   position  = capture->next_frame_time - capture->movie_start_time;
296                                 
297                                 if (property_id == CV_CAP_PROP_POS_MSEC)
298                                 {
299                                         TimeScale   timescale = GetMovieTimeScale (capture->myMovie);
300                                         return (static_cast<double> (position) * 1000.0 / timescale);
301                                 }
302                                 else
303                                 {
304                                         TimeValue   duration  = GetMovieDuration  (capture->myMovie);
305                                         return (static_cast<double> (position) / duration);
306                                 }
307                         }
308                         break; // never reached
309                 
310                 case CV_CAP_PROP_FRAME_WIDTH:
311                         return static_cast<double> (capture->size.width);
312                 
313                 case CV_CAP_PROP_FRAME_HEIGHT:
314                         return static_cast<double> (capture->size.height);
315                 
316                 case CV_CAP_PROP_FPS:
317                         {
318                                 TimeValue   duration  = GetMovieDuration  (capture->myMovie);
319                                 TimeScale   timescale = GetMovieTimeScale (capture->myMovie);
320                                 
321                                 return (capture->number_of_frames / (static_cast<double> (duration) / timescale));
322                         }
323                 
324                 case CV_CAP_PROP_FRAME_COUNT:
325                         return static_cast<double> (capture->number_of_frames);
326                 
327                 case CV_CAP_PROP_FOURCC:  // not implemented
328                 case CV_CAP_PROP_FORMAT:  // not implemented
329                 case CV_CAP_PROP_MODE:    // not implemented
330                 default:
331                         // unhandled or unknown capture property
332                         OPENCV_ERROR (CV_StsBadArg, "icvSetProperty_QT_Movie", "unknown or unhandled property_id");
333                         return CV_StsBadArg;
334     }
335     
336     return 0;
337 }
338
339 /**
340  * set a capture property. With movie files, it is only possible to set the
341  * position (i.e. jump to a given time or frame number)
342  * 
343  * @author Mark Asbach <asbach@ient.rwth-aachen.de>
344  * @date   2005-11-05
345  */
346 static int icvSetProperty_QT_Movie (CvCapture_QT_Movie * capture, int property_id, double value)
347 {
348         OPENCV_ASSERT (capture,                        "icvSetProperty_QT_Movie", "'capture' is a NULL-pointer");
349         OPENCV_ASSERT (capture->myMovie,               "icvSetProperty_QT_Movie", "invalid Movie handle");
350         OPENCV_ASSERT (capture->number_of_frames >  0, "icvSetProperty_QT_Movie", "movie has invalid number of frames");
351         OPENCV_ASSERT (capture->movie_start_time >= 0, "icvSetProperty_QT_Movie", "movie has invalid start time");
352     
353     // inquire desired property
354         // 
355         // rework these three points to really work through 'interesting times'.
356         // with the current implementation, they result in wrong times or wrong frame numbers with content that 
357         // features varying frame durations
358     switch (property_id)
359     {
360                 case CV_CAP_PROP_POS_MSEC:
361                 case CV_CAP_PROP_POS_AVI_RATIO:
362                         {
363                                 TimeValue    destination;
364                                 OSType       myType     = VisualMediaCharacteristic;
365                                 OSErr        myErr      = noErr;
366
367                                 if (property_id == CV_CAP_PROP_POS_MSEC)
368                                 {
369                                         TimeScale  timescale   = GetMovieTimeScale      (capture->myMovie);
370                                                    destination = static_cast<TimeValue> (value / 1000.0 * timescale + capture->movie_start_time);
371                                 }
372                                 else
373                                 {
374                                         TimeValue  duration    = GetMovieDuration       (capture->myMovie);
375                                                    destination = static_cast<TimeValue> (value * duration + capture->movie_start_time);
376                                 }
377                                 
378                                 // really seek?
379                                 if (capture->next_frame_time == destination)
380                                         break;
381                                 
382                                 // seek into which direction?
383                                 if (capture->next_frame_time < destination)
384                                 {
385                                         while (capture->next_frame_time < destination)
386                                         {
387                                                 capture->next_frame_number++;
388                                                 GetMovieNextInterestingTime (capture->myMovie, nextTimeStep, 1, & myType, capture->next_frame_time,  
389                                                                              1, & capture->next_frame_time, NULL);
390                                                 myErr = GetMoviesError();
391                                                 if (myErr != noErr)
392                                                 {
393                                                         fprintf (stderr, "Couldn't go on to GetMovieNextInterestingTime() in icvGrabFrame_QT.\n");
394                                                         return 0;
395                                                 }
396                                         }
397                                 }
398                                 else
399                                 {
400                                         while (capture->next_frame_time > destination)
401                                         {
402                                                 capture->next_frame_number--;
403                                                 GetMovieNextInterestingTime (capture->myMovie, nextTimeStep, 1, & myType, capture->next_frame_time, 
404                                                                              -1, & capture->next_frame_time, NULL);
405                                                 myErr = GetMoviesError();
406                                                 if (myErr != noErr)
407                                                 {
408                                                         fprintf (stderr, "Couldn't go back to GetMovieNextInterestingTime() in icvGrabFrame_QT.\n");
409                                                         return 0;
410                                                 }
411                                         }
412                                 }
413                         }
414                         break;
415                 
416                 case CV_CAP_PROP_POS_FRAMES:
417                         {
418                                 TimeValue    destination = static_cast<TimeValue> (value);
419                                 short        direction   = (destination > capture->next_frame_number) ? 1 : -1;
420                                 OSType       myType      = VisualMediaCharacteristic;
421                                 OSErr        myErr       = noErr;
422                                 
423                                 while (destination != capture->next_frame_number)
424                                 {
425                                         capture->next_frame_number += direction;
426                                         GetMovieNextInterestingTime (capture->myMovie, nextTimeStep, 1, & myType, capture->next_frame_time, 
427                                                                                                  direction, & capture->next_frame_time, NULL);
428                                         myErr = GetMoviesError();
429                                         if (myErr != noErr)
430                                         {
431                                                 fprintf (stderr, "Couldn't step to desired frame number in icvGrabFrame_QT.\n");
432                                                 return 0;
433                                         }
434                                 }
435                         }
436                         break;
437                 
438                 default:
439                         // unhandled or unknown capture property
440                         OPENCV_ERROR (CV_StsBadArg, "icvSetProperty_QT_Movie", "unknown or unhandled property_id");
441                         return 0;
442         }
443         
444         // positive result means success
445         return 1;
446 }
447
448 /**
449  * the original meaning of this method is to acquire raw frame data for the next video
450  * frame but not decompress it. With the QuickTime video reader, this is reduced to
451  * advance to the current frame time.
452  * 
453  * @author Mark Asbach <asbach@ient.rwth-aachen.de>
454  * @date   2005-11-06
455  */
456 static int icvGrabFrame_QT_Movie (CvCapture_QT_Movie * capture)
457 {
458         OPENCV_ASSERT (capture,          "icvGrabFrame_QT_Movie", "'capture' is a NULL-pointer");
459         OPENCV_ASSERT (capture->myMovie, "icvGrabFrame_QT_Movie", "invalid Movie handle");
460         
461         TimeValue    myCurrTime;
462         OSType       myType     = VisualMediaCharacteristic;
463         OSErr        myErr      = noErr;
464         
465         
466         // jump to current video sample
467         SetMovieTimeValue (capture->myMovie, capture->next_frame_time);
468         myErr = GetMoviesError();
469         if (myErr != noErr)
470         {
471                 fprintf (stderr, "Couldn't SetMovieTimeValue() in icvGrabFrame_QT_Movie.\n");
472                 return  0;
473         }
474         
475         // where are we now?
476         myCurrTime = GetMovieTime (capture->myMovie, NULL);
477         
478         // increment counters
479         capture->next_frame_number++;
480         GetMovieNextInterestingTime (capture->myMovie, nextTimeStep, 1, & myType, myCurrTime, 1, & capture->next_frame_time, NULL);
481         myErr = GetMoviesError();
482         if (myErr != noErr)
483         {
484                 fprintf (stderr, "Couldn't GetMovieNextInterestingTime() in icvGrabFrame_QT_Movie.\n");
485                 return 0;
486         }
487         
488         // that's it
489     return 1;
490 }
491
492 /**
493  * render the current frame into an image buffer and convert to OpenCV IplImage
494  * buffer layout (BGR sampling)
495  * 
496  * @author Mark Asbach <asbach@ient.rwth-aachen.de>
497  * @date   2005-11-06
498  */
499 static const void * icvRetrieveFrame_QT_Movie (CvCapture_QT_Movie * capture)
500 {
501         OPENCV_ASSERT (capture,            "icvRetrieveFrame_QT_Movie", "'capture' is a NULL-pointer");
502         OPENCV_ASSERT (capture->myMovie,   "icvRetrieveFrame_QT_Movie", "invalid Movie handle");
503         OPENCV_ASSERT (capture->image_rgb, "icvRetrieveFrame_QT_Movie", "invalid source image");
504         OPENCV_ASSERT (capture->image_bgr, "icvRetrieveFrame_QT_Movie", "invalid destination image");
505         
506         PixMapHandle  myPixMapHandle = nil;
507         OSErr         myErr          = noErr;
508         
509         
510         // invalidates the movie's display state so that the Movie Toolbox 
511         // redraws the movie the next time we call MoviesTask
512         UpdateMovie (capture->myMovie);
513         myErr = GetMoviesError ();
514         if (myErr != noErr)
515         {
516                 fprintf (stderr, "Couldn't UpdateMovie() in icvRetrieveFrame_QT_Movie().\n");
517                 return 0;
518         }
519         
520         // service active movie (= redraw immediately)
521         MoviesTask (capture->myMovie, 0L);
522         myErr = GetMoviesError ();
523         if (myErr != noErr)
524         {
525                 fprintf (stderr, "MoviesTask() didn't succeed in icvRetrieveFrame_QT_Movie().\n");
526                 return 0;
527         }
528         
529         // update IplImage header that points to PixMap of the Movie's GWorld.
530         // unfortunately, cvCvtColor doesn't know ARGB, the QuickTime pixel format,
531         // so we pass a modfied address.
532         // ATTENTION: don't access the last pixel's alpha entry, it's inexistant
533         myPixMapHandle = GetGWorldPixMap (capture->myGWorld);
534         LockPixels (myPixMapHandle);
535         cvSetData (capture->image_rgb, GetPixBaseAddr (myPixMapHandle) + 1, GetPixRowBytes (myPixMapHandle));
536         
537         // covert RGB of GWorld to BGR
538         cvCvtColor (capture->image_rgb, capture->image_bgr, CV_RGBA2BGR);
539         
540         // allow QuickTime to access the buffer again
541         UnlockPixels (myPixMapHandle);
542         
543     // always return the same image pointer
544         return capture->image_bgr;
545 }
546
547
548 // ----------------------------------------------------------------------------------------
549 #pragma mark -
550 #pragma mark Capturing from Video Cameras
551
552 #ifdef USE_VDIG_VERSION
553
554         /// SequenceGrabber state structure for QuickTime
555         typedef struct CvCapture_QT_Cam_vdig
556         {
557                 CvCaptureVTable  * vtable; 
558
559                 ComponentInstance  grabber;
560                 short              channel;
561                 GWorldPtr          myGWorld;
562                 PixMapHandle       pixmap;
563                 
564                 CvSize             size;
565                 long               number_of_frames;
566                 
567                 IplImage         * image_rgb; // will point to the PixMap of myGWorld
568                 IplImage         * image_bgr; // will be returned by icvRetrieveFrame_QT()
569
570         } CvCapture_QT_Cam;
571
572 #else
573
574         typedef struct CvCapture_QT_Cam_barg
575         {
576                 CvCaptureVTable  * vtable; 
577
578                 SeqGrabComponent   grabber;
579                 SGChannel          channel;
580                 GWorldPtr          gworld;
581                 Rect               bounds;
582                 ImageSequence      sequence;
583
584                 volatile bool      got_frame;
585
586                 CvSize             size;
587                 IplImage         * image_rgb; // will point to the PixMap of myGWorld
588                 IplImage         * image_bgr; // will be returned by icvRetrieveFrame_QT()
589
590         } CvCapture_QT_Cam;
591
592 #endif
593
594 static       int         icvOpenCamera_QT        (CvCapture_QT_Cam * capture, const int index);
595 static       int         icvClose_QT_Cam         (CvCapture_QT_Cam * capture);
596 static       double      icvGetProperty_QT_Cam   (CvCapture_QT_Cam * capture, int property_id);
597 static       int         icvSetProperty_QT_Cam   (CvCapture_QT_Cam * capture, int property_id, double value);
598 static       int         icvGrabFrame_QT_Cam     (CvCapture_QT_Cam * capture);
599 static const void      * icvRetrieveFrame_QT_Cam (CvCapture_QT_Cam * capture);
600
601
602 static CvCaptureVTable capture_QT_Cam_vtable = 
603 {
604     6,
605     (CvCaptureCloseFunc)           icvClose_QT_Cam,
606     (CvCaptureGrabFrameFunc)       icvGrabFrame_QT_Cam,
607     (CvCaptureRetrieveFrameFunc)   icvRetrieveFrame_QT_Cam,
608     (CvCaptureGetPropertyFunc)     icvGetProperty_QT_Cam,
609     (CvCaptureSetPropertyFunc)     icvSetProperty_QT_Cam,
610     (CvCaptureGetDescriptionFunc)  0
611 };
612
613
614 /**
615  * Initialize memory structure and call method to open camera
616  *
617  * @author Mark Asbach <asbach@ient.rwth-aachen.de>
618  * @date 2006-01-29
619  */
620 CvCapture * cvCaptureFromCAM_QT (const int index)
621 {
622         if (! did_enter_movies)
623         {
624                 EnterMovies();
625                 did_enter_movies = 1;
626         }
627         
628     CvCapture_QT_Cam * capture = 0;
629         
630     if (index >= 0)
631     {
632         capture = (CvCapture_QT_Cam *) cvAlloc (sizeof (*capture));
633         memset (capture, 0, sizeof(*capture));
634                 
635         capture->vtable = &capture_QT_Cam_vtable;
636         
637         if (!icvOpenCamera_QT (capture, index))
638             cvReleaseCapture ((CvCapture**) & capture);
639     }
640         
641     return (CvCapture *) capture;
642 }
643
644 /// capture properties currently unimplemented for QuickTime camera interface
645 static double icvGetProperty_QT_Cam (CvCapture_QT_Cam * capture, int property_id)
646 {
647         assert (0);
648         return 0;
649 }
650
651 /// capture properties currently unimplemented for QuickTime camera interface
652 static int icvSetProperty_QT_Cam (CvCapture_QT_Cam * capture, int property_id, double value)
653 {
654         assert (0);
655         return 0;
656 }
657
658 #ifdef USE_VDIG_VERSION
659 #pragma mark Capturing using VDIG
660
661 /**
662  * Open a quicktime video grabber component. This could be an attached
663  * IEEE1394 camera, a web cam, an iSight or digitizer card / video converter.
664  * 
665  * @author Mark Asbach <asbach@ient.rwth-aachen.de>
666  * @date 2006-01-29
667  */
668 static int icvOpenCamera_QT (CvCapture_QT_Cam * capture, const int index)
669 {
670         OPENCV_ASSERT (capture,            "icvOpenCamera_QT", "'capture' is a NULL-pointer");
671         OPENCV_ASSERT (index >=0, "icvOpenCamera_QT", "camera index is negative");
672
673         ComponentDescription    component_description;
674         Component                               component = 0;
675         int                     number_of_inputs = 0;
676         Rect                    myRect;
677         ComponentResult                 result = noErr;
678         
679
680         // travers all components and count video digitizer channels
681         component_description.componentType         = videoDigitizerComponentType;
682         component_description.componentSubType      = 0L;
683         component_description.componentManufacturer = 0L;
684         component_description.componentFlags        = 0L;
685         component_description.componentFlagsMask    = 0L;
686         do
687         {
688                 // traverse component list
689                 component = FindNextComponent (component, & component_description);             
690                 
691                 // found a component?
692                 if (component)
693                 {
694                         // dump component name
695                         #ifndef NDEBUG
696                                 ComponentDescription  desc;
697                                 Handle                nameHandle = NewHandleClear (200);  
698                                 char                  nameBuffer [255];
699
700                                 result = GetComponentInfo (component, & desc, nameHandle, nil, nil);
701                                 OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt GetComponentInfo()");
702                                 OPENCV_ASSERT (*nameHandle, "icvOpenCamera_QT", "No name returned by GetComponentInfo()");
703                                 snprintf (nameBuffer, (**nameHandle) + 1, "%s", (char *) (* nameHandle + 1));
704                                 printf ("- Videodevice: %s\n", nameBuffer);
705                                 DisposeHandle (nameHandle);
706                         #endif
707                         
708                         // open component to count number of inputs
709                         capture->grabber = OpenComponent (component);
710                         if (capture->grabber)
711                         {
712                                 result = VDGetNumberOfInputs (capture->grabber, & capture->channel);
713                                 if (result != noErr)
714                                         fprintf (stderr, "Couldnt GetNumberOfInputs: %d\n", (int) result);
715                                 else
716                                 {
717                                         #ifndef NDEBUG
718                                                 printf ("  Number of inputs: %d\n", (int) capture->channel + 1);
719                                         #endif
720                                         
721                                         // add to overall number of inputs
722                                         number_of_inputs += capture->channel + 1;
723                                         
724                                         // did the user select an input that falls into this device's 
725                                         // range of inputs? Then leave the loop
726                                         if (number_of_inputs > index)
727                                         {
728                                                 // calculate relative channel index
729                                                 capture->channel = index - number_of_inputs + capture->channel + 1;
730                                                 OPENCV_ASSERT (capture->channel >= 0, "icvOpenCamera_QT", "negative channel number");
731                                                 
732                                                 // dump channel name
733                                                 #ifndef NDEBUG
734                                                         char  name[256];
735                                                         Str255  nameBuffer;
736                                                 
737                                                         result = VDGetInputName (capture->grabber, capture->channel, nameBuffer);
738                                                         OPENCV_ASSERT (result == noErr, "ictOpenCamera_QT", "couldnt GetInputName()");
739                                                         snprintf (name, *nameBuffer, "%s", (char *) (nameBuffer + 1));
740                                                         printf ("  Choosing input %d - %s\n", (int) capture->channel, name);
741                                                 #endif
742                                                 
743                                                 // leave the loop
744                                                 break;
745                                         }
746                                 }
747                                 
748                                 // obviously no inputs of this device/component were needed
749                                 CloseComponent (capture->grabber);
750                         }
751                 }
752         }
753         while (component);
754         
755         // did we find the desired input?
756         if (! component)
757         {
758                 fprintf(stderr, "Not enough inputs available - can't choose input %d\n", index);
759                 return 0;
760         }
761         
762         // -- Okay now, we selected the digitizer input, lets set up digitizer destination --
763         
764         ClearMoviesStickyError();
765         
766         // Select the desired input
767         result = VDSetInput (capture->grabber, capture->channel);
768         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt select video digitizer input");
769                                                                                 
770         // get the bounding rectangle of the video digitizer
771         result = VDGetActiveSrcRect (capture->grabber, capture->channel, & myRect);
772         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt create VDGetActiveSrcRect from digitizer");
773         myRect.right = 640; myRect.bottom = 480;
774         capture->size = cvSize (myRect.right - myRect.left, myRect.bottom - myRect.top);
775         printf ("Source rect is %d, %d -- %d, %d\n", (int) myRect.left, (int) myRect.top, (int) myRect.right, (int) myRect.bottom);
776         
777         // create offscreen GWorld
778         result = QTNewGWorld (& capture->myGWorld, k32ARGBPixelFormat, & myRect, nil, nil, 0);
779         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt create QTNewGWorld() for output image");
780         
781         // get pixmap
782         capture->pixmap = GetGWorldPixMap (capture->myGWorld);
783         result = GetMoviesError ();
784         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt get pixmap");
785
786         // set digitizer rect
787         result = VDSetDigitizerRect (capture->grabber, & myRect);
788         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt create VDGetActiveSrcRect from digitizer");
789         
790         // set destination of digitized input
791         result = VDSetPlayThruDestination (capture->grabber, capture->pixmap, & myRect, nil, nil);
792         printf ("QuickTime error: %d\n", (int) result);
793         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set video destination");
794         
795         // get destination of digitized images
796         result = VDGetPlayThruDestination (capture->grabber, & capture->pixmap, nil, nil, nil);
797         printf ("QuickTime error: %d\n", (int) result);
798         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt get video destination");
799         OPENCV_ASSERT (capture->pixmap != nil, "icvOpenCamera_QT", "empty set video destination");
800         
801         // get the bounding rectangle of the video digitizer
802         GetPixBounds (capture->pixmap, & myRect);
803         capture->size = cvSize (myRect.right - myRect.left, myRect.bottom - myRect.top);
804
805         // build IplImage header that will point to the PixMap of the Movie's GWorld later on
806         capture->image_rgb = cvCreateImageHeader (capture->size, IPL_DEPTH_8U, 4);
807         OPENCV_ASSERT (capture->image_rgb, "icvOpenCamera_QT", "couldnt create image header");
808         
809         // create IplImage that hold correctly formatted result
810         capture->image_bgr = cvCreateImage (capture->size, IPL_DEPTH_8U, 3);
811         OPENCV_ASSERT (capture->image_bgr, "icvOpenCamera_QT", "couldnt create image");
812         
813         // notify digitizer component, that we well be starting grabbing soon
814         result = VDCaptureStateChanging (capture->grabber, vdFlagCaptureIsForRecord | vdFlagCaptureStarting | vdFlagCaptureLowLatency);
815         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set capture state");
816         
817         
818         // yeah, we did it
819         return 1;
820 }
821
822 static int icvClose_QT_Cam (CvCapture_QT_Cam * capture)
823 {
824         OPENCV_ASSERT (capture, "icvClose_QT_Cam", "'capture' is a NULL-pointer");
825         
826         ComponentResult result = noErr;
827
828         // notify digitizer component, that we well be stopping grabbing soon
829         result = VDCaptureStateChanging (capture->grabber, vdFlagCaptureStopping);
830         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set capture state");
831         
832         // release memory
833         cvReleaseImage       (& capture->image_bgr);
834         cvReleaseImageHeader (& capture->image_rgb);
835         DisposeGWorld        (capture->myGWorld);
836         CloseComponent       (capture->grabber);
837         
838         // sucessful
839         return 1;
840 }
841
842 static int icvGrabFrame_QT_Cam (CvCapture_QT_Cam * capture)
843 {
844         OPENCV_ASSERT (capture,          "icvGrabFrame_QT_Cam", "'capture' is a NULL-pointer");
845         OPENCV_ASSERT (capture->grabber, "icvGrabFrame_QT_Cam", "'grabber' is a NULL-pointer");
846         
847         ComponentResult result = noErr;
848         
849         // grab one frame
850         result = VDGrabOneFrame (capture->grabber);
851         if (result != noErr)
852         {
853                 fprintf (stderr, "VDGrabOneFrame failed\n");
854                 return 0;
855         }
856         
857         // successful
858         return 1;
859 }
860
861 static const void * icvRetrieveFrame_QT_Cam (CvCapture_QT_Cam * capture)
862 {
863         OPENCV_ASSERT (capture, "icvRetrieveFrame_QT_Cam", "'capture' is a NULL-pointer");
864         
865         PixMapHandle  myPixMapHandle = nil;
866
867         // update IplImage header that points to PixMap of the Movie's GWorld.
868         // unfortunately, cvCvtColor doesn't know ARGB, the QuickTime pixel format,
869         // so we pass a modfied address.
870         // ATTENTION: don't access the last pixel's alpha entry, it's inexistant
871         //myPixMapHandle = GetGWorldPixMap (capture->myGWorld);
872         myPixMapHandle = capture->pixmap;
873         LockPixels (myPixMapHandle);
874         cvSetData (capture->image_rgb, GetPixBaseAddr (myPixMapHandle) + 1, GetPixRowBytes (myPixMapHandle));
875         
876         // covert RGB of GWorld to BGR
877         cvCvtColor (capture->image_rgb, capture->image_bgr, CV_RGBA2BGR);
878         
879         // allow QuickTime to access the buffer again
880         UnlockPixels (myPixMapHandle);
881         
882     // always return the same image pointer
883         return capture->image_bgr;
884 }
885
886 #else 
887 #pragma mark Capturing using Sequence Grabber
888
889 static OSErr icvDataProc_QT_Cam (SGChannel channel, Ptr raw_data, long len, long *, long, TimeValue, short, long refCon)
890 {
891         CvCapture_QT_Cam  * capture = (CvCapture_QT_Cam *) refCon;
892         CodecFlags          ignore;
893         ComponentResult     err     = noErr;
894         
895         
896         // we need valid pointers
897         OPENCV_ASSERT (capture,          "icvDataProc_QT_Cam", "'capture' is a NULL-pointer");
898         OPENCV_ASSERT (capture->gworld,  "icvDataProc_QT_Cam", "'gworld' is a NULL-pointer");
899         OPENCV_ASSERT (raw_data,         "icvDataProc_QT_Cam", "'raw_data' is a NULL-pointer");
900         
901         // create a decompression sequence the first time
902         if (capture->sequence == 0)
903         {
904                 ImageDescriptionHandle   description = (ImageDescriptionHandle) NewHandle(0);
905                 
906                 // we need a decompression sequence that fits the raw data coming from the camera
907                 err = SGGetChannelSampleDescription (channel, (Handle) description);
908                 OPENCV_ASSERT (err == noErr, "icvDataProc_QT_Cam", "couldnt get channel sample description");
909                 err = DecompressSequenceBegin (&capture->sequence, description, capture->gworld, 0, &capture->bounds, 
910                                                    nil, srcCopy, nil, 0, codecNormalQuality, bestSpeedCodec);
911                 OPENCV_ASSERT (err == noErr, "icvDataProc_QT_Cam", "couldnt begin decompression sequence");
912
913                 DisposeHandle ((Handle) description);
914         }
915         
916         // okay, we have a decompression sequence -> decompress!
917         err = DecompressSequenceFrameS (capture->sequence, raw_data, len, 0, &ignore, nil);
918         if (err != noErr)
919         {
920                 fprintf (stderr, "icvDataProc_QT_Cam: couldn't decompress frame - %d\n", (int) err);
921                 return err;
922         }
923         
924         // check if we dropped a frame
925         #ifndef NDEBUG
926                 if (capture->got_frame)
927                         fprintf (stderr, "icvDataProc_QT_Cam: frame was dropped\n");
928         #endif
929         
930         // everything worked as expected
931         capture->got_frame = true;
932         return noErr;
933 }
934
935
936 static int icvOpenCamera_QT (CvCapture_QT_Cam * capture, const int index)
937 {
938         OPENCV_ASSERT (capture,    "icvOpenCamera_QT", "'capture' is a NULL-pointer");
939         OPENCV_ASSERT (index >= 0, "icvOpenCamera_QT", "camera index is negative");
940         
941         PixMapHandle  pixmap       = nil;
942         OSErr         result       = noErr;
943         
944         // open sequence grabber component
945         capture->grabber = OpenDefaultComponent (SeqGrabComponentType, 0);
946         OPENCV_ASSERT (capture->grabber, "icvOpenCamera_QT", "couldnt create image");
947         
948         // initialize sequence grabber component
949         result = SGInitialize (capture->grabber);
950         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt initialize sequence grabber");
951         result = SGSetDataRef (capture->grabber, 0, 0, seqGrabDontMakeMovie);
952         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set data reference of sequence grabber");
953         
954         // set up video channel
955         result = SGNewChannel (capture->grabber, VideoMediaType, & (capture->channel));
956         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt create new video channel");
957         
958         // query natural camera resolution -- this will be wrong, but will be an upper
959         // bound on the actual resolution -- the actual resolution is set below
960         // after starting the frame grabber
961         result = SGGetSrcVideoBounds (capture->channel, & (capture->bounds));
962         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set video channel bounds");
963
964         // create offscreen GWorld
965         result = QTNewGWorld (& (capture->gworld), k32ARGBPixelFormat, & (capture->bounds), 0, 0, 0);
966         result = SGSetGWorld (capture->grabber, capture->gworld, 0);
967         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set GWorld for sequence grabber");
968         result = SGSetChannelBounds (capture->channel, & (capture->bounds));
969         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set video channel bounds");
970         result = SGSetChannelUsage (capture->channel, seqGrabRecord);
971         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set channel usage");
972
973     // start recording so we can size
974         result = SGStartRecord (capture->grabber);
975         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt start recording");
976
977         // don't know *actual* resolution until now
978         ImageDescriptionHandle imageDesc = (ImageDescriptionHandle)NewHandle(0);
979         result = SGGetChannelSampleDescription(capture->channel, (Handle)imageDesc);
980         OPENCV_ASSERT( result == noErr, "icvOpenCamera_QT", "couldn't get image size");
981         capture->bounds.right = (**imageDesc).width;
982         capture->bounds.bottom = (**imageDesc).height;
983         DisposeHandle ((Handle) imageDesc);
984
985         // stop grabber so that we can reset the parameters to the right size
986         result = SGStop (capture->grabber);
987         OPENCV_ASSERT (result == noErr, "icveClose_QT_Cam", "couldnt stop recording");
988
989         // reset GWorld to correct image size
990         GWorldPtr tmpgworld;
991         result = QTNewGWorld( &tmpgworld, k32ARGBPixelFormat, &(capture->bounds), 0, 0, 0);
992         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt create offscreen GWorld");
993         result = SGSetGWorld( capture->grabber, tmpgworld, 0);
994         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set GWorld for sequence grabber");
995         DisposeGWorld( capture->gworld );
996         capture->gworld = tmpgworld;
997
998         result = SGSetChannelBounds (capture->channel, & (capture->bounds));
999         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set video channel bounds");        
1000
1001         // allocate images
1002         capture->size = cvSize (capture->bounds.right - capture->bounds.left, capture->bounds.bottom - capture->bounds.top);
1003         
1004         // build IplImage header that points to the PixMap of the Movie's GWorld.
1005         // unfortunately, cvCvtColor doesn't know ARGB, the QuickTime pixel format,
1006         // so we shift the base address by one byte.
1007         // ATTENTION: don't access the last pixel's alpha entry, it's inexistant
1008         capture->image_rgb = cvCreateImageHeader (capture->size, IPL_DEPTH_8U, 4);
1009         OPENCV_ASSERT (capture->image_rgb, "icvOpenCamera_QT", "couldnt create image header");
1010         pixmap = GetGWorldPixMap (capture->gworld);
1011         OPENCV_ASSERT (pixmap, "icvOpenCamera_QT", "didn't get GWorld PixMap handle");
1012         LockPixels (pixmap);
1013         cvSetData (capture->image_rgb, GetPixBaseAddr (pixmap) + 1, GetPixRowBytes (pixmap));
1014         
1015         // create IplImage that hold correctly formatted result
1016         capture->image_bgr = cvCreateImage (capture->size, IPL_DEPTH_8U, 3);
1017         OPENCV_ASSERT (capture->image_bgr, "icvOpenCamera_QT", "couldnt create image");
1018
1019         
1020         // tell the sequence grabber to invoke our data proc
1021         result = SGSetDataProc (capture->grabber, NewSGDataUPP (icvDataProc_QT_Cam), (long) capture);
1022         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set data proc");
1023         
1024         // start recording
1025         result = SGStartRecord (capture->grabber);
1026         OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt start recording");
1027
1028         return 1;
1029 }
1030
1031
1032 static int icvClose_QT_Cam (CvCapture_QT_Cam * capture)
1033 {
1034         OPENCV_ASSERT (capture, "icvClose_QT_Cam", "'capture' is a NULL-pointer");
1035         
1036         OSErr  result = noErr;
1037         
1038         
1039         // stop recording
1040         result = SGStop (capture->grabber);
1041         OPENCV_ASSERT (result == noErr, "icveClose_QT_Cam", "couldnt stop recording");
1042
1043         // close sequence grabber component
1044         result = CloseComponent (capture->grabber);
1045         OPENCV_ASSERT (result == noErr, "icveClose_QT_Cam", "couldnt close sequence grabber component");
1046         
1047         // end decompression sequence
1048         CDSequenceEnd (capture->sequence);
1049         
1050         // free memory
1051         cvReleaseImage (& capture->image_bgr);
1052         cvReleaseImageHeader (& capture->image_rgb);
1053         DisposeGWorld (capture->gworld); 
1054         
1055         // sucessful
1056         return 1;
1057 }
1058
1059 static int icvGrabFrame_QT_Cam (CvCapture_QT_Cam * capture)
1060 {
1061         OPENCV_ASSERT (capture,          "icvGrabFrame_QT_Cam", "'capture' is a NULL-pointer");
1062         OPENCV_ASSERT (capture->grabber, "icvGrabFrame_QT_Cam", "'grabber' is a NULL-pointer");
1063
1064         ComponentResult result = noErr;
1065
1066
1067         // grab one frame
1068         result = SGIdle (capture->grabber);
1069         if (result != noErr)
1070         {
1071                 fprintf (stderr, "SGIdle failed in icvGrabFrame_QT_Cam with error %d\n", (int) result);
1072                 return 0;
1073         }
1074         
1075         // successful
1076         return 1;
1077 }
1078
1079 static const void * icvRetrieveFrame_QT_Cam (CvCapture_QT_Cam * capture)
1080 {
1081         OPENCV_ASSERT (capture,            "icvRetrieveFrame_QT_Cam", "'capture' is a NULL-pointer");
1082         OPENCV_ASSERT (capture->image_rgb, "icvRetrieveFrame_QT_Cam", "invalid source image");
1083         OPENCV_ASSERT (capture->image_bgr, "icvRetrieveFrame_QT_Cam", "invalid destination image");
1084         
1085         OSErr         myErr          = noErr;
1086         
1087
1088         // service active sequence grabbers (= redraw immediately)
1089         while (! capture->got_frame)
1090         {
1091                 myErr = SGIdle (capture->grabber);
1092                 if (myErr != noErr)
1093                 {
1094                         fprintf (stderr, "SGIdle() didn't succeed in icvRetrieveFrame_QT_Cam().\n");
1095                         return 0;
1096                 }
1097         }
1098         
1099         // covert RGB of GWorld to BGR
1100         cvCvtColor (capture->image_rgb, capture->image_bgr, CV_RGBA2BGR);
1101         
1102         // reset grabbing status
1103         capture->got_frame = false;
1104         
1105     // always return the same image pointer
1106         return capture->image_bgr;
1107 }
1108
1109 #endif
1110
1111
1112 typedef struct CvVideoWriter_QT {
1113     DataHandler data_handler;
1114     Movie movie;
1115     Track track;
1116     Media video;
1117
1118     ICMCompressionSessionRef compression_session_ref;
1119
1120     TimeValue duration_per_sample;
1121 } CvVideoWriter_QT;
1122
1123 static TimeScale const TIME_SCALE = 600;
1124
1125 OSStatus icvEncodedFrameOutputCallback(
1126     void* writer,
1127     ICMCompressionSessionRef compression_session_ref,
1128     OSStatus error,
1129     ICMEncodedFrameRef encoded_frame_ref,
1130     void* reserved
1131 );
1132
1133 void icvSourceTrackingCallback(
1134     void *source_tracking_ref_con,
1135     ICMSourceTrackingFlags source_tracking_flags,
1136     void *source_frame_ref_con,
1137     void *reserved
1138 );
1139
1140 CV_IMPL CvVideoWriter* cvCreateVideoWriter(
1141     const char * filename,
1142     int fourcc,
1143     double fps,
1144     CvSize frame_size,
1145     int is_color
1146 ) {
1147     CV_FUNCNAME( "cvCreateVideoWriter" );
1148
1149     CvVideoWriter_QT* video_writer =
1150         static_cast<CvVideoWriter_QT*>( cvAlloc( sizeof( CvVideoWriter_QT ) ) );
1151     memset( video_writer, 0, sizeof( CvVideoWriter_QT ) );
1152
1153     Handle data_ref = NULL;
1154     OSType data_ref_type;
1155     DataHandler data_handler = NULL;
1156     Movie movie = NULL;
1157     ICMCompressionSessionOptionsRef options_ref = NULL;
1158     ICMCompressionSessionRef compression_session_ref = NULL;
1159
1160     OSErr err = noErr;
1161
1162     __BEGIN__
1163
1164     // validate input arguments
1165     if ( filename == NULL ) {
1166         CV_ERROR( CV_StsBadArg, "Video file name must not be NULL" );
1167     }
1168     if ( fps <= 0.0 ) {
1169         CV_ERROR( CV_StsBadArg, "FPS must be larger than 0.0" );
1170     }
1171     if ( ( frame_size.width <= 0 ) || ( frame_size.height <= 0 ) ) {
1172         CV_ERROR( CV_StsBadArg,
1173             "Frame width and height must be larger than 0" );
1174     }
1175
1176     // initialize QuickTime
1177     if ( !did_enter_movies ) {
1178         err = EnterMovies();
1179         if ( err != noErr ) {
1180             CV_ERROR( CV_StsInternal, "Unable to initialize QuickTime" );
1181         }
1182         did_enter_movies = 1;
1183     }
1184
1185     // convert the file name into a data reference
1186     CFStringRef out_path = CFStringCreateWithCString( kCFAllocatorDefault,
1187         filename, kCFStringEncodingISOLatin1 );
1188     CV_ASSERT( out_path != nil );
1189     err = QTNewDataReferenceFromFullPathCFString( out_path, kQTPOSIXPathStyle,
1190         0, &data_ref, &data_ref_type );
1191     CFRelease( out_path );
1192     if ( err != noErr ) {
1193         CV_ERROR( CV_StsInternal,
1194             "Cannot create data reference from file name" );
1195     }
1196
1197     // create a new movie on disk
1198     err = CreateMovieStorage( data_ref, data_ref_type, 'TVOD',
1199         smCurrentScript, newMovieActive, &data_handler, &movie );
1200
1201     if ( err != noErr ) {
1202         CV_ERROR( CV_StsInternal, "Cannot create movie storage" );
1203     }
1204
1205     // create a track with video
1206     Track video_track =
1207         NewMovieTrack(
1208             movie,
1209             FixRatio( frame_size.width, 1 ),
1210             FixRatio( frame_size.height, 1 ),
1211             kNoVolume
1212         );
1213     err = GetMoviesError();
1214     if ( err != noErr ) {
1215         CV_ERROR( CV_StsInternal, "Cannot create video track" );
1216     }
1217     Media video =
1218         NewTrackMedia( video_track, VideoMediaType, TIME_SCALE, nil, 0 );
1219     err = GetMoviesError();
1220     if ( err != noErr ) {
1221         CV_ERROR( CV_StsInternal, "Cannot create video media" );
1222     }
1223
1224     CodecType codecType;
1225     switch ( fourcc ) {
1226         case CV_FOURCC( 'D', 'I', 'B', ' ' ):
1227             codecType = kRawCodecType;
1228             break;
1229         default:
1230             codecType = kRawCodecType;
1231             break;
1232     }
1233
1234     // start a compression session
1235     err = ICMCompressionSessionOptionsCreate( kCFAllocatorDefault,
1236         &options_ref );
1237     if ( err != noErr ) {
1238         CV_ERROR( CV_StsInternal, "Cannot create compression session options" );
1239     }
1240     err = ICMCompressionSessionOptionsSetAllowTemporalCompression( options_ref,
1241         true );
1242     if ( err != noErr) {
1243         CV_ERROR( CV_StsInternal, "Cannot enable temporal compression" );
1244     }
1245     err = ICMCompressionSessionOptionsSetAllowFrameReordering( options_ref,
1246         true );
1247     if ( err != noErr) {
1248         CV_ERROR( CV_StsInternal, "Cannot enable frame reordering" );
1249     }
1250
1251     ICMEncodedFrameOutputRecord encoded_frame_output_record;
1252     encoded_frame_output_record.encodedFrameOutputCallback =
1253         icvEncodedFrameOutputCallback;
1254     encoded_frame_output_record.encodedFrameOutputRefCon =
1255         static_cast<void*>( video_writer );
1256     encoded_frame_output_record.frameDataAllocator = NULL;
1257     
1258     err = ICMCompressionSessionCreate( kCFAllocatorDefault, frame_size.width,
1259         frame_size.height, codecType, TIME_SCALE, options_ref,
1260         NULL /*source_pixel_buffer_attributes*/, &encoded_frame_output_record,
1261         &compression_session_ref );
1262     ICMCompressionSessionOptionsRelease( options_ref );
1263     if ( err != noErr ) {
1264         CV_ERROR( CV_StsInternal, "Cannot create compression session" );
1265     }
1266
1267     err = BeginMediaEdits( video );
1268     if ( err != noErr ) {
1269         CV_ERROR( CV_StsInternal, "Cannot begin media edits" );
1270     }
1271
1272     // fill in the video writer structure
1273     video_writer->data_handler = data_handler;
1274     video_writer->movie = movie;
1275     video_writer->track = video_track;
1276     video_writer->video = video;
1277     video_writer->compression_session_ref = compression_session_ref;
1278     video_writer->duration_per_sample =
1279         static_cast<TimeValue>( static_cast<double>( TIME_SCALE ) / fps );
1280
1281     __END__
1282
1283     // clean up in case of error (unless error processing mode is
1284     // CV_ErrModeLeaf)
1285     if ( err != noErr ) {
1286         if ( options_ref != NULL ) {
1287             ICMCompressionSessionOptionsRelease( options_ref );
1288         }
1289         if ( compression_session_ref != NULL ) {
1290             ICMCompressionSessionRelease( compression_session_ref );
1291         }
1292         if ( data_handler != NULL ) {
1293             CloseMovieStorage( data_handler );
1294         }
1295         if ( movie != NULL ) {
1296             DisposeMovie( movie );
1297         }
1298         if ( data_ref != NULL ) {
1299             DeleteMovieStorage( data_ref, data_ref_type );
1300             DisposeHandle( data_ref );
1301         }
1302         cvFree( reinterpret_cast<void**>( &video_writer ) );
1303         video_writer = NULL;
1304     }
1305
1306     return reinterpret_cast<CvVideoWriter*>( video_writer );
1307 }
1308
1309 CV_IMPL int cvWriteFrame(
1310     CvVideoWriter * writer,
1311     const IplImage * image
1312 ) {
1313     CvVideoWriter_QT* video_writer =
1314         reinterpret_cast<CvVideoWriter_QT*>( writer );
1315
1316     CVPixelBufferRef pixel_buffer_ref = NULL;
1317     CVReturn retval =
1318         CVPixelBufferCreate(
1319             kCFAllocatorDefault,
1320             image->width, image->height, k24RGBPixelFormat,
1321             NULL /* pixel_buffer_attributes */,
1322             &pixel_buffer_ref
1323         );
1324
1325     // convert BGR IPL image to RGB pixel buffer
1326     IplImage* image_rgb =
1327         cvCreateImageHeader(
1328             cvSize( image->width, image->height ),
1329             IPL_DEPTH_8U,
1330             3
1331         );
1332
1333     retval = CVPixelBufferLockBaseAddress( pixel_buffer_ref, 0 );
1334
1335     void* base_address = CVPixelBufferGetBaseAddress( pixel_buffer_ref );
1336     size_t bytes_per_row = CVPixelBufferGetBytesPerRow( pixel_buffer_ref );
1337     cvSetData( image_rgb, base_address, bytes_per_row );
1338
1339     cvConvertImage( image, image_rgb, CV_CVTIMG_SWAP_RB );
1340
1341     retval = CVPixelBufferUnlockBaseAddress( pixel_buffer_ref, 0 );
1342
1343     cvReleaseImageHeader( &image_rgb );
1344
1345     ICMSourceTrackingCallbackRecord source_tracking_callback_record;
1346     source_tracking_callback_record.sourceTrackingCallback =
1347         icvSourceTrackingCallback;
1348     source_tracking_callback_record.sourceTrackingRefCon = NULL;
1349
1350     OSStatus status =
1351         ICMCompressionSessionEncodeFrame(
1352             video_writer->compression_session_ref,
1353             pixel_buffer_ref,
1354             0,
1355             video_writer->duration_per_sample,
1356             kICMValidTime_DisplayDurationIsValid,
1357             NULL,
1358             &source_tracking_callback_record,
1359             static_cast<void*>( &pixel_buffer_ref )
1360         );
1361
1362     return 0;
1363 }
1364
1365 CV_IMPL void cvReleaseVideoWriter( CvVideoWriter ** writer ) {
1366     if ( ( writer != NULL ) && ( *writer != NULL ) ) {
1367         CvVideoWriter_QT* video_writer =
1368             reinterpret_cast<CvVideoWriter_QT*>( *writer );
1369
1370         // force compression session to complete encoding of outstanding source
1371         // frames
1372         ICMCompressionSessionCompleteFrames(
1373             video_writer->compression_session_ref, TRUE, 0, 0
1374         );
1375
1376         EndMediaEdits( video_writer->video );
1377
1378         ICMCompressionSessionRelease( video_writer->compression_session_ref );
1379
1380         InsertMediaIntoTrack(
1381             video_writer->track,
1382             0,
1383             0,
1384             GetMediaDuration( video_writer->video ),
1385             FixRatio( 1, 1 )
1386         );
1387
1388         UpdateMovieInStorage( video_writer->movie, video_writer->data_handler );
1389
1390         CloseMovieStorage( video_writer->data_handler );
1391
1392 /*
1393         // export to AVI
1394         Handle data_ref;
1395         OSType data_ref_type;
1396         QTNewDataReferenceFromFullPathCFString(
1397             CFSTR( "/Users/seibert/Desktop/test.avi" ), kQTPOSIXPathStyle, 0,
1398             &data_ref, &data_ref_type
1399         );
1400
1401         ConvertMovieToDataRef( video_writer->movie, NULL, data_ref,
1402             data_ref_type, kQTFileTypeAVI, 'TVOD', 0, NULL );
1403
1404         DisposeHandle( data_ref );
1405 */
1406
1407         DisposeMovie( video_writer->movie );
1408
1409         cvFree( reinterpret_cast<void**>( &video_writer ) );
1410         *writer = NULL;
1411     }
1412 }
1413
1414 OSStatus icvEncodedFrameOutputCallback(
1415     void* writer,
1416     ICMCompressionSessionRef compression_session_ref,
1417     OSStatus error,
1418     ICMEncodedFrameRef encoded_frame_ref,
1419     void* reserved
1420 ) {
1421     CvVideoWriter_QT* video_writer = static_cast<CvVideoWriter_QT*>( writer );
1422
1423     OSStatus err = AddMediaSampleFromEncodedFrame( video_writer->video,
1424         encoded_frame_ref, NULL );
1425
1426     return err;
1427 }
1428
1429 void icvSourceTrackingCallback(
1430     void *source_tracking_ref_con,
1431     ICMSourceTrackingFlags source_tracking_flags,
1432     void *source_frame_ref_con,
1433     void *reserved
1434 ) {
1435     if ( source_tracking_flags & kICMSourceTracking_ReleasedPixelBuffer ) {
1436         CVPixelBufferRelease(
1437             *static_cast<CVPixelBufferRef*>( source_frame_ref_con )
1438         );
1439     }
1440 }