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