QuickTimeGrabber.C

Go to the documentation of this file.
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
Generated on Sun May 8 08:04:45 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3