mirror of https://github.com/opencv/opencv.git
Open Source Computer Vision Library
https://opencv.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1626 lines
53 KiB
1626 lines
53 KiB
/*M/////////////////////////////////////////////////////////////////////////////////////// |
|
// |
|
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. |
|
// |
|
// By downloading, copying, installing or using the software you agree to this license. |
|
// If you do not agree to this license, do not download, install, |
|
// copy or use the software. |
|
// |
|
// |
|
// Intel License Agreement |
|
// For Open Source Computer Vision Library |
|
// |
|
// Copyright (C) 2000, Intel Corporation, all rights reserved. |
|
// Third party copyrights are property of their respective owners. |
|
// |
|
// Redistribution and use in source and binary forms, with or without modification, |
|
// are permitted provided that the following conditions are met: |
|
// |
|
// * Redistribution's of source code must retain the above copyright notice, |
|
// this list of conditions and the following disclaimer. |
|
// |
|
// * Redistribution's in binary form must reproduce the above copyright notice, |
|
// this list of conditions and the following disclaimer in the documentation |
|
// and/or other materials provided with the distribution. |
|
// |
|
// * The name of Intel Corporation may not be used to endorse or promote products |
|
// derived from this software without specific prior written permission. |
|
// |
|
// This software is provided by the copyright holders and contributors "as is" and |
|
// any express or implied warranties, including, but not limited to, the implied |
|
// warranties of merchantability and fitness for a particular purpose are disclaimed. |
|
// In no event shall the Intel Corporation or contributors be liable for any direct, |
|
// indirect, incidental, special, exemplary, or consequential damages |
|
// (including, but not limited to, procurement of substitute goods or services; |
|
// loss of use, data, or profits; or business interruption) however caused |
|
// and on any theory of liability, whether in contract, strict liability, |
|
// or tort (including negligence or otherwise) arising in any way out of |
|
// the use of this software, even if advised of the possibility of such damage. |
|
// |
|
//M*/ |
|
|
|
|
|
#include "precomp.hpp" |
|
#include "cv.h" |
|
|
|
// Original implementation by Mark Asbach |
|
// Institute of Communications Engineering |
|
// RWTH Aachen University |
|
// |
|
// For implementation details and background see: |
|
// http://developer.apple.com/samplecode/qtframestepper.win/listing1.html |
|
// |
|
// Please note that timing will only be correct for videos that contain a visual track |
|
// that has full length (compared to other tracks) |
|
|
|
|
|
// standard includes |
|
#include <cstdio> |
|
#include <cassert> |
|
|
|
// Mac OS includes |
|
#include <Carbon/Carbon.h> |
|
#include <CoreFoundation/CoreFoundation.h> |
|
#include <QuickTime/QuickTime.h> |
|
|
|
|
|
// Global state (did we call EnterMovies?) |
|
static int did_enter_movies = 0; |
|
|
|
// ---------------------------------------------------------------------------------------- |
|
#pragma mark Reading Video Files |
|
|
|
/// Movie state structure for QuickTime movies |
|
typedef struct CvCapture_QT_Movie |
|
{ |
|
Movie myMovie; // movie handle |
|
GWorldPtr myGWorld; // we render into an offscreen GWorld |
|
|
|
CvSize size; // dimensions of the movie |
|
TimeValue movie_start_time; // movies can start at arbitrary times |
|
long number_of_frames; // duration in frames |
|
long next_frame_time; |
|
long next_frame_number; |
|
|
|
IplImage * image_rgb; // will point to the PixMap of myGWorld |
|
IplImage * image_bgr; // will be returned by icvRetrieveFrame_QT() |
|
|
|
} CvCapture_QT_Movie; |
|
|
|
|
|
static int icvOpenFile_QT_Movie (CvCapture_QT_Movie * capture, const char * filename); |
|
static int icvClose_QT_Movie (CvCapture_QT_Movie * capture); |
|
static double icvGetProperty_QT_Movie (CvCapture_QT_Movie * capture, int property_id); |
|
static int icvSetProperty_QT_Movie (CvCapture_QT_Movie * capture, int property_id, double value); |
|
static int icvGrabFrame_QT_Movie (CvCapture_QT_Movie * capture); |
|
static const void * icvRetrieveFrame_QT_Movie (CvCapture_QT_Movie * capture, int); |
|
|
|
|
|
static CvCapture_QT_Movie * icvCaptureFromFile_QT (const char * filename) |
|
{ |
|
static int did_enter_movies = 0; |
|
if (! did_enter_movies) |
|
{ |
|
EnterMovies(); |
|
did_enter_movies = 1; |
|
} |
|
|
|
CvCapture_QT_Movie * capture = 0; |
|
|
|
if (filename) |
|
{ |
|
capture = (CvCapture_QT_Movie *) cvAlloc (sizeof (*capture)); |
|
memset (capture, 0, sizeof(*capture)); |
|
|
|
if (!icvOpenFile_QT_Movie (capture, filename)) |
|
cvFree( &capture ); |
|
} |
|
|
|
return capture; |
|
} |
|
|
|
|
|
|
|
/** |
|
* convert full path to CFStringRef and open corresponding Movie. Then |
|
* step over 'interesting frame times' to count total number of frames |
|
* for video material with varying frame durations and create offscreen |
|
* GWorld for rendering the movie frames. |
|
* |
|
* @author Mark Asbach <asbach@ient.rwth-aachen.de> |
|
* @date 2005-11-04 |
|
*/ |
|
static int icvOpenFile_QT_Movie (CvCapture_QT_Movie * capture, const char * filename) |
|
{ |
|
Rect myRect; |
|
short myResID = 0; |
|
Handle myDataRef = nil; |
|
OSType myDataRefType = 0; |
|
OSErr myErr = noErr; |
|
|
|
|
|
// no old errors please |
|
ClearMoviesStickyError (); |
|
|
|
// initialize pointers to zero |
|
capture->myMovie = 0; |
|
capture->myGWorld = nil; |
|
|
|
// initialize numbers with invalid values |
|
capture->next_frame_time = -1; |
|
capture->next_frame_number = -1; |
|
capture->number_of_frames = -1; |
|
capture->movie_start_time = -1; |
|
capture->size = cvSize (-1,-1); |
|
|
|
|
|
// we would use CFStringCreateWithFileSystemRepresentation (kCFAllocatorDefault, filename) on Mac OS X 10.4 |
|
CFStringRef inPath = CFStringCreateWithCString (kCFAllocatorDefault, filename, kCFStringEncodingISOLatin1); |
|
OPENCV_ASSERT ((inPath != nil), "icvOpenFile_QT_Movie", "couldnt create CFString from a string"); |
|
|
|
// create the data reference |
|
myErr = QTNewDataReferenceFromFullPathCFString (inPath, kQTPOSIXPathStyle, 0, & myDataRef, & myDataRefType); |
|
if (myErr != noErr) |
|
{ |
|
fprintf (stderr, "Couldn't create QTNewDataReferenceFromFullPathCFString().\n"); |
|
return 0; |
|
} |
|
|
|
// get the Movie |
|
myErr = NewMovieFromDataRef(& capture->myMovie, newMovieActive | newMovieAsyncOK /* | newMovieIdleImportOK */, |
|
& myResID, myDataRef, myDataRefType); |
|
|
|
// dispose of the data reference handle - we no longer need it |
|
DisposeHandle (myDataRef); |
|
|
|
// if NewMovieFromDataRef failed, we already disposed the DataRef, so just return with an error |
|
if (myErr != noErr) |
|
{ |
|
fprintf (stderr, "Couldn't create a NewMovieFromDataRef() - error is %d.\n", myErr); |
|
return 0; |
|
} |
|
|
|
// count the number of video 'frames' in the movie by stepping through all of the |
|
// video 'interesting times', or in other words, the places where the movie displays |
|
// a new video sample. The time between these interesting times is not necessarily constant. |
|
{ |
|
OSType whichMediaType = VisualMediaCharacteristic; |
|
TimeValue theTime = -1; |
|
|
|
// find out movie start time |
|
GetMovieNextInterestingTime (capture->myMovie, short (nextTimeMediaSample + nextTimeEdgeOK), |
|
1, & whichMediaType, TimeValue (0), 0, & theTime, NULL); |
|
if (theTime == -1) |
|
{ |
|
fprintf (stderr, "Couldn't inquire first frame time\n"); |
|
return 0; |
|
} |
|
capture->movie_start_time = theTime; |
|
capture->next_frame_time = theTime; |
|
capture->next_frame_number = 0; |
|
|
|
// count all 'interesting times' of the movie |
|
capture->number_of_frames = 0; |
|
while (theTime >= 0) |
|
{ |
|
GetMovieNextInterestingTime (capture->myMovie, short (nextTimeMediaSample), |
|
1, & whichMediaType, theTime, 0, & theTime, NULL); |
|
capture->number_of_frames++; |
|
} |
|
} |
|
|
|
// get the bounding rectangle of the movie |
|
GetMoviesError (); |
|
GetMovieBox (capture->myMovie, & myRect); |
|
capture->size = cvSize (myRect.right - myRect.left, myRect.bottom - myRect.top); |
|
|
|
// create gworld for decompressed image |
|
myErr = QTNewGWorld (& capture->myGWorld, k32ARGBPixelFormat /* k24BGRPixelFormat geht leider nicht */, |
|
& myRect, nil, nil, 0); |
|
OPENCV_ASSERT (myErr == noErr, "icvOpenFile_QT_Movie", "couldnt create QTNewGWorld() for output image"); |
|
SetMovieGWorld (capture->myMovie, capture->myGWorld, nil); |
|
|
|
// build IplImage header that will point to the PixMap of the Movie's GWorld later on |
|
capture->image_rgb = cvCreateImageHeader (capture->size, IPL_DEPTH_8U, 4); |
|
|
|
// create IplImage that hold correctly formatted result |
|
capture->image_bgr = cvCreateImage (capture->size, IPL_DEPTH_8U, 3); |
|
|
|
// okay, that's it - should we wait until the Movie is playable? |
|
return 1; |
|
} |
|
|
|
/** |
|
* dispose of QuickTime Movie and free memory buffers |
|
* |
|
* @author Mark Asbach <asbach@ient.rwth-aachen.de> |
|
* @date 2005-11-04 |
|
*/ |
|
static int icvClose_QT_Movie (CvCapture_QT_Movie * capture) |
|
{ |
|
OPENCV_ASSERT (capture, "icvClose_QT_Movie", "'capture' is a NULL-pointer"); |
|
|
|
// deallocate and free resources |
|
if (capture->myMovie) |
|
{ |
|
cvReleaseImage (& capture->image_bgr); |
|
cvReleaseImageHeader (& capture->image_rgb); |
|
DisposeGWorld (capture->myGWorld); |
|
DisposeMovie (capture->myMovie); |
|
} |
|
|
|
// okay, that's it |
|
return 1; |
|
} |
|
|
|
/** |
|
* get a capture property |
|
* |
|
* @author Mark Asbach <asbach@ient.rwth-aachen.de> |
|
* @date 2005-11-05 |
|
*/ |
|
static double icvGetProperty_QT_Movie (CvCapture_QT_Movie * capture, int property_id) |
|
{ |
|
OPENCV_ASSERT (capture, "icvGetProperty_QT_Movie", "'capture' is a NULL-pointer"); |
|
OPENCV_ASSERT (capture->myMovie, "icvGetProperty_QT_Movie", "invalid Movie handle"); |
|
OPENCV_ASSERT (capture->number_of_frames > 0, "icvGetProperty_QT_Movie", "movie has invalid number of frames"); |
|
OPENCV_ASSERT (capture->movie_start_time >= 0, "icvGetProperty_QT_Movie", "movie has invalid start time"); |
|
|
|
// inquire desired property |
|
switch (property_id) |
|
{ |
|
case CV_CAP_PROP_POS_FRAMES: |
|
return (capture->next_frame_number); |
|
|
|
case CV_CAP_PROP_POS_MSEC: |
|
case CV_CAP_PROP_POS_AVI_RATIO: |
|
{ |
|
TimeValue position = capture->next_frame_time - capture->movie_start_time; |
|
|
|
if (property_id == CV_CAP_PROP_POS_MSEC) |
|
{ |
|
TimeScale timescale = GetMovieTimeScale (capture->myMovie); |
|
return (static_cast<double> (position) * 1000.0 / timescale); |
|
} |
|
else |
|
{ |
|
TimeValue duration = GetMovieDuration (capture->myMovie); |
|
return (static_cast<double> (position) / duration); |
|
} |
|
} |
|
break; // never reached |
|
|
|
case CV_CAP_PROP_FRAME_WIDTH: |
|
return static_cast<double> (capture->size.width); |
|
|
|
case CV_CAP_PROP_FRAME_HEIGHT: |
|
return static_cast<double> (capture->size.height); |
|
|
|
case CV_CAP_PROP_FPS: |
|
{ |
|
TimeValue duration = GetMovieDuration (capture->myMovie); |
|
TimeScale timescale = GetMovieTimeScale (capture->myMovie); |
|
|
|
return (capture->number_of_frames / (static_cast<double> (duration) / timescale)); |
|
} |
|
|
|
case CV_CAP_PROP_FRAME_COUNT: |
|
return static_cast<double> (capture->number_of_frames); |
|
|
|
case CV_CAP_PROP_FOURCC: // not implemented |
|
case CV_CAP_PROP_FORMAT: // not implemented |
|
case CV_CAP_PROP_MODE: // not implemented |
|
default: |
|
// unhandled or unknown capture property |
|
OPENCV_ERROR (CV_StsBadArg, "icvSetProperty_QT_Movie", "unknown or unhandled property_id"); |
|
return CV_StsBadArg; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/** |
|
* set a capture property. With movie files, it is only possible to set the |
|
* position (i.e. jump to a given time or frame number) |
|
* |
|
* @author Mark Asbach <asbach@ient.rwth-aachen.de> |
|
* @date 2005-11-05 |
|
*/ |
|
static int icvSetProperty_QT_Movie (CvCapture_QT_Movie * capture, int property_id, double value) |
|
{ |
|
OPENCV_ASSERT (capture, "icvSetProperty_QT_Movie", "'capture' is a NULL-pointer"); |
|
OPENCV_ASSERT (capture->myMovie, "icvSetProperty_QT_Movie", "invalid Movie handle"); |
|
OPENCV_ASSERT (capture->number_of_frames > 0, "icvSetProperty_QT_Movie", "movie has invalid number of frames"); |
|
OPENCV_ASSERT (capture->movie_start_time >= 0, "icvSetProperty_QT_Movie", "movie has invalid start time"); |
|
|
|
// inquire desired property |
|
// |
|
// rework these three points to really work through 'interesting times'. |
|
// with the current implementation, they result in wrong times or wrong frame numbers with content that |
|
// features varying frame durations |
|
switch (property_id) |
|
{ |
|
case CV_CAP_PROP_POS_MSEC: |
|
case CV_CAP_PROP_POS_AVI_RATIO: |
|
{ |
|
TimeValue destination; |
|
OSType myType = VisualMediaCharacteristic; |
|
OSErr myErr = noErr; |
|
|
|
if (property_id == CV_CAP_PROP_POS_MSEC) |
|
{ |
|
TimeScale timescale = GetMovieTimeScale (capture->myMovie); |
|
destination = static_cast<TimeValue> (value / 1000.0 * timescale + capture->movie_start_time); |
|
} |
|
else |
|
{ |
|
TimeValue duration = GetMovieDuration (capture->myMovie); |
|
destination = static_cast<TimeValue> (value * duration + capture->movie_start_time); |
|
} |
|
|
|
// really seek? |
|
if (capture->next_frame_time == destination) |
|
break; |
|
|
|
// seek into which direction? |
|
if (capture->next_frame_time < destination) |
|
{ |
|
while (capture->next_frame_time < destination) |
|
{ |
|
capture->next_frame_number++; |
|
GetMovieNextInterestingTime (capture->myMovie, nextTimeStep, 1, & myType, capture->next_frame_time, |
|
1, & capture->next_frame_time, NULL); |
|
myErr = GetMoviesError(); |
|
if (myErr != noErr) |
|
{ |
|
fprintf (stderr, "Couldn't go on to GetMovieNextInterestingTime() in icvGrabFrame_QT.\n"); |
|
return 0; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
while (capture->next_frame_time > destination) |
|
{ |
|
capture->next_frame_number--; |
|
GetMovieNextInterestingTime (capture->myMovie, nextTimeStep, 1, & myType, capture->next_frame_time, |
|
-1, & capture->next_frame_time, NULL); |
|
myErr = GetMoviesError(); |
|
if (myErr != noErr) |
|
{ |
|
fprintf (stderr, "Couldn't go back to GetMovieNextInterestingTime() in icvGrabFrame_QT.\n"); |
|
return 0; |
|
} |
|
} |
|
} |
|
} |
|
break; |
|
|
|
case CV_CAP_PROP_POS_FRAMES: |
|
{ |
|
TimeValue destination = static_cast<TimeValue> (value); |
|
short direction = (destination > capture->next_frame_number) ? 1 : -1; |
|
OSType myType = VisualMediaCharacteristic; |
|
OSErr myErr = noErr; |
|
|
|
while (destination != capture->next_frame_number) |
|
{ |
|
capture->next_frame_number += direction; |
|
GetMovieNextInterestingTime (capture->myMovie, nextTimeStep, 1, & myType, capture->next_frame_time, |
|
direction, & capture->next_frame_time, NULL); |
|
myErr = GetMoviesError(); |
|
if (myErr != noErr) |
|
{ |
|
fprintf (stderr, "Couldn't step to desired frame number in icvGrabFrame_QT.\n"); |
|
return 0; |
|
} |
|
} |
|
} |
|
break; |
|
|
|
default: |
|
// unhandled or unknown capture property |
|
OPENCV_ERROR (CV_StsBadArg, "icvSetProperty_QT_Movie", "unknown or unhandled property_id"); |
|
return 0; |
|
} |
|
|
|
// positive result means success |
|
return 1; |
|
} |
|
|
|
/** |
|
* the original meaning of this method is to acquire raw frame data for the next video |
|
* frame but not decompress it. With the QuickTime video reader, this is reduced to |
|
* advance to the current frame time. |
|
* |
|
* @author Mark Asbach <asbach@ient.rwth-aachen.de> |
|
* @date 2005-11-06 |
|
*/ |
|
static int icvGrabFrame_QT_Movie (CvCapture_QT_Movie * capture) |
|
{ |
|
OPENCV_ASSERT (capture, "icvGrabFrame_QT_Movie", "'capture' is a NULL-pointer"); |
|
OPENCV_ASSERT (capture->myMovie, "icvGrabFrame_QT_Movie", "invalid Movie handle"); |
|
|
|
TimeValue myCurrTime; |
|
OSType myType = VisualMediaCharacteristic; |
|
OSErr myErr = noErr; |
|
|
|
|
|
// jump to current video sample |
|
SetMovieTimeValue (capture->myMovie, capture->next_frame_time); |
|
myErr = GetMoviesError(); |
|
if (myErr != noErr) |
|
{ |
|
fprintf (stderr, "Couldn't SetMovieTimeValue() in icvGrabFrame_QT_Movie.\n"); |
|
return 0; |
|
} |
|
|
|
// where are we now? |
|
myCurrTime = GetMovieTime (capture->myMovie, NULL); |
|
|
|
// increment counters |
|
capture->next_frame_number++; |
|
GetMovieNextInterestingTime (capture->myMovie, nextTimeStep, 1, & myType, myCurrTime, 1, & capture->next_frame_time, NULL); |
|
myErr = GetMoviesError(); |
|
if (myErr != noErr) |
|
{ |
|
fprintf (stderr, "Couldn't GetMovieNextInterestingTime() in icvGrabFrame_QT_Movie.\n"); |
|
return 0; |
|
} |
|
|
|
// that's it |
|
return 1; |
|
} |
|
|
|
/** |
|
* render the current frame into an image buffer and convert to OpenCV IplImage |
|
* buffer layout (BGR sampling) |
|
* |
|
* @author Mark Asbach <asbach@ient.rwth-aachen.de> |
|
* @date 2005-11-06 |
|
*/ |
|
static const void * icvRetrieveFrame_QT_Movie (CvCapture_QT_Movie * capture, int) |
|
{ |
|
OPENCV_ASSERT (capture, "icvRetrieveFrame_QT_Movie", "'capture' is a NULL-pointer"); |
|
OPENCV_ASSERT (capture->myMovie, "icvRetrieveFrame_QT_Movie", "invalid Movie handle"); |
|
OPENCV_ASSERT (capture->image_rgb, "icvRetrieveFrame_QT_Movie", "invalid source image"); |
|
OPENCV_ASSERT (capture->image_bgr, "icvRetrieveFrame_QT_Movie", "invalid destination image"); |
|
|
|
PixMapHandle myPixMapHandle = nil; |
|
OSErr myErr = noErr; |
|
|
|
|
|
// invalidates the movie's display state so that the Movie Toolbox |
|
// redraws the movie the next time we call MoviesTask |
|
UpdateMovie (capture->myMovie); |
|
myErr = GetMoviesError (); |
|
if (myErr != noErr) |
|
{ |
|
fprintf (stderr, "Couldn't UpdateMovie() in icvRetrieveFrame_QT_Movie().\n"); |
|
return 0; |
|
} |
|
|
|
// service active movie (= redraw immediately) |
|
MoviesTask (capture->myMovie, 0L); |
|
myErr = GetMoviesError (); |
|
if (myErr != noErr) |
|
{ |
|
fprintf (stderr, "MoviesTask() didn't succeed in icvRetrieveFrame_QT_Movie().\n"); |
|
return 0; |
|
} |
|
|
|
// update IplImage header that points to PixMap of the Movie's GWorld. |
|
// unfortunately, cvCvtColor doesn't know ARGB, the QuickTime pixel format, |
|
// so we pass a modfied address. |
|
// ATTENTION: don't access the last pixel's alpha entry, it's inexistant |
|
myPixMapHandle = GetGWorldPixMap (capture->myGWorld); |
|
LockPixels (myPixMapHandle); |
|
cvSetData (capture->image_rgb, GetPixBaseAddr (myPixMapHandle) + 1, GetPixRowBytes (myPixMapHandle)); |
|
|
|
// covert RGB of GWorld to BGR |
|
cvCvtColor (capture->image_rgb, capture->image_bgr, CV_RGBA2BGR); |
|
|
|
// allow QuickTime to access the buffer again |
|
UnlockPixels (myPixMapHandle); |
|
|
|
// always return the same image pointer |
|
return capture->image_bgr; |
|
} |
|
|
|
|
|
// ---------------------------------------------------------------------------------------- |
|
#pragma mark - |
|
#pragma mark Capturing from Video Cameras |
|
|
|
#ifdef USE_VDIG_VERSION |
|
|
|
/// SequenceGrabber state structure for QuickTime |
|
typedef struct CvCapture_QT_Cam_vdig |
|
{ |
|
ComponentInstance grabber; |
|
short channel; |
|
GWorldPtr myGWorld; |
|
PixMapHandle pixmap; |
|
|
|
CvSize size; |
|
long number_of_frames; |
|
|
|
IplImage * image_rgb; // will point to the PixMap of myGWorld |
|
IplImage * image_bgr; // will be returned by icvRetrieveFrame_QT() |
|
|
|
} CvCapture_QT_Cam; |
|
|
|
#else |
|
|
|
typedef struct CvCapture_QT_Cam_barg |
|
{ |
|
SeqGrabComponent grabber; |
|
SGChannel channel; |
|
GWorldPtr gworld; |
|
Rect bounds; |
|
ImageSequence sequence; |
|
|
|
volatile bool got_frame; |
|
|
|
CvSize size; |
|
IplImage * image_rgb; // will point to the PixMap of myGWorld |
|
IplImage * image_bgr; // will be returned by icvRetrieveFrame_QT() |
|
|
|
} CvCapture_QT_Cam; |
|
|
|
#endif |
|
|
|
static int icvOpenCamera_QT (CvCapture_QT_Cam * capture, const int index); |
|
static int icvClose_QT_Cam (CvCapture_QT_Cam * capture); |
|
static double icvGetProperty_QT_Cam (CvCapture_QT_Cam * capture, int property_id); |
|
static int icvSetProperty_QT_Cam (CvCapture_QT_Cam * capture, int property_id, double value); |
|
static int icvGrabFrame_QT_Cam (CvCapture_QT_Cam * capture); |
|
static const void * icvRetrieveFrame_QT_Cam (CvCapture_QT_Cam * capture, int); |
|
|
|
|
|
/** |
|
* Initialize memory structure and call method to open camera |
|
* |
|
* @author Mark Asbach <asbach@ient.rwth-aachen.de> |
|
* @date 2006-01-29 |
|
*/ |
|
static CvCapture_QT_Cam * icvCaptureFromCam_QT (const int index) |
|
{ |
|
if (! did_enter_movies) |
|
{ |
|
EnterMovies(); |
|
did_enter_movies = 1; |
|
} |
|
|
|
CvCapture_QT_Cam * capture = 0; |
|
|
|
if (index >= 0) |
|
{ |
|
capture = (CvCapture_QT_Cam *) cvAlloc (sizeof (*capture)); |
|
memset (capture, 0, sizeof(*capture)); |
|
|
|
if (!icvOpenCamera_QT (capture, index)) |
|
cvFree (&capture); |
|
} |
|
|
|
return capture; |
|
} |
|
|
|
/// capture properties currently unimplemented for QuickTime camera interface |
|
static double icvGetProperty_QT_Cam (CvCapture_QT_Cam * capture, int property_id) |
|
{ |
|
assert (0); |
|
return 0; |
|
} |
|
|
|
/// capture properties currently unimplemented for QuickTime camera interface |
|
static int icvSetProperty_QT_Cam (CvCapture_QT_Cam * capture, int property_id, double value) |
|
{ |
|
assert (0); |
|
return 0; |
|
} |
|
|
|
#ifdef USE_VDIG_VERSION |
|
#pragma mark Capturing using VDIG |
|
|
|
/** |
|
* Open a quicktime video grabber component. This could be an attached |
|
* IEEE1394 camera, a web cam, an iSight or digitizer card / video converter. |
|
* |
|
* @author Mark Asbach <asbach@ient.rwth-aachen.de> |
|
* @date 2006-01-29 |
|
*/ |
|
static int icvOpenCamera_QT (CvCapture_QT_Cam * capture, const int index) |
|
{ |
|
OPENCV_ASSERT (capture, "icvOpenCamera_QT", "'capture' is a NULL-pointer"); |
|
OPENCV_ASSERT (index >=0, "icvOpenCamera_QT", "camera index is negative"); |
|
|
|
ComponentDescription component_description; |
|
Component component = 0; |
|
int number_of_inputs = 0; |
|
Rect myRect; |
|
ComponentResult result = noErr; |
|
|
|
|
|
// travers all components and count video digitizer channels |
|
component_description.componentType = videoDigitizerComponentType; |
|
component_description.componentSubType = 0L; |
|
component_description.componentManufacturer = 0L; |
|
component_description.componentFlags = 0L; |
|
component_description.componentFlagsMask = 0L; |
|
do |
|
{ |
|
// traverse component list |
|
component = FindNextComponent (component, & component_description); |
|
|
|
// found a component? |
|
if (component) |
|
{ |
|
// dump component name |
|
#ifndef NDEBUG |
|
ComponentDescription desc; |
|
Handle nameHandle = NewHandleClear (200); |
|
char nameBuffer [255]; |
|
|
|
result = GetComponentInfo (component, & desc, nameHandle, nil, nil); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt GetComponentInfo()"); |
|
OPENCV_ASSERT (*nameHandle, "icvOpenCamera_QT", "No name returned by GetComponentInfo()"); |
|
snprintf (nameBuffer, (**nameHandle) + 1, "%s", (char *) (* nameHandle + 1)); |
|
printf ("- Videodevice: %s\n", nameBuffer); |
|
DisposeHandle (nameHandle); |
|
#endif |
|
|
|
// open component to count number of inputs |
|
capture->grabber = OpenComponent (component); |
|
if (capture->grabber) |
|
{ |
|
result = VDGetNumberOfInputs (capture->grabber, & capture->channel); |
|
if (result != noErr) |
|
fprintf (stderr, "Couldnt GetNumberOfInputs: %d\n", (int) result); |
|
else |
|
{ |
|
#ifndef NDEBUG |
|
printf (" Number of inputs: %d\n", (int) capture->channel + 1); |
|
#endif |
|
|
|
// add to overall number of inputs |
|
number_of_inputs += capture->channel + 1; |
|
|
|
// did the user select an input that falls into this device's |
|
// range of inputs? Then leave the loop |
|
if (number_of_inputs > index) |
|
{ |
|
// calculate relative channel index |
|
capture->channel = index - number_of_inputs + capture->channel + 1; |
|
OPENCV_ASSERT (capture->channel >= 0, "icvOpenCamera_QT", "negative channel number"); |
|
|
|
// dump channel name |
|
#ifndef NDEBUG |
|
char name[256]; |
|
Str255 nameBuffer; |
|
|
|
result = VDGetInputName (capture->grabber, capture->channel, nameBuffer); |
|
OPENCV_ASSERT (result == noErr, "ictOpenCamera_QT", "couldnt GetInputName()"); |
|
snprintf (name, *nameBuffer, "%s", (char *) (nameBuffer + 1)); |
|
printf (" Choosing input %d - %s\n", (int) capture->channel, name); |
|
#endif |
|
|
|
// leave the loop |
|
break; |
|
} |
|
} |
|
|
|
// obviously no inputs of this device/component were needed |
|
CloseComponent (capture->grabber); |
|
} |
|
} |
|
} |
|
while (component); |
|
|
|
// did we find the desired input? |
|
if (! component) |
|
{ |
|
fprintf(stderr, "Not enough inputs available - can't choose input %d\n", index); |
|
return 0; |
|
} |
|
|
|
// -- Okay now, we selected the digitizer input, lets set up digitizer destination -- |
|
|
|
ClearMoviesStickyError(); |
|
|
|
// Select the desired input |
|
result = VDSetInput (capture->grabber, capture->channel); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt select video digitizer input"); |
|
|
|
// get the bounding rectangle of the video digitizer |
|
result = VDGetActiveSrcRect (capture->grabber, capture->channel, & myRect); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt create VDGetActiveSrcRect from digitizer"); |
|
myRect.right = 640; myRect.bottom = 480; |
|
capture->size = cvSize (myRect.right - myRect.left, myRect.bottom - myRect.top); |
|
printf ("Source rect is %d, %d -- %d, %d\n", (int) myRect.left, (int) myRect.top, (int) myRect.right, (int) myRect.bottom); |
|
|
|
// create offscreen GWorld |
|
result = QTNewGWorld (& capture->myGWorld, k32ARGBPixelFormat, & myRect, nil, nil, 0); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt create QTNewGWorld() for output image"); |
|
|
|
// get pixmap |
|
capture->pixmap = GetGWorldPixMap (capture->myGWorld); |
|
result = GetMoviesError (); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt get pixmap"); |
|
|
|
// set digitizer rect |
|
result = VDSetDigitizerRect (capture->grabber, & myRect); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt create VDGetActiveSrcRect from digitizer"); |
|
|
|
// set destination of digitized input |
|
result = VDSetPlayThruDestination (capture->grabber, capture->pixmap, & myRect, nil, nil); |
|
printf ("QuickTime error: %d\n", (int) result); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set video destination"); |
|
|
|
// get destination of digitized images |
|
result = VDGetPlayThruDestination (capture->grabber, & capture->pixmap, nil, nil, nil); |
|
printf ("QuickTime error: %d\n", (int) result); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt get video destination"); |
|
OPENCV_ASSERT (capture->pixmap != nil, "icvOpenCamera_QT", "empty set video destination"); |
|
|
|
// get the bounding rectangle of the video digitizer |
|
GetPixBounds (capture->pixmap, & myRect); |
|
capture->size = cvSize (myRect.right - myRect.left, myRect.bottom - myRect.top); |
|
|
|
// build IplImage header that will point to the PixMap of the Movie's GWorld later on |
|
capture->image_rgb = cvCreateImageHeader (capture->size, IPL_DEPTH_8U, 4); |
|
OPENCV_ASSERT (capture->image_rgb, "icvOpenCamera_QT", "couldnt create image header"); |
|
|
|
// create IplImage that hold correctly formatted result |
|
capture->image_bgr = cvCreateImage (capture->size, IPL_DEPTH_8U, 3); |
|
OPENCV_ASSERT (capture->image_bgr, "icvOpenCamera_QT", "couldnt create image"); |
|
|
|
// notify digitizer component, that we well be starting grabbing soon |
|
result = VDCaptureStateChanging (capture->grabber, vdFlagCaptureIsForRecord | vdFlagCaptureStarting | vdFlagCaptureLowLatency); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set capture state"); |
|
|
|
|
|
// yeah, we did it |
|
return 1; |
|
} |
|
|
|
static int icvClose_QT_Cam (CvCapture_QT_Cam * capture) |
|
{ |
|
OPENCV_ASSERT (capture, "icvClose_QT_Cam", "'capture' is a NULL-pointer"); |
|
|
|
ComponentResult result = noErr; |
|
|
|
// notify digitizer component, that we well be stopping grabbing soon |
|
result = VDCaptureStateChanging (capture->grabber, vdFlagCaptureStopping); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set capture state"); |
|
|
|
// release memory |
|
cvReleaseImage (& capture->image_bgr); |
|
cvReleaseImageHeader (& capture->image_rgb); |
|
DisposeGWorld (capture->myGWorld); |
|
CloseComponent (capture->grabber); |
|
|
|
// sucessful |
|
return 1; |
|
} |
|
|
|
static int icvGrabFrame_QT_Cam (CvCapture_QT_Cam * capture) |
|
{ |
|
OPENCV_ASSERT (capture, "icvGrabFrame_QT_Cam", "'capture' is a NULL-pointer"); |
|
OPENCV_ASSERT (capture->grabber, "icvGrabFrame_QT_Cam", "'grabber' is a NULL-pointer"); |
|
|
|
ComponentResult result = noErr; |
|
|
|
// grab one frame |
|
result = VDGrabOneFrame (capture->grabber); |
|
if (result != noErr) |
|
{ |
|
fprintf (stderr, "VDGrabOneFrame failed\n"); |
|
return 0; |
|
} |
|
|
|
// successful |
|
return 1; |
|
} |
|
|
|
static const void * icvRetrieveFrame_QT_Cam (CvCapture_QT_Cam * capture, int) |
|
{ |
|
OPENCV_ASSERT (capture, "icvRetrieveFrame_QT_Cam", "'capture' is a NULL-pointer"); |
|
|
|
PixMapHandle myPixMapHandle = nil; |
|
|
|
// update IplImage header that points to PixMap of the Movie's GWorld. |
|
// unfortunately, cvCvtColor doesn't know ARGB, the QuickTime pixel format, |
|
// so we pass a modfied address. |
|
// ATTENTION: don't access the last pixel's alpha entry, it's inexistant |
|
//myPixMapHandle = GetGWorldPixMap (capture->myGWorld); |
|
myPixMapHandle = capture->pixmap; |
|
LockPixels (myPixMapHandle); |
|
cvSetData (capture->image_rgb, GetPixBaseAddr (myPixMapHandle) + 1, GetPixRowBytes (myPixMapHandle)); |
|
|
|
// covert RGB of GWorld to BGR |
|
cvCvtColor (capture->image_rgb, capture->image_bgr, CV_RGBA2BGR); |
|
|
|
// allow QuickTime to access the buffer again |
|
UnlockPixels (myPixMapHandle); |
|
|
|
// always return the same image pointer |
|
return capture->image_bgr; |
|
} |
|
|
|
#else |
|
#pragma mark Capturing using Sequence Grabber |
|
|
|
static OSErr icvDataProc_QT_Cam (SGChannel channel, Ptr raw_data, long len, long *, long, TimeValue, short, long refCon) |
|
{ |
|
CvCapture_QT_Cam * capture = (CvCapture_QT_Cam *) refCon; |
|
CodecFlags ignore; |
|
ComponentResult err = noErr; |
|
|
|
|
|
// we need valid pointers |
|
OPENCV_ASSERT (capture, "icvDataProc_QT_Cam", "'capture' is a NULL-pointer"); |
|
OPENCV_ASSERT (capture->gworld, "icvDataProc_QT_Cam", "'gworld' is a NULL-pointer"); |
|
OPENCV_ASSERT (raw_data, "icvDataProc_QT_Cam", "'raw_data' is a NULL-pointer"); |
|
|
|
// create a decompression sequence the first time |
|
if (capture->sequence == 0) |
|
{ |
|
ImageDescriptionHandle description = (ImageDescriptionHandle) NewHandle(0); |
|
|
|
// we need a decompression sequence that fits the raw data coming from the camera |
|
err = SGGetChannelSampleDescription (channel, (Handle) description); |
|
OPENCV_ASSERT (err == noErr, "icvDataProc_QT_Cam", "couldnt get channel sample description"); |
|
|
|
//*************************************************************************************// |
|
//This fixed a bug when Quicktime is called twice to grab a frame (black band bug) - Yannick Verdie 2010 |
|
Rect sourceRect; |
|
sourceRect.top = 0; |
|
sourceRect.left = 0; |
|
sourceRect.right = (**description).width; |
|
sourceRect.bottom = (**description).height; |
|
|
|
MatrixRecord scaleMatrix; |
|
RectMatrix(&scaleMatrix,&sourceRect,&capture->bounds); |
|
|
|
err = DecompressSequenceBegin (&capture->sequence, description, capture->gworld, 0,&capture->bounds,&scaleMatrix, srcCopy, NULL, 0, codecNormalQuality, bestSpeedCodec); |
|
//**************************************************************************************// |
|
|
|
OPENCV_ASSERT (err == noErr, "icvDataProc_QT_Cam", "couldnt begin decompression sequence"); |
|
DisposeHandle ((Handle) description); |
|
} |
|
|
|
// okay, we have a decompression sequence -> decompress! |
|
err = DecompressSequenceFrameS (capture->sequence, raw_data, len, 0, &ignore, nil); |
|
if (err != noErr) |
|
{ |
|
fprintf (stderr, "icvDataProc_QT_Cam: couldn't decompress frame - %d\n", (int) err); |
|
return err; |
|
} |
|
|
|
// check if we dropped a frame |
|
/*#ifndef NDEBUG |
|
if (capture->got_frame) |
|
fprintf (stderr, "icvDataProc_QT_Cam: frame was dropped\n"); |
|
#endif*/ |
|
|
|
// everything worked as expected |
|
capture->got_frame = true; |
|
return noErr; |
|
} |
|
|
|
|
|
static int icvOpenCamera_QT (CvCapture_QT_Cam * capture, const int index) |
|
{ |
|
OPENCV_ASSERT (capture, "icvOpenCamera_QT", "'capture' is a NULL-pointer"); |
|
OPENCV_ASSERT (index >= 0, "icvOpenCamera_QT", "camera index is negative"); |
|
|
|
PixMapHandle pixmap = nil; |
|
OSErr result = noErr; |
|
|
|
// open sequence grabber component |
|
capture->grabber = OpenDefaultComponent (SeqGrabComponentType, 0); |
|
OPENCV_ASSERT (capture->grabber, "icvOpenCamera_QT", "couldnt create image"); |
|
|
|
// initialize sequence grabber component |
|
result = SGInitialize (capture->grabber); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt initialize sequence grabber"); |
|
result = SGSetDataRef (capture->grabber, 0, 0, seqGrabDontMakeMovie); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set data reference of sequence grabber"); |
|
|
|
// set up video channel |
|
result = SGNewChannel (capture->grabber, VideoMediaType, & (capture->channel)); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt create new video channel"); |
|
|
|
// select the camera indicated by index |
|
SGDeviceList device_list = 0; |
|
result = SGGetChannelDeviceList (capture->channel, 0, & device_list); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt get channel device list"); |
|
for (int i = 0, current_index = 1; i < (*device_list)->count; i++) |
|
{ |
|
SGDeviceName device = (*device_list)->entry[i]; |
|
if (device.flags == 0) |
|
{ |
|
if (current_index == index) |
|
{ |
|
result = SGSetChannelDevice (capture->channel, device.name); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set the channel video device"); |
|
break; |
|
} |
|
current_index++; |
|
} |
|
} |
|
result = SGDisposeDeviceList (capture->grabber, device_list); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt dispose the channel device list"); |
|
|
|
// query natural camera resolution -- this will be wrong, but will be an upper |
|
// bound on the actual resolution -- the actual resolution is set below |
|
// after starting the frame grabber |
|
result = SGGetSrcVideoBounds (capture->channel, & (capture->bounds)); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set video channel bounds"); |
|
|
|
// create offscreen GWorld |
|
result = QTNewGWorld (& (capture->gworld), k32ARGBPixelFormat, & (capture->bounds), 0, 0, 0); |
|
result = SGSetGWorld (capture->grabber, capture->gworld, 0); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set GWorld for sequence grabber"); |
|
result = SGSetChannelBounds (capture->channel, & (capture->bounds)); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set video channel bounds"); |
|
result = SGSetChannelUsage (capture->channel, seqGrabRecord); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set channel usage"); |
|
|
|
// start recording so we can size |
|
result = SGStartRecord (capture->grabber); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt start recording"); |
|
|
|
// don't know *actual* resolution until now |
|
ImageDescriptionHandle imageDesc = (ImageDescriptionHandle)NewHandle(0); |
|
result = SGGetChannelSampleDescription(capture->channel, (Handle)imageDesc); |
|
OPENCV_ASSERT( result == noErr, "icvOpenCamera_QT", "couldn't get image size"); |
|
capture->bounds.right = (**imageDesc).width; |
|
capture->bounds.bottom = (**imageDesc).height; |
|
DisposeHandle ((Handle) imageDesc); |
|
|
|
// stop grabber so that we can reset the parameters to the right size |
|
result = SGStop (capture->grabber); |
|
OPENCV_ASSERT (result == noErr, "icveClose_QT_Cam", "couldnt stop recording"); |
|
|
|
// reset GWorld to correct image size |
|
GWorldPtr tmpgworld; |
|
result = QTNewGWorld( &tmpgworld, k32ARGBPixelFormat, &(capture->bounds), 0, 0, 0); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt create offscreen GWorld"); |
|
result = SGSetGWorld( capture->grabber, tmpgworld, 0); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set GWorld for sequence grabber"); |
|
DisposeGWorld( capture->gworld ); |
|
capture->gworld = tmpgworld; |
|
|
|
result = SGSetChannelBounds (capture->channel, & (capture->bounds)); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set video channel bounds"); |
|
|
|
// allocate images |
|
capture->size = cvSize (capture->bounds.right - capture->bounds.left, capture->bounds.bottom - capture->bounds.top); |
|
|
|
// build IplImage header that points to the PixMap of the Movie's GWorld. |
|
// unfortunately, cvCvtColor doesn't know ARGB, the QuickTime pixel format, |
|
// so we shift the base address by one byte. |
|
// ATTENTION: don't access the last pixel's alpha entry, it's inexistant |
|
capture->image_rgb = cvCreateImageHeader (capture->size, IPL_DEPTH_8U, 4); |
|
OPENCV_ASSERT (capture->image_rgb, "icvOpenCamera_QT", "couldnt create image header"); |
|
pixmap = GetGWorldPixMap (capture->gworld); |
|
OPENCV_ASSERT (pixmap, "icvOpenCamera_QT", "didn't get GWorld PixMap handle"); |
|
LockPixels (pixmap); |
|
cvSetData (capture->image_rgb, GetPixBaseAddr (pixmap) + 1, GetPixRowBytes (pixmap)); |
|
|
|
// create IplImage that hold correctly formatted result |
|
capture->image_bgr = cvCreateImage (capture->size, IPL_DEPTH_8U, 3); |
|
OPENCV_ASSERT (capture->image_bgr, "icvOpenCamera_QT", "couldnt create image"); |
|
|
|
|
|
// tell the sequence grabber to invoke our data proc |
|
result = SGSetDataProc (capture->grabber, NewSGDataUPP (icvDataProc_QT_Cam), (long) capture); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt set data proc"); |
|
|
|
// start recording |
|
result = SGStartRecord (capture->grabber); |
|
OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldnt start recording"); |
|
|
|
return 1; |
|
} |
|
|
|
|
|
static int icvClose_QT_Cam (CvCapture_QT_Cam * capture) |
|
{ |
|
OPENCV_ASSERT (capture, "icvClose_QT_Cam", "'capture' is a NULL-pointer"); |
|
|
|
OSErr result = noErr; |
|
|
|
|
|
// stop recording |
|
result = SGStop (capture->grabber); |
|
OPENCV_ASSERT (result == noErr, "icveClose_QT_Cam", "couldnt stop recording"); |
|
|
|
// close sequence grabber component |
|
result = CloseComponent (capture->grabber); |
|
OPENCV_ASSERT (result == noErr, "icveClose_QT_Cam", "couldnt close sequence grabber component"); |
|
|
|
// end decompression sequence |
|
CDSequenceEnd (capture->sequence); |
|
|
|
// free memory |
|
cvReleaseImage (& capture->image_bgr); |
|
cvReleaseImageHeader (& capture->image_rgb); |
|
DisposeGWorld (capture->gworld); |
|
|
|
// sucessful |
|
return 1; |
|
} |
|
|
|
static int icvGrabFrame_QT_Cam (CvCapture_QT_Cam * capture) |
|
{ |
|
OPENCV_ASSERT (capture, "icvGrabFrame_QT_Cam", "'capture' is a NULL-pointer"); |
|
OPENCV_ASSERT (capture->grabber, "icvGrabFrame_QT_Cam", "'grabber' is a NULL-pointer"); |
|
|
|
ComponentResult result = noErr; |
|
|
|
|
|
// grab one frame |
|
result = SGIdle (capture->grabber); |
|
if (result != noErr) |
|
{ |
|
fprintf (stderr, "SGIdle failed in icvGrabFrame_QT_Cam with error %d\n", (int) result); |
|
return 0; |
|
} |
|
|
|
// successful |
|
return 1; |
|
} |
|
|
|
static const void * icvRetrieveFrame_QT_Cam (CvCapture_QT_Cam * capture, int) |
|
{ |
|
OPENCV_ASSERT (capture, "icvRetrieveFrame_QT_Cam", "'capture' is a NULL-pointer"); |
|
OPENCV_ASSERT (capture->image_rgb, "icvRetrieveFrame_QT_Cam", "invalid source image"); |
|
OPENCV_ASSERT (capture->image_bgr, "icvRetrieveFrame_QT_Cam", "invalid destination image"); |
|
|
|
OSErr myErr = noErr; |
|
|
|
|
|
// service active sequence grabbers (= redraw immediately) |
|
while (! capture->got_frame) |
|
{ |
|
myErr = SGIdle (capture->grabber); |
|
if (myErr != noErr) |
|
{ |
|
fprintf (stderr, "SGIdle() didn't succeed in icvRetrieveFrame_QT_Cam().\n"); |
|
return 0; |
|
} |
|
} |
|
|
|
// covert RGB of GWorld to BGR |
|
cvCvtColor (capture->image_rgb, capture->image_bgr, CV_RGBA2BGR); |
|
|
|
// reset grabbing status |
|
capture->got_frame = false; |
|
|
|
// always return the same image pointer |
|
return capture->image_bgr; |
|
} |
|
|
|
#endif |
|
|
|
|
|
typedef struct CvVideoWriter_QT { |
|
|
|
DataHandler data_handler; |
|
Movie movie; |
|
Track track; |
|
Media video; |
|
|
|
ICMCompressionSessionRef compression_session_ref; |
|
|
|
TimeValue duration_per_sample; |
|
} CvVideoWriter_QT; |
|
|
|
|
|
static TimeScale const TIME_SCALE = 600; |
|
|
|
static OSStatus icvEncodedFrameOutputCallback( |
|
void* writer, |
|
ICMCompressionSessionRef compression_session_ref, |
|
OSStatus error, |
|
ICMEncodedFrameRef encoded_frame_ref, |
|
void* reserved |
|
); |
|
|
|
static void icvSourceTrackingCallback( |
|
void *source_tracking_ref_con, |
|
ICMSourceTrackingFlags source_tracking_flags, |
|
void *source_frame_ref_con, |
|
void *reserved |
|
); |
|
|
|
static int icvWriteFrame_QT( |
|
CvVideoWriter_QT * video_writer, |
|
const IplImage * image |
|
) { |
|
CVPixelBufferRef pixel_buffer_ref = NULL; |
|
CVReturn retval = |
|
CVPixelBufferCreate( |
|
kCFAllocatorDefault, |
|
image->width, image->height, k24RGBPixelFormat, |
|
NULL /* pixel_buffer_attributes */, |
|
&pixel_buffer_ref |
|
); |
|
|
|
// convert BGR IPL image to RGB pixel buffer |
|
IplImage* image_rgb = |
|
cvCreateImageHeader( |
|
cvSize( image->width, image->height ), |
|
IPL_DEPTH_8U, |
|
3 |
|
); |
|
|
|
retval = CVPixelBufferLockBaseAddress( pixel_buffer_ref, 0 ); |
|
|
|
void* base_address = CVPixelBufferGetBaseAddress( pixel_buffer_ref ); |
|
size_t bytes_per_row = CVPixelBufferGetBytesPerRow( pixel_buffer_ref ); |
|
cvSetData( image_rgb, base_address, bytes_per_row ); |
|
|
|
cvConvertImage( image, image_rgb, CV_CVTIMG_SWAP_RB ); |
|
|
|
retval = CVPixelBufferUnlockBaseAddress( pixel_buffer_ref, 0 ); |
|
|
|
cvReleaseImageHeader( &image_rgb ); |
|
|
|
ICMSourceTrackingCallbackRecord source_tracking_callback_record; |
|
source_tracking_callback_record.sourceTrackingCallback = |
|
icvSourceTrackingCallback; |
|
source_tracking_callback_record.sourceTrackingRefCon = NULL; |
|
|
|
OSStatus status = |
|
ICMCompressionSessionEncodeFrame( |
|
video_writer->compression_session_ref, |
|
pixel_buffer_ref, |
|
0, |
|
video_writer->duration_per_sample, |
|
kICMValidTime_DisplayDurationIsValid, |
|
NULL, |
|
&source_tracking_callback_record, |
|
static_cast<void*>( &pixel_buffer_ref ) |
|
); |
|
|
|
return 0; |
|
} |
|
|
|
static void icvReleaseVideoWriter_QT( CvVideoWriter_QT ** writer ) { |
|
if ( ( writer != NULL ) && ( *writer != NULL ) ) { |
|
CvVideoWriter_QT* video_writer = *writer; |
|
|
|
// force compression session to complete encoding of outstanding source |
|
// frames |
|
ICMCompressionSessionCompleteFrames( |
|
video_writer->compression_session_ref, TRUE, 0, 0 |
|
); |
|
|
|
EndMediaEdits( video_writer->video ); |
|
|
|
ICMCompressionSessionRelease( video_writer->compression_session_ref ); |
|
|
|
InsertMediaIntoTrack( |
|
video_writer->track, |
|
0, |
|
0, |
|
GetMediaDuration( video_writer->video ), |
|
FixRatio( 1, 1 ) |
|
); |
|
|
|
UpdateMovieInStorage( video_writer->movie, video_writer->data_handler ); |
|
|
|
CloseMovieStorage( video_writer->data_handler ); |
|
|
|
/* |
|
// export to AVI |
|
Handle data_ref; |
|
OSType data_ref_type; |
|
QTNewDataReferenceFromFullPathCFString( |
|
CFSTR( "/Users/seibert/Desktop/test.avi" ), kQTPOSIXPathStyle, 0, |
|
&data_ref, &data_ref_type |
|
); |
|
|
|
ConvertMovieToDataRef( video_writer->movie, NULL, data_ref, |
|
data_ref_type, kQTFileTypeAVI, 'TVOD', 0, NULL ); |
|
|
|
DisposeHandle( data_ref ); |
|
*/ |
|
|
|
DisposeMovie( video_writer->movie ); |
|
|
|
cvFree( writer ); |
|
} |
|
} |
|
|
|
static OSStatus icvEncodedFrameOutputCallback( |
|
void* writer, |
|
ICMCompressionSessionRef compression_session_ref, |
|
OSStatus error, |
|
ICMEncodedFrameRef encoded_frame_ref, |
|
void* reserved |
|
) { |
|
CvVideoWriter_QT* video_writer = static_cast<CvVideoWriter_QT*>( writer ); |
|
|
|
OSStatus err = AddMediaSampleFromEncodedFrame( video_writer->video, |
|
encoded_frame_ref, NULL ); |
|
|
|
return err; |
|
} |
|
|
|
static void icvSourceTrackingCallback( |
|
void *source_tracking_ref_con, |
|
ICMSourceTrackingFlags source_tracking_flags, |
|
void *source_frame_ref_con, |
|
void *reserved |
|
) { |
|
if ( source_tracking_flags & kICMSourceTracking_ReleasedPixelBuffer ) { |
|
CVPixelBufferRelease( |
|
*static_cast<CVPixelBufferRef*>( source_frame_ref_con ) |
|
); |
|
} |
|
} |
|
|
|
|
|
static CvVideoWriter_QT* icvCreateVideoWriter_QT( |
|
const char * filename, |
|
int fourcc, |
|
double fps, |
|
CvSize frame_size, |
|
int is_color |
|
) { |
|
CV_FUNCNAME( "icvCreateVideoWriter" ); |
|
|
|
CvVideoWriter_QT* video_writer = |
|
static_cast<CvVideoWriter_QT*>( cvAlloc( sizeof( CvVideoWriter_QT ) ) ); |
|
memset( video_writer, 0, sizeof( CvVideoWriter_QT ) ); |
|
|
|
Handle data_ref = NULL; |
|
OSType data_ref_type; |
|
DataHandler data_handler = NULL; |
|
Movie movie = NULL; |
|
ICMCompressionSessionOptionsRef options_ref = NULL; |
|
ICMCompressionSessionRef compression_session_ref = NULL; |
|
CFStringRef out_path = nil; |
|
Track video_track = nil; |
|
Media video = nil; |
|
OSErr err = noErr; |
|
|
|
__BEGIN__ |
|
|
|
// validate input arguments |
|
if ( filename == NULL ) { |
|
CV_ERROR( CV_StsBadArg, "Video file name must not be NULL" ); |
|
} |
|
if ( fps <= 0.0 ) { |
|
CV_ERROR( CV_StsBadArg, "FPS must be larger than 0.0" ); |
|
} |
|
if ( ( frame_size.width <= 0 ) || ( frame_size.height <= 0 ) ) { |
|
CV_ERROR( CV_StsBadArg, |
|
"Frame width and height must be larger than 0" ); |
|
} |
|
|
|
// initialize QuickTime |
|
if ( !did_enter_movies ) { |
|
err = EnterMovies(); |
|
if ( err != noErr ) { |
|
CV_ERROR( CV_StsInternal, "Unable to initialize QuickTime" ); |
|
} |
|
did_enter_movies = 1; |
|
} |
|
|
|
// convert the file name into a data reference |
|
out_path = CFStringCreateWithCString( kCFAllocatorDefault, filename, kCFStringEncodingISOLatin1 ); |
|
CV_ASSERT( out_path != nil ); |
|
err = QTNewDataReferenceFromFullPathCFString( out_path, kQTPOSIXPathStyle, |
|
0, &data_ref, &data_ref_type ); |
|
CFRelease( out_path ); |
|
if ( err != noErr ) { |
|
CV_ERROR( CV_StsInternal, |
|
"Cannot create data reference from file name" ); |
|
} |
|
|
|
// create a new movie on disk |
|
err = CreateMovieStorage( data_ref, data_ref_type, 'TVOD', |
|
smCurrentScript, newMovieActive, &data_handler, &movie ); |
|
|
|
if ( err != noErr ) { |
|
CV_ERROR( CV_StsInternal, "Cannot create movie storage" ); |
|
} |
|
|
|
// create a track with video |
|
video_track = NewMovieTrack (movie, |
|
FixRatio( frame_size.width, 1 ), |
|
FixRatio( frame_size.height, 1 ), |
|
kNoVolume); |
|
err = GetMoviesError(); |
|
if ( err != noErr ) { |
|
CV_ERROR( CV_StsInternal, "Cannot create video track" ); |
|
} |
|
video = NewTrackMedia( video_track, VideoMediaType, TIME_SCALE, nil, 0 ); |
|
err = GetMoviesError(); |
|
if ( err != noErr ) { |
|
CV_ERROR( CV_StsInternal, "Cannot create video media" ); |
|
} |
|
|
|
CodecType codecType; |
|
switch ( fourcc ) { |
|
case CV_FOURCC( 'D', 'I', 'B', ' ' ): |
|
codecType = kRawCodecType; |
|
break; |
|
default: |
|
codecType = kRawCodecType; |
|
break; |
|
} |
|
|
|
// start a compression session |
|
err = ICMCompressionSessionOptionsCreate( kCFAllocatorDefault, |
|
&options_ref ); |
|
if ( err != noErr ) { |
|
CV_ERROR( CV_StsInternal, "Cannot create compression session options" ); |
|
} |
|
err = ICMCompressionSessionOptionsSetAllowTemporalCompression( options_ref, |
|
true ); |
|
if ( err != noErr) { |
|
CV_ERROR( CV_StsInternal, "Cannot enable temporal compression" ); |
|
} |
|
err = ICMCompressionSessionOptionsSetAllowFrameReordering( options_ref, |
|
true ); |
|
if ( err != noErr) { |
|
CV_ERROR( CV_StsInternal, "Cannot enable frame reordering" ); |
|
} |
|
|
|
ICMEncodedFrameOutputRecord encoded_frame_output_record; |
|
encoded_frame_output_record.encodedFrameOutputCallback = |
|
icvEncodedFrameOutputCallback; |
|
encoded_frame_output_record.encodedFrameOutputRefCon = |
|
static_cast<void*>( video_writer ); |
|
encoded_frame_output_record.frameDataAllocator = NULL; |
|
|
|
err = ICMCompressionSessionCreate( kCFAllocatorDefault, frame_size.width, |
|
frame_size.height, codecType, TIME_SCALE, options_ref, |
|
NULL /*source_pixel_buffer_attributes*/, &encoded_frame_output_record, |
|
&compression_session_ref ); |
|
ICMCompressionSessionOptionsRelease( options_ref ); |
|
if ( err != noErr ) { |
|
CV_ERROR( CV_StsInternal, "Cannot create compression session" ); |
|
} |
|
|
|
err = BeginMediaEdits( video ); |
|
if ( err != noErr ) { |
|
CV_ERROR( CV_StsInternal, "Cannot begin media edits" ); |
|
} |
|
|
|
// fill in the video writer structure |
|
video_writer->data_handler = data_handler; |
|
video_writer->movie = movie; |
|
video_writer->track = video_track; |
|
video_writer->video = video; |
|
video_writer->compression_session_ref = compression_session_ref; |
|
video_writer->duration_per_sample = |
|
static_cast<TimeValue>( static_cast<double>( TIME_SCALE ) / fps ); |
|
|
|
__END__ |
|
|
|
// clean up in case of error (unless error processing mode is |
|
// CV_ErrModeLeaf) |
|
if ( err != noErr ) { |
|
if ( options_ref != NULL ) { |
|
ICMCompressionSessionOptionsRelease( options_ref ); |
|
} |
|
if ( compression_session_ref != NULL ) { |
|
ICMCompressionSessionRelease( compression_session_ref ); |
|
} |
|
if ( data_handler != NULL ) { |
|
CloseMovieStorage( data_handler ); |
|
} |
|
if ( movie != NULL ) { |
|
DisposeMovie( movie ); |
|
} |
|
if ( data_ref != NULL ) { |
|
DeleteMovieStorage( data_ref, data_ref_type ); |
|
DisposeHandle( data_ref ); |
|
} |
|
cvFree( reinterpret_cast<void**>( &video_writer ) ); |
|
video_writer = NULL; |
|
} |
|
|
|
return video_writer; |
|
} |
|
|
|
|
|
/** |
|
* |
|
* Wrappers for the new C++ CvCapture & CvVideoWriter structures |
|
* |
|
*/ |
|
|
|
class CvCapture_QT_Movie_CPP : public CvCapture |
|
{ |
|
public: |
|
CvCapture_QT_Movie_CPP() { captureQT = 0; } |
|
virtual ~CvCapture_QT_Movie_CPP() { close(); } |
|
|
|
virtual bool open( const char* filename ); |
|
virtual void close(); |
|
|
|
virtual double getProperty(int); |
|
virtual bool setProperty(int, double); |
|
virtual bool grabFrame(); |
|
virtual IplImage* retrieveFrame(int); |
|
virtual int getCaptureDomain() { return CV_CAP_QT; } // Return the type of the capture object: CV_CAP_VFW, etc... |
|
protected: |
|
|
|
CvCapture_QT_Movie* captureQT; |
|
}; |
|
|
|
bool CvCapture_QT_Movie_CPP::open( const char* filename ) |
|
{ |
|
close(); |
|
captureQT = icvCaptureFromFile_QT( filename ); |
|
return captureQT != 0; |
|
} |
|
|
|
void CvCapture_QT_Movie_CPP::close() |
|
{ |
|
if( captureQT ) |
|
{ |
|
icvClose_QT_Movie( captureQT ); |
|
cvFree( &captureQT ); |
|
} |
|
} |
|
|
|
bool CvCapture_QT_Movie_CPP::grabFrame() |
|
{ |
|
return captureQT ? icvGrabFrame_QT_Movie( captureQT ) != 0 : false; |
|
} |
|
|
|
IplImage* CvCapture_QT_Movie_CPP::retrieveFrame(int) |
|
{ |
|
return captureQT ? (IplImage*)icvRetrieveFrame_QT_Movie( captureQT, 0 ) : 0; |
|
} |
|
|
|
double CvCapture_QT_Movie_CPP::getProperty( int propId ) |
|
{ |
|
return captureQT ? icvGetProperty_QT_Movie( captureQT, propId ) : 0; |
|
} |
|
|
|
bool CvCapture_QT_Movie_CPP::setProperty( int propId, double value ) |
|
{ |
|
return captureQT ? icvSetProperty_QT_Movie( captureQT, propId, value ) != 0 : false; |
|
} |
|
|
|
CvCapture* cvCreateFileCapture_QT( const char* filename ) |
|
{ |
|
CvCapture_QT_Movie_CPP* capture = new CvCapture_QT_Movie_CPP; |
|
|
|
if( capture->open( filename )) |
|
return capture; |
|
|
|
delete capture; |
|
return 0; |
|
} |
|
|
|
|
|
///////////////////////////////////// |
|
|
|
class CvCapture_QT_Cam_CPP : public CvCapture |
|
{ |
|
public: |
|
CvCapture_QT_Cam_CPP() { captureQT = 0; } |
|
virtual ~CvCapture_QT_Cam_CPP() { close(); } |
|
|
|
virtual bool open( int index ); |
|
virtual void close(); |
|
|
|
virtual double getProperty(int); |
|
virtual bool setProperty(int, double); |
|
virtual bool grabFrame(); |
|
virtual IplImage* retrieveFrame(int); |
|
virtual int getCaptureDomain() { return CV_CAP_QT; } // Return the type of the capture object: CV_CAP_VFW, etc... |
|
protected: |
|
|
|
CvCapture_QT_Cam* captureQT; |
|
}; |
|
|
|
bool CvCapture_QT_Cam_CPP::open( int index ) |
|
{ |
|
close(); |
|
captureQT = icvCaptureFromCam_QT( index ); |
|
return captureQT != 0; |
|
} |
|
|
|
void CvCapture_QT_Cam_CPP::close() |
|
{ |
|
if( captureQT ) |
|
{ |
|
icvClose_QT_Cam( captureQT ); |
|
cvFree( &captureQT ); |
|
} |
|
} |
|
|
|
bool CvCapture_QT_Cam_CPP::grabFrame() |
|
{ |
|
return captureQT ? icvGrabFrame_QT_Cam( captureQT ) != 0 : false; |
|
} |
|
|
|
IplImage* CvCapture_QT_Cam_CPP::retrieveFrame(int) |
|
{ |
|
return captureQT ? (IplImage*)icvRetrieveFrame_QT_Cam( captureQT, 0 ) : 0; |
|
} |
|
|
|
double CvCapture_QT_Cam_CPP::getProperty( int propId ) |
|
{ |
|
return captureQT ? icvGetProperty_QT_Cam( captureQT, propId ) : 0; |
|
} |
|
|
|
bool CvCapture_QT_Cam_CPP::setProperty( int propId, double value ) |
|
{ |
|
return captureQT ? icvSetProperty_QT_Cam( captureQT, propId, value ) != 0 : false; |
|
} |
|
|
|
CvCapture* cvCreateCameraCapture_QT( int index ) |
|
{ |
|
CvCapture_QT_Cam_CPP* capture = new CvCapture_QT_Cam_CPP; |
|
|
|
if( capture->open( index )) |
|
return capture; |
|
|
|
delete capture; |
|
return 0; |
|
} |
|
|
|
///////////////////////////////// |
|
|
|
class CvVideoWriter_QT_CPP : public CvVideoWriter |
|
{ |
|
public: |
|
CvVideoWriter_QT_CPP() { writerQT = 0; } |
|
virtual ~CvVideoWriter_QT_CPP() { close(); } |
|
|
|
virtual bool open( const char* filename, int fourcc, |
|
double fps, CvSize frameSize, bool isColor ); |
|
virtual void close(); |
|
virtual bool writeFrame( const IplImage* ); |
|
|
|
protected: |
|
CvVideoWriter_QT* writerQT; |
|
}; |
|
|
|
bool CvVideoWriter_QT_CPP::open( const char* filename, int fourcc, |
|
double fps, CvSize frameSize, bool isColor ) |
|
{ |
|
close(); |
|
writerQT = icvCreateVideoWriter_QT( filename, fourcc, fps, frameSize, isColor ); |
|
return writerQT != 0; |
|
} |
|
|
|
void CvVideoWriter_QT_CPP::close() |
|
{ |
|
if( writerQT ) |
|
{ |
|
icvReleaseVideoWriter_QT( &writerQT ); |
|
writerQT = 0; |
|
} |
|
} |
|
|
|
bool CvVideoWriter_QT_CPP::writeFrame( const IplImage* image ) |
|
{ |
|
if( !writerQT || !image ) |
|
return false; |
|
return icvWriteFrame_QT( writerQT, image ) >= 0; |
|
} |
|
|
|
CvVideoWriter* cvCreateVideoWriter_QT( const char* filename, int fourcc, |
|
double fps, CvSize frameSize, int isColor ) |
|
{ |
|
CvVideoWriter_QT_CPP* writer = new CvVideoWriter_QT_CPP; |
|
if( writer->open( filename, fourcc, fps, frameSize, isColor != 0 )) |
|
return writer; |
|
delete writer; |
|
return 0; |
|
}
|
|
|