00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
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
00072
00073
00074
00075 Rect sourceBounds;
00076 SGGetSrcVideoBounds(videoChannel, &sourceBounds);
00077
00078 MatrixRecord scaledSourceBoundsToSourceBounds;
00079 RectMatrix(&scaledSourceBoundsToSourceBounds,
00080 scaledSourceBounds, &sourceBounds);
00081
00082
00083
00084 Rect videoBounds = *scaledVideoBounds;
00085 TransformRect(&scaledSourceBoundsToSourceBounds, &videoBounds, 0);
00086
00087 if (noErr != SGSetVideoRect(videoChannel, &videoBounds))
00088 {
00089
00090
00091
00092 SGSetVideoRect(videoChannel, &sourceBounds);
00093 }
00094
00095
00096 Rect channelBounds = *scaledVideoBounds;
00097 OffsetRect(&channelBounds, -channelBounds.left, -channelBounds.top);
00098
00099
00100
00101
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&);
00122 SGChannelHolder& operator=(const SGChannelHolder&);
00123 SeqGrabComponent* itsOwner;
00124 };
00125
00126 Janitor<SeqGrabComponent> itsSeqGrab;
00127 SGChannelHolder itsSGChanVideo;
00128 ImageSequence itsDrawSeq;
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* ,
00144 long , TimeValue time,
00145 short , 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
00186 itsSeqGrab.it = OpenDefaultComponent(SeqGrabComponentType, 0);
00187 if (itsSeqGrab.it == NULL)
00188 LFATAL("OpenDefaultComponent() failed");
00189
00190
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
00203 if (noErr != (err = SGSetGWorld(itsSeqGrab.it, itsGWorld, NULL)))
00204 LFATAL("SGSetGWorld() failed (err=%ld)", (long) err);
00205
00206
00207
00208
00209
00210
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
00226 sgchanSound.it = NULL;
00227 LERROR("SGNewChannel(audio) failed (err=%ld)", (long) err);
00228 }
00229
00230
00231 Rect srcBounds;
00232 if (noErr != (err = SGGetSrcVideoBounds(itsSGChanVideo.it, &srcBounds)))
00233 LFATAL("SGGetSrcVideoBounds() failed (err=%ld)", (long) err);
00234
00235
00236 setVideoChannelBounds(itsSGChanVideo.it, &srcBounds, &srcBounds);
00237
00238
00239
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
00248 seqGrabLowLatencyCapture |
00249 seqGrabAlwaysUseTimeBase)))
00250 LERROR("SGSetChannelUsage(audio) failed (err=%ld)", (long) err);
00251
00252
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
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;
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
00287 if (itsDrawSeq)
00288 CDSequenceEnd(itsDrawSeq);
00289
00290 DisposeGWorld(itsGWorld);
00291 }
00292
00293
00294
00295
00296
00297
00298
00299
00300
00301
00302
00303
00304
00305
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321
00322
00323
00324
00325
00326
00327
00328
00329
00330
00331
00332
00333
00334
00335
00336
00337
00338
00339
00340
00341
00342
00343
00344
00345
00346
00347
00348 pascal OSErr QuickTimeGrabber::Impl::
00349 grabDataProc(SGChannel c, Ptr p, long len,
00350 long* ,
00351 long , TimeValue time,
00352 short , 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
00379 if (c != itsSGChanVideo.it)
00380 {
00381 return noErr;
00382 }
00383
00384
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
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
00428 ImageDescriptionHandle imageDesc =
00429 (ImageDescriptionHandle)NewHandle(0);
00430
00431
00432
00433
00434 if (noErr != SGGetChannelSampleDescription(c, (Handle)imageDesc))
00435 {
00436 itsErrorMsg = "SGGetChannelSampleDescription() failed";
00437 return OSErr(-1);
00438 }
00439
00440
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
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
00464
00465
00466
00467
00468
00469
00470
00471 CGrafPtr dest = itsGWorld;
00472 if (noErr != DecompressSequenceBeginS
00473 (&itsDrawSeq,
00474 imageDesc,
00475 p,
00476 len,
00477 dest,
00478 NULL,
00479 NULL,
00480 &scaleMatrix,
00481 srcCopy,
00482 (RgnHandle)NULL,
00483 0,
00484 cFlags,
00485 bestSpeedCodec))
00486 {
00487 itsErrorMsg = "DSeqBegin failed";
00488 return OSErr(-1);
00489 }
00490
00491 DisposeHandle((Handle)imageDesc);
00492
00493 }
00494
00495
00496
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
00509
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
00525 if (noErr != DecompressSequenceFrameS
00526 (itsDrawSeq,
00527 p,
00528 len,
00529 0,
00530 &ignore,
00531 NULL))
00532 {
00533 itsErrorMsg = "DSeqFrameS failed";
00534 return OSErr(-1);
00535 }
00536
00537
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
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
00587
00588
00589
00590
00591
00592
00593
00594
00595
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
00605 const OSErr err = SGGrabCompressComplete(c, queuedFrameCount, ci, t);
00606
00607
00608 rep->itsQueuedFrameCount = *queuedFrameCount;
00609
00610 return err;
00611 }
00612
00613
00614 void QuickTimeGrabber::Impl::startStream()
00615 {
00616
00617 if (noErr != SGPrepare(itsSeqGrab.it, false, true))
00618 LFATAL("SGPrepare() failed");
00619
00620
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
00643
00644
00645
00646
00647
00648
00649
00650
00651
00652
00653
00654 LFATAL("QuickTimeGrabber error during SGIdle (%s)",
00655 itsErrorMsg.c_str());
00656
00657
00658
00659
00660
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 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
00780
00781
00782
00783
00784
00785 #endif // DEVICES_QUICKTIMEGRABBER_C_DEFINED