QuartzQuickTimeDecoder.C

Go to the documentation of this file.
00001 /*!@file Media/QuartzQuickTimeDecoder.C */
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/Media/QuartzQuickTimeDecoder.C $
00035 // $Id: QuartzQuickTimeDecoder.C 13800 2010-08-18 20:58:25Z dberg $
00036 //
00037 
00038 // Portions of the source code in this file are derived from code in
00039 // the QTPixelBufferVCToCGImage sample program, Copyright 2005-2006
00040 // Apple Computer, Inc., in which the included license agreement
00041 // allows redistribution with or without modifications as long as the
00042 // Apple Computer name is not used to endorse the modified product.
00043 
00044 #ifndef MEDIA_QUARTZQUICKTIMEDECODER_C_DEFINED
00045 #define MEDIA_QUARTZQUICKTIMEDECODER_C_DEFINED
00046 
00047 #include "Media/QuartzQuickTimeDecoder.H"
00048 
00049 #ifdef HAVE_QUICKTIME_QUICKTIME_H
00050 
00051 #include "Image/Image.H"
00052 #include "Image/Pixels.H"
00053 #include "Raster/GenericFrame.H"
00054 #include "Util/log.H"
00055 
00056 // ######################################################################
00057 static GenericFrame PixelBuffer2GenericFrame(CVImageBufferRef inImage)
00058 {
00059   if (NULL == inImage) return GenericFrame();
00060 
00061   const byte* const baseAddress =
00062     static_cast<const byte*>(CVPixelBufferGetBaseAddress(inImage));
00063   const size_t bytesPerRow = CVPixelBufferGetBytesPerRow(inImage);
00064   const size_t width = CVPixelBufferGetWidth(inImage);
00065   const size_t height = CVPixelBufferGetHeight(inImage);
00066 
00067   const OSType inPixelFormat =
00068     CVPixelBufferGetPixelFormatType((CVPixelBufferRef)inImage);
00069 
00070   LDEBUG("PixelBuffer FormatType: %lx", inPixelFormat);
00071 
00072   switch(inPixelFormat)
00073     {
00074     case k32ARGBPixelFormat:
00075 
00076       LDEBUG("baseAddress=%p", baseAddress);
00077       LDEBUG("bytesPerRow=%u", (unsigned int) bytesPerRow);
00078 
00079       {
00080         Image<PixRGB<byte> > rgb(width, height, NO_INIT);
00081         byte* data = reinterpret_cast<byte*>(rgb.getArrayPtr());
00082 
00083         for (size_t y = 0; y < height; ++y)
00084           {
00085             const byte* row = baseAddress + y*bytesPerRow;
00086             for (size_t x = 0; x < width; ++x)
00087               {
00088                 // alpha = row[0];
00089                 data[0] = row[1];
00090                 data[1] = row[2];
00091                 data[2] = row[3];
00092                 data += 3;
00093                 row += 4;
00094               }
00095           }
00096 
00097         return GenericFrame(rgb);
00098       }
00099 
00100       break;
00101     default:
00102       LFATAL("I don't know what to do with this format!");
00103       break;
00104     }
00105 
00106   /* can't happen */ return GenericFrame();
00107 }
00108 
00109 // ######################################################################
00110 static void SetNumberValue(CFMutableDictionaryRef inDict,
00111                            CFStringRef inKey, SInt32 inValue)
00112 {
00113   CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &inValue);
00114   if (NULL == number)
00115     LFATAL("CFNumberCreate() failed");
00116 
00117   CFDictionarySetValue(inDict, inKey, number);
00118 
00119   CFRelease(number);
00120 }
00121 
00122 // ######################################################################
00123 /* Create a QuickTime Pixel Buffer Context
00124    This function creates a QuickTime Visual Context which will produce CVPixelBuffers
00125 */
00126 static QTVisualContextRef CreatePixelBufferContext(SInt32 inPixelFormat,
00127                                                    const CGRect* inBounds)
00128 {
00129   if (0 == inPixelFormat)
00130     LFATAL("pixel format must be non-zero");
00131 
00132   if (CGRectIsNull(*inBounds))
00133     LFATAL("bounds rect must be non-empty");
00134 
00135   // Pixel Buffer attributes
00136   Janitor<CFMutableDictionaryRef> pixelBufferOptions
00137     (CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
00138                                &kCFTypeDictionaryKeyCallBacks,
00139                                &kCFTypeDictionaryValueCallBacks),
00140      &CFRelease);
00141 
00142   if (NULL == pixelBufferOptions.it)
00143     LFATAL("couldn't create pixelBufferOptions");
00144 
00145   // the pixel format we want
00146   SetNumberValue(pixelBufferOptions.it, kCVPixelBufferPixelFormatTypeKey, inPixelFormat);
00147 
00148   // size
00149   SetNumberValue(pixelBufferOptions.it, kCVPixelBufferWidthKey, int(inBounds->size.width));
00150   SetNumberValue(pixelBufferOptions.it, kCVPixelBufferHeightKey, int(inBounds->size.height));
00151 
00152   // alignment
00153   SetNumberValue(pixelBufferOptions.it, kCVPixelBufferBytesPerRowAlignmentKey, 16);
00154 
00155   // QT Visual Context attributes
00156   Janitor<CFMutableDictionaryRef> visualContextOptions
00157     (CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
00158                                &kCFTypeDictionaryKeyCallBacks,
00159                                &kCFTypeDictionaryValueCallBacks),
00160      &CFRelease);
00161 
00162   if (NULL == visualContextOptions.it)
00163     LFATAL("couldn't create visualContextOptions");
00164 
00165   // set the pixel buffer attributes for the visual context
00166   CFDictionarySetValue(visualContextOptions.it,
00167                        kQTVisualContextPixelBufferAttributesKey,
00168                        pixelBufferOptions.it);
00169 
00170   // create a Pixel Buffer visual context
00171   QTVisualContextRef outVisualContext;
00172   if (noErr != QTPixelBufferContextCreate(kCFAllocatorDefault,
00173                                           visualContextOptions.it,
00174                                           &outVisualContext))
00175     LFATAL("couldn't create visualContext");
00176 
00177   if (NULL == outVisualContext)
00178     LFATAL("newly created visualContext was null");
00179 
00180   return outVisualContext;
00181 }
00182 
00183 // ######################################################################
00184 /* A Callback to receive notifications when a new image becomes
00185    available.  This callback is called from random threads and is best
00186    used as a notification that something has changed during playback
00187    to the visual context.
00188 */
00189 void QuartzQuickTimeDecoder::
00190 imageAvailableCallback(QTVisualContextRef visualContext,
00191                        const CVTimeStamp* timeStamp, void* refCon)
00192 {
00193   // Print out some information about the timeStamp
00194   LDEBUG("CVTimeStamp Flags: %llu", timeStamp->flags);
00195 
00196   if (timeStamp->flags & kCVTimeStampVideoTimeValid)
00197     LDEBUG("CVTimeStamp VideoTime: %lld", timeStamp->videoTime);
00198 
00199   if (timeStamp->flags & kCVTimeStampHostTimeValid)
00200     LDEBUG("CVTimeStamp HostTime: %llu", timeStamp->hostTime);
00201 
00202   if (timeStamp->flags & kCVTimeStampRateScalarValid)
00203     LDEBUG("CVTimeStamp Rate: %1.0g", timeStamp->rateScalar);
00204 
00205   QuartzQuickTimeDecoder* qqd = (QuartzQuickTimeDecoder*) refCon;
00206 
00207   // Check to make sure we do have an image for this time and if so then grab it
00208   if (!QTVisualContextIsNewImageAvailable(qqd->itsVisualContext.it, timeStamp))
00209     {
00210       qqd->itsCallbackError = "no new image was available";
00211     }
00212   else
00213     {
00214       CVImageBufferRef newImage = NULL;
00215       if (noErr != QTVisualContextCopyImageForTime(qqd->itsVisualContext.it,
00216                                                    kCFAllocatorDefault, timeStamp,
00217                                                    &newImage))
00218         {
00219           qqd->itsCallbackError = "QTVisualContextCopyImageForTime() failed";
00220         }
00221       else if (NULL == newImage)
00222         {
00223           qqd->itsCallbackError =
00224             "QTVisualContextCopyImageForTime() gave a null image";
00225         }
00226       else if (noErr != (CVPixelBufferLockBaseAddress
00227                          ((CVPixelBufferRef)newImage, 0)))
00228         {
00229           qqd->itsCallbackError = "CVPixelBufferLockBaseAddress failed";
00230         }
00231       else
00232         {
00233           // Get a CGImage from the returned CV pixel buffer
00234           try
00235             {
00236               qqd->itsFrame = PixelBuffer2GenericFrame(newImage);
00237               ++qqd->itsFrameNumber;
00238             }
00239           catch (std::exception& e)
00240             {
00241               qqd->itsCallbackError = e.what();
00242             }
00243           catch (...)
00244             {
00245               qqd->itsCallbackError = "unknown error";
00246             }
00247 
00248           CVPixelBufferUnlockBaseAddress((CVPixelBufferRef)newImage, 0);
00249         }
00250 
00251       if (newImage)
00252         CVPixelBufferRelease(newImage);
00253     }
00254 
00255   QTVisualContextTask(qqd->itsVisualContext.it);
00256 }
00257 
00258 // ######################################################################
00259 QuartzQuickTimeDecoder::QuartzQuickTimeDecoder(const char* fname)
00260   :
00261   itsMovie(NULL, &DisposeMovie),
00262   itsVisualContext(NULL, &QTVisualContextRelease),
00263   itsFrame(),
00264   itsFrameNumber(0),
00265   itsNextTime(0),
00266   itsNextFramePushback(false),
00267   itsFirstFrame(true)
00268 {
00269   // Initialize QuickTime
00270   EnterMovies();
00271 
00272   // Convert movie path to CFString
00273   CFStringRef inPath = CFStringCreateWithCString(NULL, fname,
00274                                                  CFStringGetSystemEncoding());
00275   if (!inPath)
00276     LFATAL("Could not get CFString from %s", fname);
00277 
00278   // create the data reference
00279   Janitor<Handle> myDataRef(NULL, &DisposeHandle);
00280   OSType myDataRefType;
00281   if (noErr != QTNewDataReferenceFromFullPathCFString
00282       (inPath, (unsigned long) kQTNativeDefaultPathStyle,
00283        0, &myDataRef.it, &myDataRefType))
00284     LFATAL("Could not get DataRef for %s", fname);
00285 
00286   // get the Movie
00287   short actualResId = DoTheRightThing;
00288   if (noErr != NewMovieFromDataRef(&itsMovie.it, newMovieActive,
00289                                    &actualResId, myDataRef.it, myDataRefType))
00290     LFATAL("Could not get Movie from DataRef for %s", fname);
00291 
00292   // Create the QT Pixel Buffer Visual Context
00293   Rect bounds;
00294   GetMovieBox(itsMovie.it, &bounds);
00295 
00296   HIRect theBounds;
00297   theBounds.origin.x = bounds.left;
00298   theBounds.origin.y = bounds.top;
00299   theBounds.size.width = bounds.right - bounds.left;
00300   theBounds.size.height = bounds.bottom - bounds.top;
00301 
00302   itsDims = Dims(int(theBounds.size.width), int(theBounds.size.height));
00303 
00304   LDEBUG("theBounds2 = {%f, %f, %f, %f}\n",
00305          theBounds.origin.x, theBounds.origin.y,
00306          theBounds.size.width, theBounds.size.height);
00307 
00308   itsVisualContext.it =
00309     CreatePixelBufferContext(k32ARGBPixelFormat, &theBounds);
00310 
00311   if (noErr != SetMovieVisualContext(itsMovie.it, itsVisualContext.it))
00312     LFATAL("SetMovieVisualContext failed");
00313 
00314   // Install our visual context callback we'll use as a notification mechanism
00315   if (noErr !=
00316       QTVisualContextSetImageAvailableCallback
00317       (itsVisualContext.it, &QuartzQuickTimeDecoder::imageAvailableCallback,
00318        this))
00319     LFATAL("QTVisualContextSetImageAvailableCallback failed");
00320 }
00321 
00322 // ######################################################################
00323 QuartzQuickTimeDecoder::~QuartzQuickTimeDecoder()
00324 {}
00325 
00326 // ######################################################################
00327 int QuartzQuickTimeDecoder::apparentFrameNumber() const
00328 {
00329   return
00330     itsNextFramePushback
00331     ? itsFrameNumber - 1
00332     : itsFrameNumber;
00333 }
00334 
00335 // ######################################################################
00336 GenericFrameSpec QuartzQuickTimeDecoder::peekFrameSpec()
00337 {
00338 //   if (itsFirstFrame)
00339 //     {
00340 //       // if we're still waiting for the first frame, then we can't
00341 //       // possibly have a pushback frame:
00342 //       ASSERT(!itsNextFramePushback);
00343 
00344 //       readRawFrame();
00345 //       itsNextFramePushback = true;
00346 //     }
00347 
00348 //   ASSERT(!itsFirstFrame);
00349 
00350   GenericFrameSpec result;
00351 
00352   result.nativeType = GenericFrame::RGB_U8;
00353   result.videoFormat = VIDFMT_AUTO;
00354   result.videoByteSwap = false;
00355   result.dims = itsDims;
00356   result.floatFlags = 0;
00357 
00358   return result;
00359 }
00360 
00361 // ######################################################################
00362 VideoFrame QuartzQuickTimeDecoder::readVideoFrame()
00363 {
00364   return this->readFrame().asVideo();
00365 }
00366 
00367 // ######################################################################
00368 Image<PixRGB<byte> > QuartzQuickTimeDecoder::readRGB()
00369 {
00370   return this->readFrame().asRgb();
00371 }
00372 
00373 // ######################################################################
00374 bool QuartzQuickTimeDecoder::readAndDiscardFrame()
00375 {
00376   return (this->readFrame().initialized());
00377 }
00378 
00379 // ######################################################################
00380 GenericFrame QuartzQuickTimeDecoder::readFrame()
00381 {
00382   if (itsNextTime < 0)
00383     return GenericFrame();
00384 
00385   // else...
00386 
00387   if (itsNextFramePushback)
00388     {
00389       itsNextFramePushback = false;
00390       ASSERT(itsFrame.initialized());
00391       const GenericFrame f = itsFrame;
00392       itsFrame = GenericFrame();
00393       return f;
00394     }
00395 
00396   const int oldFrameNumber = itsFrameNumber;
00397 
00398   if (itsFirstFrame)
00399     {
00400       PrerollMovie(itsMovie.it, 0, fixed1);
00401 
00402       MoviesTask(itsMovie.it, 0);
00403     }
00404 
00405   // It's a little tricky trying to get things started properly for
00406   // both mpeg-1 and mpeg-4 movies. Apparently with mpeg-1 movies, the
00407   // first MoviesTask() call (under if(itsFirstFrame)) does NOT
00408   // trigger a drawCompleteCallback(). We test whether a new frame has
00409   // been decoded by checking if itsFrameNumber has changed from
00410   // oldFrameNumber. We expect that the 'gotframe' variable here will
00411   // be true only on itsFirstFrame of mpeg-4 movies, and will be false
00412   // on other frames of mpeg-4 movies and will be false for all frames
00413   // (including itsFirstFrame) of mpeg-1 movies.
00414 
00415   const bool gotframe = (itsFrameNumber != oldFrameNumber);
00416 
00417   TimeValue current = GetMovieTime(itsMovie.it, NULL);
00418   GetMovieNextInterestingTime(itsMovie.it, nextTimeStep, 0, NULL,
00419                               current, fixed1, &itsNextTime, NULL);
00420 
00421   LDEBUG("current=%ld next=%ld", current, itsNextTime);
00422 
00423   // if we haven't yet gotten a frame, then we need to issue another
00424   // MoviesTask() call
00425   if (!gotframe)
00426     MoviesTask(itsMovie.it, 0);
00427 
00428   // wait for the drawCompleteCallback() to indicate the current frame
00429   // has been rendered (actually, I'm not sure if this is necessary or
00430   // not -- if MoviesTask() is single-threaded, then it's not
00431   // necessary, but I'm not positive that it's single-threaded)
00432 
00433   while (itsFrameNumber == oldFrameNumber && itsCallbackError.length() == 0)
00434     {
00435       LDEBUG("waiting for drawCompleteCallback");
00436       usleep(10000);
00437     }
00438 
00439   if (itsCallbackError.length() > 0)
00440     {
00441       const std::string s = itsCallbackError;
00442       itsCallbackError = std::string();
00443       LFATAL("error during callback: %s", s.c_str());
00444     }
00445 
00446   LDEBUG("frame #%d @ %ld", itsFrameNumber, current);
00447 
00448   if (itsNextTime >= 0)
00449     SetMovieTimeValue(itsMovie.it, itsNextTime);
00450 
00451   itsFirstFrame = false;
00452 
00453   ASSERT(itsFrame.initialized());
00454   const GenericFrame f = itsFrame;
00455   itsFrame = GenericFrame();
00456   return f;
00457 }
00458 
00459 #endif // HAVE_QUICKTIME_QUICKTIME_H
00460 
00461 // ######################################################################
00462 /* So things look consistent in everyone's emacs... */
00463 /* Local Variables: */
00464 /* mode: c++ */
00465 /* indent-tabs-mode: nil */
00466 /* End: */
00467 
00468 #endif // MEDIA_QUARTZQUICKTIMEDECODER_C_DEFINED
Generated on Sun May 8 08:41:01 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3