00001 /*!@file Devices/QuickTimeGrabber.C Grab frames (e.g. from a camera) using QuickTime's SequenceGrabber APIs */ 00002 00003 // //////////////////////////////////////////////////////////////////// // 00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005 // 00005 // by the University of Southern California (USC) and the iLab at USC. // 00006 // See http://iLab.usc.edu for information about this project. // 00007 // //////////////////////////////////////////////////////////////////// // 00008 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00009 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00010 // in Visual Environments, and Applications'' by Christof Koch and // 00011 // Laurent Itti, California Institute of Technology, 2001 (patent // 00012 // pending; application number 09/912,225 filed July 23, 2001; see // 00013 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00014 // //////////////////////////////////////////////////////////////////// // 00015 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00016 // // 00017 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00018 // redistribute it and/or modify it under the terms of the GNU General // 00019 // Public License as published by the Free Software Foundation; either // 00020 // version 2 of the License, or (at your option) any later version. // 00021 // // 00022 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00023 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00024 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00025 // PURPOSE. See the GNU General Public License for more details. // 00026 // // 00027 // You should have received a copy of the GNU General Public License // 00028 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00029 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00030 // Boston, MA 02111-1307 USA. // 00031 // //////////////////////////////////////////////////////////////////// // 00032 // 00033 // Primary maintainer for this file: Rob Peters <rjpeters at usc dot edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Devices/QuickTimeGrabber.C $ 00035 // $Id: QuickTimeGrabber.C 9341 2008-02-28 00:13:24Z rjpeters $ 00036 // 00037 00038 // Much of the source code in this file is derived from code in the 00039 // "BrideOfMungGrab" sample program, Copyright 2000-2005 Apple 00040 // Computer, Inc., in which the included license agreement allows 00041 // redistribution with or without modifications as long as the Apple 00042 // Computer name is not used to endorse the modified product. 00043 00044 #ifndef DEVICES_QUICKTIMEGRABBER_C_DEFINED 00045 #define DEVICES_QUICKTIMEGRABBER_C_DEFINED 00046 00047 #define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_3 00048 00049 #include "Devices/QuickTimeGrabber.H" 00050 00051 #include "Devices/DeviceOpts.H" 00052 #include "Raster/GenericFrame.H" 00053 #include "Util/Janitor.H" 00054 #include "Util/log.H" 00055 #include "Util/sformat.H" 00056 00057 #ifdef HAVE_QUICKTIME_QUICKTIME_H 00058 #include <Carbon/Carbon.h> 00059 #include <QuickTime/QuickTime.h> 00060 #endif 00061 00062 #include <unistd.h> 00063 00064 #ifdef HAVE_QUICKTIME_QUICKTIME_H 00065 00066 // ###################################################################### 00067 static void setVideoChannelBounds(SGChannel videoChannel, 00068 const Rect* scaledSourceBounds, 00069 const Rect* scaledVideoBounds) 00070 { 00071 // Notes: see Q&A 1250 00072 00073 // calculate the matrix to transform the 00074 // scaledSourceBounds to the source bounds 00075 Rect sourceBounds; 00076 SGGetSrcVideoBounds(videoChannel, &sourceBounds); 00077 00078 MatrixRecord scaledSourceBoundsToSourceBounds; 00079 RectMatrix(&scaledSourceBoundsToSourceBounds, 00080 scaledSourceBounds, &sourceBounds); 00081 00082 // apply the same transform to the 00083 // scaledVideoBounds to get the video bounds 00084 Rect videoBounds = *scaledVideoBounds; 00085 TransformRect(&scaledSourceBoundsToSourceBounds, &videoBounds, 0); 00086 00087 if (noErr != SGSetVideoRect(videoChannel, &videoBounds)) 00088 { 00089 // some video digitizers may only be able to capture full frame 00090 // and will return qtParamErr or possibly digiUnimpErr if they 00091 // can't handle working with less than full frame 00092 SGSetVideoRect(videoChannel, &sourceBounds); 00093 } 00094 00095 // the channel bounds is scaledVideoBounds offset to (0, 0) 00096 Rect channelBounds = *scaledVideoBounds; 00097 OffsetRect(&channelBounds, -channelBounds.left, -channelBounds.top); 00098 00099 // Note: SGSetChannelBounds merely allows the client to specify it's 00100 // preferred bounds. The actual bounds returned by the vDig in the 00101 // image description may be different 00102 if (noErr != SGSetChannelBounds(videoChannel, &channelBounds)) 00103 LFATAL("SGSetChannelBounds() failed"); 00104 } 00105 00106 // ###################################################################### 00107 struct QuickTimeGrabber::Impl 00108 { 00109 Impl(const Dims& dims); 00110 00111 ~Impl(); 00112 00113 private: 00114 class SGChannelHolder 00115 { 00116 public: 00117 SGChannelHolder(SeqGrabComponent* owner) : it(0), itsOwner(owner) {} 00118 ~SGChannelHolder() { SGDisposeChannel(*itsOwner, it); } 00119 SGChannel it; 00120 private: 00121 SGChannelHolder(const SGChannelHolder&); // not implemented 00122 SGChannelHolder& operator=(const SGChannelHolder&); // not implemented 00123 SeqGrabComponent* itsOwner; 00124 }; 00125 00126 Janitor<SeqGrabComponent> itsSeqGrab; // sequence grabber 00127 SGChannelHolder itsSGChanVideo; 00128 ImageSequence itsDrawSeq; // unique identifier for our draw sequence 00129 TimeScale itsTimeScale; 00130 TimeBase itsTimeBase; 00131 UInt8 itsQueuedFrameCount; 00132 UInt8 itsSkipFrameCount; 00133 unsigned int itsSkipFrameCountTotal; 00134 TimeValue itsPrevTime; 00135 long itsFrameCount; 00136 GWorldPtr itsGWorld; 00137 bool itsGotFrame; 00138 Image<PixRGB<byte> > itsCurrentImage; 00139 std::string itsErrorMsg; 00140 bool itsStreamStarted; 00141 00142 static pascal OSErr grabDataProc(SGChannel c, Ptr p, long len, 00143 long* /*offset*/, 00144 long /*chRefCon*/, TimeValue time, 00145 short /*writeType*/, long refCon); 00146 00147 OSErr grabData(SGChannel c, Ptr p, long len, TimeValue time); 00148 00149 static pascal ComponentResult 00150 grabCompressCompleteBottle(SGChannel c, UInt8* itsQueuedFrameCount, 00151 SGCompressInfo* ci, TimeRecord* t, 00152 long refCon); 00153 00154 public: 00155 void startStream(); 00156 00157 GenericFrame readFrame(); 00158 00159 std::string getSummary() const; 00160 }; 00161 00162 // ###################################################################### 00163 QuickTimeGrabber::Impl::Impl(const Dims& dims) 00164 : 00165 itsSeqGrab(0, &CloseComponent), 00166 itsSGChanVideo(&itsSeqGrab.it), 00167 itsDrawSeq(0), 00168 itsTimeScale(0), 00169 itsTimeBase(0), 00170 itsQueuedFrameCount(0), 00171 itsSkipFrameCount(0), 00172 itsSkipFrameCountTotal(0), 00173 itsPrevTime(0), 00174 itsFrameCount(0), 00175 itsGWorld(0), 00176 itsGotFrame(false), 00177 itsCurrentImage(), 00178 itsErrorMsg(), 00179 itsStreamStarted(false) 00180 { 00181 OSErr err; 00182 00183 EnterMovies(); 00184 00185 // open the default sequence grabber 00186 itsSeqGrab.it = OpenDefaultComponent(SeqGrabComponentType, 0); 00187 if (itsSeqGrab.it == NULL) 00188 LFATAL("OpenDefaultComponent() failed"); 00189 00190 // initialize the default sequence grabber component 00191 if (noErr != (err = SGInitialize(itsSeqGrab.it))) 00192 LFATAL("SGInitialize() failed (err=%ld)", (long) err); 00193 00194 Rect scaleRect; 00195 MacSetRect(&scaleRect, 0, 0, dims.w(), dims.h()); 00196 ASSERT(itsGWorld == 0); 00197 QTNewGWorld(&itsGWorld, 00198 k32ARGBPixelFormat, &scaleRect, 00199 NULL, NULL, 00200 kNativeEndianPixMap); 00201 00202 // set its graphics world 00203 if (noErr != (err = SGSetGWorld(itsSeqGrab.it, itsGWorld, NULL))) 00204 LFATAL("SGSetGWorld() failed (err=%ld)", (long) err); 00205 00206 // specify the destination data reference for a record operation 00207 // tell it we're not making a movie if the flag seqGrabDontMakeMovie 00208 // is used, the sequence grabber still calls your data function, but 00209 // does not write any data to the movie file writeType will always 00210 // be set to seqGrabWriteAppend 00211 if (noErr != 00212 (err = SGSetDataRef(itsSeqGrab.it, 0, 0, 00213 seqGrabDontMakeMovie | seqGrabDataProcIsInterruptSafe))) 00214 LFATAL("SGSetDataRef() failed (err=%ld)", (long) err); 00215 00216 Impl::SGChannelHolder sgchanSound(&itsSeqGrab.it); 00217 00218 if (noErr != (err = SGNewChannel(itsSeqGrab.it, 00219 VideoMediaType, &itsSGChanVideo.it))) 00220 LFATAL("SGNewChannel(video) failed (err=%ld)", (long) err); 00221 00222 if (noErr != (err = SGNewChannel(itsSeqGrab.it, 00223 SoundMediaType, &sgchanSound.it))) 00224 { 00225 // don't care if we couldn't get a sound channel 00226 sgchanSound.it = NULL; 00227 LERROR("SGNewChannel(audio) failed (err=%ld)", (long) err); 00228 } 00229 00230 // get the active rectangle 00231 Rect srcBounds; 00232 if (noErr != (err = SGGetSrcVideoBounds(itsSGChanVideo.it, &srcBounds))) 00233 LFATAL("SGGetSrcVideoBounds() failed (err=%ld)", (long) err); 00234 00235 // we always want all the source 00236 setVideoChannelBounds(itsSGChanVideo.it, &srcBounds, &srcBounds); 00237 00238 // set usage for new video channel to avoid playthrough 00239 // note we don't set seqGrabPlayDuringRecord 00240 if (noErr != (err = SGSetChannelUsage(itsSGChanVideo.it, 00241 seqGrabRecord | 00242 seqGrabLowLatencyCapture | 00243 seqGrabAlwaysUseTimeBase))) 00244 LFATAL("SGSetChannelUsage(video) failed (err=%ld)", (long) err); 00245 00246 if (noErr != (err = SGSetChannelUsage(sgchanSound.it, seqGrabRecord | 00247 //seqGrabPlayDuringRecord | 00248 seqGrabLowLatencyCapture | 00249 seqGrabAlwaysUseTimeBase))) 00250 LERROR("SGSetChannelUsage(audio) failed (err=%ld)", (long) err); 00251 00252 // specify a sequence grabber data function 00253 if (noErr != (err = SGSetDataProc(itsSeqGrab.it, 00254 NewSGDataUPP(Impl::grabDataProc), 00255 (long)(this)))) 00256 LFATAL("SGSetDataProc() failed (err=%ld)", (long) err); 00257 00258 SGSetChannelRefCon(itsSGChanVideo.it, (long)(this)); 00259 00260 // set up the video bottlenecks so we can get our queued frame count 00261 VideoBottles vb = { 0 }; 00262 if (noErr != (err = SGGetVideoBottlenecks(itsSGChanVideo.it, &vb))) 00263 LFATAL("SGGetVideoBottlenecks() failed (err=%ld)", (long) err); 00264 00265 vb.procCount = 9; // there are 9 bottleneck procs; this must be filled in 00266 vb.grabCompressCompleteProc = 00267 NewSGGrabCompressCompleteBottleUPP 00268 (Impl::grabCompressCompleteBottle); 00269 00270 if (noErr != (err = SGSetVideoBottlenecks(itsSGChanVideo.it, &vb))) 00271 LFATAL("SGSetVideoBottlenecks() failed (err=%ld)", (long) err); 00272 00273 SGSetFrameRate(itsSGChanVideo.it, FixRatio(30, 1)); 00274 } 00275 00276 // ###################################################################### 00277 QuickTimeGrabber::Impl::~Impl() 00278 { 00279 const std::string summary = this->getSummary(); 00280 if (summary.size() > 0) 00281 LINFO("%s", summary.c_str()); 00282 00283 if (itsSeqGrab.it != 0) 00284 SGStop(itsSeqGrab.it); 00285 00286 // clean up the bits 00287 if (itsDrawSeq) 00288 CDSequenceEnd(itsDrawSeq); 00289 00290 DisposeGWorld(itsGWorld); 00291 } 00292 00293 // ###################################################################### 00294 /* grabDataProc 00295 00296 Purpose: sequence grabber data procedure - this is where the work 00297 is done 00298 00299 Notes: 00300 00301 the sequence grabber calls the data function whenever any of the 00302 grabber's channels write digitized data to the destination movie 00303 file. 00304 00305 NOTE: We really mean any, if you have an audio and video channel 00306 then the DataProc will be called for either channel whenever 00307 data has been captured. Be sure to check which channel is 00308 being passed in. In this example we never create an audio 00309 channel so we know we're always dealing with video. 00310 00311 This data function does two things, it first decompresses captured 00312 video data into an offscreen GWorld, draws some status information 00313 onto the frame then transfers the frame to an onscreen window. 00314 00315 For more information refer to Inside Macintosh: QuickTime 00316 Components, page 5-120 00317 00318 c - the channel component that is writing the digitized data. 00319 00320 p - a pointer to the digitized data. 00321 00322 len - the number of bytes of digitized data. 00323 00324 offset - a pointer to a field that may specify where you are to 00325 write the digitized data, and that is to receive a value indicating 00326 where you wrote the data. 00327 00328 chRefCon - per channel reference constant specified using 00329 SGSetChannelRefCon. 00330 00331 time - the starting time of the data, in the channel's time scale. 00332 00333 writeType - the type of write operation being performed. 00334 00335 seqGrabWriteAppend - Append new data. 00336 00337 seqGrabWriteReserve - Do not write data. Instead, reserve space for 00338 the amount of data specified in the len parameter. 00339 00340 seqGrabWriteFill - Write data into the location specified by 00341 offset. Used to fill the space previously reserved with 00342 seqGrabWriteReserve. The Sequence Grabber may call the DataProc 00343 several times to fill a single reserved location. 00344 00345 refCon - the reference constant you specified when you assigned 00346 your data function to the sequence grabber. 00347 */ 00348 pascal OSErr QuickTimeGrabber::Impl:: 00349 grabDataProc(SGChannel c, Ptr p, long len, 00350 long* /*offset*/, 00351 long /*chRefCon*/, TimeValue time, 00352 short /*writeType*/, long refCon) 00353 { 00354 QuickTimeGrabber::Impl* rep = (QuickTimeGrabber::Impl*)refCon; 00355 if (rep != NULL) 00356 try 00357 { 00358 return rep->grabData(c, p, len, time); 00359 } 00360 catch (...) 00361 { 00362 return -1; 00363 } 00364 00365 return -1; 00366 } 00367 00368 // ###################################################################### 00369 OSErr QuickTimeGrabber::Impl::grabData(SGChannel c, Ptr p, 00370 long len, TimeValue time) 00371 { 00372 if (itsGotFrame) 00373 { 00374 LDEBUG("already got a frame on this iteration"); 00375 return noErr; 00376 } 00377 00378 // we only care about the video 00379 if (c != itsSGChanVideo.it) 00380 { 00381 return noErr; 00382 } 00383 00384 // reset frame and time counters after a stop/start 00385 if (time < itsPrevTime) 00386 { 00387 LDEBUG("resetting frame/time counters (current=%ld, last=%ld)", 00388 (long) time, (long) itsPrevTime); 00389 itsPrevTime = 0; 00390 itsFrameCount = 0; 00391 } 00392 00393 if (itsTimeScale == 0) 00394 { 00395 LDEBUG("setting up time scale & timebase"); 00396 00397 Fixed framesPerSecond; 00398 long milliSecPerFrameIgnore, bytesPerSecondIgnore; 00399 00400 // first time here so get the time scale & timebase 00401 if (noErr != SGGetChannelTimeScale(c, &itsTimeScale)) 00402 { 00403 itsErrorMsg = "SGGetChannelTimeScale() failed"; 00404 return OSErr(-1); 00405 } 00406 00407 if (noErr != SGGetTimeBase(itsSeqGrab.it, &itsTimeBase)) 00408 { 00409 itsErrorMsg = "SGGetTimeBase() failed"; 00410 return OSErr(-1); 00411 } 00412 00413 if (noErr != VDGetDataRate(SGGetVideoDigitizerComponent(c), 00414 &milliSecPerFrameIgnore, 00415 &framesPerSecond, 00416 &bytesPerSecondIgnore)) 00417 { 00418 itsErrorMsg = "VDGetDataRate() failed"; 00419 return OSErr(-1); 00420 } 00421 } 00422 00423 if (itsDrawSeq == 0) 00424 { 00425 LDEBUG("setting up decompression sequence"); 00426 00427 // set up decompression sequence 00428 ImageDescriptionHandle imageDesc = 00429 (ImageDescriptionHandle)NewHandle(0); 00430 00431 // retrieve a channel's current sample description, the channel 00432 // returns a sample description that is appropriate to the type 00433 // of data being captured 00434 if (noErr != SGGetChannelSampleDescription(c, (Handle)imageDesc)) 00435 { 00436 itsErrorMsg = "SGGetChannelSampleDescription() failed"; 00437 return OSErr(-1); 00438 } 00439 00440 // make a scaling matrix for the sequence 00441 Rect sourceRect = { 0, 0 }; 00442 sourceRect.right = (**imageDesc).width; 00443 sourceRect.bottom = (**imageDesc).height; 00444 00445 Rect scaleRect; 00446 GetPixBounds(GetGWorldPixMap(itsGWorld), &scaleRect); 00447 00448 // if DV do high quality 720x480 both fields 00449 CodecFlags cFlags = 00450 (kDVCNTSCCodecType == (**imageDesc).cType) 00451 ? codecHighQuality 00452 : codecNormalQuality; 00453 00454 MatrixRecord scaleMatrix; 00455 RectMatrix(&scaleMatrix, &sourceRect, &scaleRect); 00456 00457 LINFO("sourceRect = %dx%d, scaleRect = %dx%d", 00458 sourceRect.right - sourceRect.left, 00459 sourceRect.bottom - sourceRect.top, 00460 scaleRect.right - scaleRect.left, 00461 scaleRect.bottom - scaleRect.top); 00462 00463 // begin the process of decompressing a sequence of frames this 00464 // is a set-up call and is only called once for the sequence - 00465 // the ICM will interrogate different codecs and construct a 00466 // suitable decompression chain, as this is a time consuming 00467 // process we don't want to do this once per frame (eg. by using 00468 // DecompressImage) for more information see Ice Floe #8 00469 // http://developer.apple.com/quicktime/icefloe/dispatch008.html 00470 // the destination is specified as the GWorld 00471 CGrafPtr dest = itsGWorld; 00472 if (noErr != DecompressSequenceBeginS 00473 (&itsDrawSeq, // pointer to field to receive unique ID for sequence 00474 imageDesc, // handle to image description structure 00475 p, // points to the compressed image data 00476 len, // size of the data buffer 00477 dest, // port for the DESTINATION image 00478 NULL, // graphics device handle, if port is set, set to NULL 00479 NULL, // decompress the entire source image - no source extraction 00480 &scaleMatrix, // transformation matrix 00481 srcCopy, // transfer mode specifier 00482 (RgnHandle)NULL, // clipping region in dest. coordinate system to use as a mask 00483 0, // flags 00484 cFlags, // accuracy in decompression 00485 bestSpeedCodec)) // compressor identifier or special identifiers ie. bestSpeedCodec 00486 { 00487 itsErrorMsg = "DSeqBegin failed"; 00488 return OSErr(-1); 00489 } 00490 00491 DisposeHandle((Handle)imageDesc); 00492 00493 } // itsDrawSeq == 0 00494 00495 // get the TimeBase time and figure out the delta between that time 00496 // and this frame time 00497 const TimeValue timeBaseTime = GetTimeBaseTime(itsTimeBase, 00498 itsTimeScale, NULL); 00499 const TimeValue timeBaseDelta = timeBaseTime - time; 00500 const TimeValue frameTimeDelta = time - itsPrevTime; 00501 00502 if (timeBaseDelta < 0) 00503 { 00504 itsErrorMsg = "bogus timeBaseDelta"; 00505 return OSErr(-1); 00506 } 00507 00508 // if we have more than one queued frame and our capture rate drops 00509 // below 10 frames, skip the frame to try and catch up 00510 if ((itsQueuedFrameCount > 1) 00511 && ((itsTimeScale / frameTimeDelta) < 10) 00512 && (itsSkipFrameCount < 15)) 00513 { 00514 LDEBUG("dropping frame"); 00515 ++itsSkipFrameCount; 00516 ++itsSkipFrameCountTotal; 00517 } 00518 else 00519 { 00520 itsFrameCount++; 00521 00522 CodecFlags ignore; 00523 00524 // decompress a frame into the window - can queue a frame for async decompression when passed in a completion proc 00525 if (noErr != DecompressSequenceFrameS 00526 (itsDrawSeq, // sequence ID returned by DecompressSequenceBegin 00527 p, // pointer to compressed image data 00528 len, // size of the buffer 00529 0, // in flags 00530 &ignore, // out flags 00531 NULL)) // async completion proc 00532 { 00533 itsErrorMsg = "DSeqFrameS failed"; 00534 return OSErr(-1); 00535 } 00536 00537 // get the information we need from the GWorld 00538 Rect pbound; 00539 GetPixBounds(GetGWorldPixMap(itsGWorld), &pbound); 00540 00541 char* const baseAddr = 00542 GetPixBaseAddr(GetGWorldPixMap(itsGWorld)); 00543 00544 const long rowBytes = 00545 QTGetPixMapHandleRowBytes(GetGWorldPixMap(itsGWorld)); 00546 00547 itsCurrentImage.resize(Dims(pbound.right - pbound.left, 00548 pbound.bottom - pbound.top)); 00549 00550 Image<PixRGB<byte> >::iterator itr = itsCurrentImage.beginw(); 00551 00552 for (int y = pbound.top; y < pbound.bottom; ++y) 00553 { 00554 char* p = baseAddr + rowBytes * (y-pbound.top); 00555 00556 for (int x = pbound.left; x < pbound.right; ++x) 00557 { 00558 const UInt32 color = *((UInt32*)(p) + x - pbound.left); 00559 const UInt32 R = (color & 0x00FF0000) >> 16; 00560 const UInt32 G = (color & 0x0000FF00) >> 8; 00561 const UInt32 B = (color & 0x000000FF) >> 0; 00562 00563 *itr++ = PixRGB<byte>(R,G,B); 00564 } 00565 } 00566 00567 itsSkipFrameCount = 0; 00568 itsPrevTime = time; 00569 itsGotFrame = true; 00570 } 00571 00572 // status information 00573 const float fps = (float)itsTimeScale / (float)frameTimeDelta; 00574 const float averagefps = ((float)itsFrameCount * (float)itsTimeScale) / (float)time; 00575 const UInt8 minutes = (time / itsTimeScale) / 60; 00576 const UInt8 seconds = (time / itsTimeScale) % 60; 00577 const UInt8 frames = (time % itsTimeScale) / frameTimeDelta; 00578 LDEBUG("#%06ld t:%ld nq:%u, %02d:%02d.%02d, fps:%5.1f av:%5.1f", 00579 itsFrameCount, time, itsQueuedFrameCount, 00580 minutes, seconds, frames, fps, averagefps); 00581 00582 return noErr; 00583 } 00584 00585 // ###################################################################### 00586 /* grabCompressCompleteBottle 00587 00588 Purpose: figure out how many frames are queued by the vDig 00589 00590 Notes: the UInt8 *queuedFrameCount replaces Boolean *done. (0 00591 (==false) still means no frames, and 1 (==true) one, but if more 00592 than one are available, the number should be returned here - The 00593 value 2 previously meant more than one frame, so some VDIGs may 00594 return 2 even if more than 2 are available, and some will still 00595 return 1 as they are using the original definition. 00596 */ 00597 pascal ComponentResult QuickTimeGrabber::Impl:: 00598 grabCompressCompleteBottle(SGChannel c, UInt8* queuedFrameCount, 00599 SGCompressInfo* ci, TimeRecord* t, long refCon) 00600 { 00601 QuickTimeGrabber::Impl* rep = (QuickTimeGrabber::Impl*)refCon; 00602 if (NULL == rep) return -1; 00603 00604 // call the original proc; you must do this 00605 const OSErr err = SGGrabCompressComplete(c, queuedFrameCount, ci, t); 00606 00607 // save the queued frame count so we have it 00608 rep->itsQueuedFrameCount = *queuedFrameCount; 00609 00610 return err; 00611 } 00612 00613 // ###################################################################### 00614 void QuickTimeGrabber::Impl::startStream() 00615 { 00616 // lights...camera... 00617 if (noErr != SGPrepare(itsSeqGrab.it, false, true)) 00618 LFATAL("SGPrepare() failed"); 00619 00620 // ...action 00621 if (noErr != SGStartRecord(itsSeqGrab.it)) 00622 LFATAL("SGStartRecord() failed"); 00623 00624 itsStreamStarted = true; 00625 } 00626 00627 // ###################################################################### 00628 GenericFrame QuickTimeGrabber::Impl::readFrame() 00629 { 00630 if (!itsStreamStarted) 00631 this->startStream(); 00632 00633 while (1) 00634 { 00635 itsGotFrame = false; 00636 itsErrorMsg = ""; 00637 if (noErr != SGIdle(itsSeqGrab.it)) 00638 LFATAL("SGIdle() failed"); 00639 00640 if (itsErrorMsg.length() > 0) 00641 { 00642 // some error specific to SGIdle occurred - any errors 00643 // returned from the data proc will also show up here and we 00644 // don't want to write over them 00645 00646 // in QT 4 you would always encounter a cDepthErr error 00647 // after a user drags the window, this failure condition has 00648 // been greatly relaxed in QT 5 it may still occur but 00649 // should only apply to vDigs that really control the screen 00650 00651 // you don't always know where these errors originate from, 00652 // some may come from the VDig... 00653 00654 LFATAL("QuickTimeGrabber error during SGIdle (%s)", 00655 itsErrorMsg.c_str()); 00656 00657 // ...to fix this we simply call SGStop and SGStartRecord 00658 // again calling stop allows the SG to release and 00659 // re-prepare for grabbing hopefully fixing any problems, 00660 // this is obviously a very relaxed approach 00661 SGStop(itsSeqGrab.it); 00662 SGStartRecord(itsSeqGrab.it); 00663 } 00664 00665 if (itsGotFrame) 00666 return GenericFrame(itsCurrentImage); 00667 00668 usleep(20000); 00669 } 00670 } 00671 00672 // ###################################################################### 00673 std::string QuickTimeGrabber::Impl::getSummary() const 00674 { 00675 if (itsPrevTime <= 0 || itsTimeScale <= 0) 00676 return std::string(); 00677 00678 const double averagefps = (double(itsFrameCount) * double(itsTimeScale)) 00679 / double(itsPrevTime); 00680 const UInt8 minutes = (itsPrevTime / itsTimeScale) / 60; 00681 const UInt8 seconds = (itsPrevTime / itsTimeScale) % 60; 00682 return sformat("summary nframes:%ld, ndrop:%u, %02d:%02d, avg fps:%5.1f", 00683 itsFrameCount, itsSkipFrameCountTotal, 00684 minutes, seconds, averagefps); 00685 } 00686 00687 #endif // HAVE_QUICKTIME_QUICKTIME_H 00688 00689 // ###################################################################### 00690 QuickTimeGrabber::QuickTimeGrabber(OptionManager& mgr, 00691 const std::string& descrName, 00692 const std::string& tagName) 00693 : 00694 FrameIstream(mgr, descrName, tagName), 00695 itsDims(&OPT_FrameGrabberDims, this), 00696 rep(0) 00697 {} 00698 00699 // ###################################################################### 00700 QuickTimeGrabber::~QuickTimeGrabber() 00701 { 00702 #ifdef HAVE_QUICKTIME_QUICKTIME_H 00703 if (rep) 00704 delete rep; 00705 #endif 00706 } 00707 00708 // ###################################################################### 00709 void QuickTimeGrabber::startStream() 00710 { 00711 if (!this->started()) 00712 LFATAL("start() must be called before startStream()"); 00713 00714 #ifndef HAVE_QUICKTIME_QUICKTIME_H 00715 LFATAL("you must have QuickTime installed to use QuickTimeGrabber"); 00716 #else 00717 ASSERT(rep != 0); 00718 rep->startStream(); 00719 #endif 00720 } 00721 00722 // ###################################################################### 00723 GenericFrameSpec QuickTimeGrabber::peekFrameSpec() 00724 { 00725 GenericFrameSpec result; 00726 00727 result.nativeType = GenericFrame::RGB_U8; 00728 result.videoFormat = VIDFMT_RGB24; 00729 result.videoByteSwap = false; 00730 result.dims = itsDims.getVal(); 00731 result.floatFlags = 0; 00732 00733 return result; 00734 } 00735 00736 // ###################################################################### 00737 GenericFrame QuickTimeGrabber::readFrame() 00738 { 00739 if (!this->started()) 00740 LFATAL("start() must be called before readFrame()"); 00741 00742 #ifndef HAVE_QUICKTIME_QUICKTIME_H 00743 LFATAL("you must have QuickTime installed to use QuickTimeGrabber"); 00744 /* can't happen */ return GenericFrame(); 00745 #else 00746 ASSERT(rep != 0); 00747 return rep->readFrame(); 00748 #endif 00749 } 00750 00751 // ###################################################################### 00752 void QuickTimeGrabber::start1() 00753 { 00754 FrameIstream::start1(); 00755 00756 #ifndef HAVE_QUICKTIME_QUICKTIME_H 00757 LFATAL("you must have QuickTime installed to use QuickTimeGrabber"); 00758 #else 00759 ASSERT(rep == 0); 00760 rep = new Impl(itsDims.getVal()); 00761 #endif 00762 } 00763 00764 // ###################################################################### 00765 void QuickTimeGrabber::stop2() 00766 { 00767 FrameIstream::stop2(); 00768 00769 #ifndef HAVE_QUICKTIME_QUICKTIME_H 00770 LERROR("you must have QuickTime installed to use QuickTimeGrabber"); 00771 #else 00772 ASSERT(rep != 0); 00773 delete rep; 00774 rep = 0; 00775 #endif 00776 } 00777 00778 // ###################################################################### 00779 /* So things look consistent in everyone's emacs... */ 00780 /* Local Variables: */ 00781 /* mode: c++ */ 00782 /* indent-tabs-mode: nil */ 00783 /* End: */ 00784 00785 #endif // DEVICES_QUICKTIMEGRABBER_C_DEFINED