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