/*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"

// 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", "couldn't 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", "couldn't 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 modified 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", "couldn't 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, "Couldn't 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", "couldn't 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", "couldn't 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", "couldn't 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", "couldn't create QTNewGWorld() for output image");

    // get pixmap
    capture->pixmap = GetGWorldPixMap (capture->myGWorld);
    result = GetMoviesError ();
    OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldn't get pixmap");

    // set digitizer rect
    result = VDSetDigitizerRect (capture->grabber, & myRect);
    OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldn't 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", "couldn't 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", "couldn't 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", "couldn't 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", "couldn't 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", "couldn't 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", "couldn't set capture state");

    // release memory
    cvReleaseImage       (& capture->image_bgr);
    cvReleaseImageHeader (& capture->image_rgb);
    DisposeGWorld        (capture->myGWorld);
    CloseComponent       (capture->grabber);

    // successful
    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 modified 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", "couldn't 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", "couldn't 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", "couldn't create image");

    // initialize sequence grabber component
    result = SGInitialize (capture->grabber);
    OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldn't initialize sequence grabber");
    result = SGSetDataRef (capture->grabber, 0, 0, seqGrabDontMakeMovie);
    OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldn't set data reference of sequence grabber");

    // set up video channel
    result = SGNewChannel (capture->grabber, VideoMediaType, & (capture->channel));
    OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldn't 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", "couldn't 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", "couldn't set the channel video device");
                break;
            }
            current_index++;
        }
    }
    result = SGDisposeDeviceList (capture->grabber, device_list);
    OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldn't 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", "couldn't 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", "couldn't set GWorld for sequence grabber");
    result = SGSetChannelBounds (capture->channel, & (capture->bounds));
    OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldn't set video channel bounds");
    result = SGSetChannelUsage (capture->channel, seqGrabRecord);
    OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldn't set channel usage");

    // start recording so we can size
    result = SGStartRecord (capture->grabber);
    OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldn't 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", "couldn't 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", "couldn't create offscreen GWorld");
    result = SGSetGWorld( capture->grabber, tmpgworld, 0);
    OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldn't set GWorld for sequence grabber");
    DisposeGWorld( capture->gworld );
    capture->gworld = tmpgworld;

    result = SGSetChannelBounds (capture->channel, & (capture->bounds));
    OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldn't 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", "couldn't 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", "couldn't 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", "couldn't set data proc");

    // start recording
    result = SGStartRecord (capture->grabber);
    OPENCV_ASSERT (result == noErr, "icvOpenCamera_QT", "couldn't 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", "couldn't stop recording");

    // close sequence grabber component
    result = CloseComponent (capture->grabber);
    OPENCV_ASSERT (result == noErr, "icveClose_QT_Cam", "couldn't close sequence grabber component");

    // end decompression sequence
    CDSequenceEnd (capture->sequence);

    // free memory
    cvReleaseImage (& capture->image_bgr);
    cvReleaseImageHeader (& capture->image_rgb);
    DisposeGWorld (capture->gworld);

    // successful
    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, 1, 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;
    CodecType codecType = kRawCodecType;

    __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" );
    }

    /*if( fourcc == CV_FOURCC( 'D', 'I', 'B', ' ' ))
        codecType = kRawCodecType;*/

    // 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) const CV_OVERRIDE;
    virtual bool setProperty(int, double) CV_OVERRIDE;
    virtual bool grabFrame() CV_OVERRIDE;
    virtual IplImage* retrieveFrame(int) CV_OVERRIDE;
    virtual int getCaptureDomain() CV_OVERRIDE { return CV_CAP_QT; }
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 ) const
{
    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) const;
    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 ) const
{
    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* );

    int getCaptureDomain() const CV_OVERRIDE { return cv::CAP_QT; }
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;
}
