void QTCode_ForceMovieRedraw(Movie theMovie) { OSErr err = noErr; Rect movieRect; RgnHandle clipRegion = NULL; if (theMovie == NULL) goto bail; clipRegion = NewRgn(); if (clipRegion == NULL) goto bail; GetClip(clipRegion); GetMovieBox(theMovie, &movieRect); ClipRect(&movieRect); UpdateMovie(theMovie); MoviesTask(theMovie, 0); SetClip(clipRegion); /* Closure. Clean up if we have handles. */ bail: if (clipRegion != NULL) { DisposeRgn(clipRegion); } }
OSErr DoPlayMovie(Boolean useOverlay) { Rect movieBounds; OSErr err; BailErr((err = GetAMovieFile(&mMovie))); GetMovieBox(mMovie, &movieBounds); mMovieController = NewMovieController(mMovie, &movieBounds, mcTopLeftMovie | mcNotVisible); NormalizeMovieRect(mMovie); GetMovieBox(mMovie, &movieBounds); BailErr((err = InitializeMungData(movieBounds, FrontWindow(), useOverlay, /* overlay */ true, /* clamp */ false /* draw with QT Effect */ ))); // This is key, as we need to draw all frames in our // offscreen gworld first. Once there, our custom // decompressor component can perform overlay drawing // or color clamping SetMovieGWorld(mMovie, GetMungDataOffscreen(), nil); // Our DrawCompleteProc calls BlitOneMungData which SetupMovieDrawCompleteProc(); // --------- SetMovieActive(mMovie, true); #ifndef __APPLE_CC__ // Due to a bug in Jaguar, some QT functions are missing from // /System/Library/CFMSupport/CarbonLib, so we must manually // grab the function pointers from the QuickTime.framework // for CFM Carbon applications. MachO Carbon applications do not // have the problem. GetMissingQTFunctionPointers(); #endif InstallMovieIdlingEventLoopTimer(&mMovieTimerState); // start the movie playing MCDoAction(mMovieController, mcActionPrerollAndPlay, (void*)Long2Fix(1)); bail: return err; }
void NormalizeMovieRect(Movie theMovie) { Rect movieBounds; GetMovieBox(theMovie, &movieBounds); OffsetRect(&movieBounds, -movieBounds.left, -movieBounds.top); movieBounds.right = movieBounds.left + 320; movieBounds.bottom = movieBounds.top + 240; SetMovieBox(theMovie, &movieBounds); }
void QTCode_PositionMovieRectInClientWindow(Movie theMovie, HWND hwnd) { Rect movieBounds; GetMovieBox(theMovie, &movieBounds); CenterMovieRectInWindow(hwnd, /* window where we are placing the image */ RECT_WIDTH(movieBounds), RECT_HEIGHT(movieBounds), /* width, height, of the image */ &movieBounds /* on output, a Mac Rect centered in the window */ ); SetMovieBox(theMovie, &movieBounds); }
OSErr QTDR_PlayMovieFromRAM (Movie theMovie) { WindowPtr myWindow = NULL; Rect myBounds = {50, 50, 100, 100}; Rect myRect; StringPtr myTitle = QTUtils_ConvertCToPascalString(kWindowTitle); OSErr myErr = memFullErr; myWindow = NewCWindow(NULL, &myBounds, myTitle, false, 0, (WindowPtr)-1, false, 0); if (myWindow == NULL) goto bail; myErr = noErr; MacSetPort((GrafPtr)GetWindowPort(myWindow)); GetMovieBox(theMovie, &myRect); MacOffsetRect(&myRect, -myRect.left, -myRect.top); SetMovieBox(theMovie, &myRect); if (!EmptyRect(&myRect)) SizeWindow(myWindow, myRect.right, myRect.bottom, false); else SizeWindow(myWindow, 200, 0, false); MacShowWindow(myWindow); SetMovieGWorld(theMovie, GetWindowPort(myWindow), NULL); GoToBeginningOfMovie(theMovie); MoviesTask(theMovie, 0); StartMovie(theMovie); myErr = GetMoviesError(); if (myErr != noErr) goto bail; while (!IsMovieDone(theMovie)) MoviesTask(theMovie, 0); bail: free(myTitle); if (theMovie != NULL) DisposeMovie(theMovie); if (myWindow != NULL) DisposeWindow(myWindow); return(myErr); }
static void QTFrame_CalcWindowMinMaxInfo (HWND theWnd, LPMINMAXINFO lpMinMax) { WindowObject myWindowObject = NULL; Movie myMovie = NULL; MovieController myMC = NULL; GraphicsImportComponent myImporter = NULL; Rect myRect; ComponentResult myErr = noErr; myWindowObject = QTFrame_GetWindowObjectFromWindow(theWnd); if (myWindowObject != NULL) { myMC = (**myWindowObject).fController; myMovie = (**myWindowObject).fMovie; myImporter = (**myWindowObject).fGraphicsImporter; } // we're expecting a window with either an image or a movie if ((myImporter == NULL) && (myMovie == NULL)) return; if (myImporter != NULL) { myErr = GraphicsImportGetBoundsRect(myImporter, &myRect); if (myErr != noErr) return; } if (myMovie != NULL) { GetMovieBox(myMovie, &myRect); if (myMC != NULL) if (MCGetVisible(myMC)) MCGetControllerBoundsRect(myMC, &myRect); } // we currently don't allow images or movies to be resized, so we return both // min and max rectangles set to the current image or movie size, plus the // appropriate window frame and caption sizes lpMinMax->ptMinTrackSize.x = myRect.right + (2 * GetSystemMetrics(SM_CXFRAME)); lpMinMax->ptMinTrackSize.y = myRect.bottom + (2 * GetSystemMetrics(SM_CXFRAME)) + GetSystemMetrics(SM_CYCAPTION); lpMinMax->ptMaxTrackSize.x = lpMinMax->ptMinTrackSize.x; lpMinMax->ptMaxTrackSize.y = lpMinMax->ptMinTrackSize.y; lpMinMax->ptMaxSize.x = lpMinMax->ptMinTrackSize.x; lpMinMax->ptMaxSize.y = lpMinMax->ptMinTrackSize.y; }
quicktime_player* quicktime_player::open( const char *path ) { OSErr err; OSType dataRefType; Handle dataRef = NULL; short resID = 0; impl *m = new impl; std::string pathStd = path; #ifdef WIN32 for (std::string::iterator p = pathStd.begin(); p != pathStd.end(); ++p) if (*p == '/') *p = '\\'; #endif CFStringRef cfPath = CFStringCreateWithCString(NULL, pathStd.c_str(), kCFStringEncodingISOLatin1); err = QTNewDataReferenceFromFullPathCFString(cfPath, (QTPathStyle)kQTNativeDefaultPathStyle, 0, &dataRef, &dataRefType); CFRelease(cfPath); if (err != noErr) { delete m; return NULL; } err = NewMovieFromDataRef(&m->movie, newMovieActive, &resID, dataRef, dataRefType); DisposeHandle(dataRef); if (err != noErr) { delete m; return NULL; } m->track = GetMovieIndTrackType(m->movie, 1, VisualMediaCharacteristic, movieTrackCharacteristic); m->media = GetTrackMedia(m->track); Rect bounds; GetMovieBox(m->movie, &bounds); m->width = bounds.right; m->height = bounds.bottom; m->buffer = (unsigned char*)malloc(4 * m->width * m->height); err = QTNewGWorldFromPtr(&m->gworld, k32BGRAPixelFormat, &bounds, NULL, NULL, 0, m->buffer, 4 * m->width); if (err != noErr) { delete m; return NULL; } SetMovieGWorld(m->movie, m->gworld, NULL); return new quicktime_player(m); }
//--------------------------------------------------------------------------- bool ofQuickTimePlayer::loadMovie(string name){ //-------------------------------------- #ifdef OF_VIDEO_PLAYER_QUICKTIME //-------------------------------------- initializeQuicktime(); // init quicktime closeMovie(); // if we have a movie open, close it bLoaded = false; // try to load now if( name.substr(0, 7) == "http://" || name.substr(0,7) == "rtsp://" ){ if(! createMovieFromURL(name, moviePtr) ) return false; }else{ name = ofToDataPath(name); if( !createMovieFromPath((char *)name.c_str(), moviePtr) ) return false; } bool bDoWeAlreadyHaveAGworld = false; if (width != 0 && height != 0){ bDoWeAlreadyHaveAGworld = true; } Rect movieRect; GetMovieBox(moviePtr, &(movieRect)); if (bDoWeAlreadyHaveAGworld){ // is the gworld the same size, then lets *not* de-allocate and reallocate: if (width == movieRect.right && height == movieRect.bottom){ SetMovieGWorld (moviePtr, offscreenGWorld, nil); } else { width = movieRect.right; height = movieRect.bottom; pixels.clear(); delete(offscreenGWorldPixels); if ((offscreenGWorld)) DisposeGWorld((offscreenGWorld)); createImgMemAndGWorld(); } } else { width = movieRect.right; height = movieRect.bottom; createImgMemAndGWorld(); } if (moviePtr == NULL){ return false; } //----------------- callback method myDrawCompleteProc = NewMovieDrawingCompleteUPP (DrawCompleteProc); SetMovieDrawingCompleteProc (moviePtr, movieDrawingCallWhenChanged, myDrawCompleteProc, (long)this); // ------------- get the total # of frames: nFrames = 0; TimeValue curMovieTime; curMovieTime = 0; TimeValue duration; //OSType whichMediaType = VIDEO_TYPE; // mingw chokes on this OSType whichMediaType = FOUR_CHAR_CODE('vide'); short flags = nextTimeMediaSample + nextTimeEdgeOK; while( curMovieTime >= 0 ) { nFrames++; GetMovieNextInterestingTime(moviePtr,flags,1,&whichMediaType,curMovieTime,0,&curMovieTime,&duration); flags = nextTimeMediaSample; } nFrames--; // there's an extra time step at the end of themovie // ------------- get some pixels in there ------ GoToBeginningOfMovie(moviePtr); SetMovieActiveSegment(moviePtr, -1,-1); MoviesTask(moviePtr,0); #if defined(TARGET_OSX) && defined(__BIG_ENDIAN__) convertPixels(offscreenGWorldPixels, pixels.getPixels(), width, height); #endif bStarted = false; bLoaded = true; bPlaying = false; bHavePixelsChanged = false; speed = 1; return true; //-------------------------------------- #endif //-------------------------------------- }
//--------------------------------------------------------------------------- bool ofQuickTimePlayer::loadMovie(string name) { //-------------------------------------- #ifdef OF_VIDEO_PLAYER_QUICKTIME //-------------------------------------- initializeQuicktime(); // init quicktime closeMovie(); // if we have a movie open, close it bLoaded = false; // try to load now // from : https://github.com/openframeworks/openFrameworks/issues/244 // http://developer.apple.com/library/mac/#documentation/QuickTime/RM/QTforWindows/QTforWindows/C-Chapter/3BuildingQuickTimeCa.html // Apple's documentation *seems* to state that a Gworld should have been set prior to calling NewMovieFromFile // So I set a dummy Gworld (1x1 pixel) before calling createMovieFromPath // it avoids crash at the creation of objet ofVideoPlayer after a previous ofVideoPlayer have been deleted #ifdef TARGET_WIN32 if (width != 0 && height != 0) { pixels.clear(); delete [] offscreenGWorldPixels; } width = 1; height = 1; createImgMemAndGWorld(); #endif if( name.substr(0, 7) == "http://" || name.substr(0,7) == "rtsp://" ) { if(! createMovieFromURL(name, moviePtr) ) return false; } else { name = ofToDataPath(name); if( !createMovieFromPath((char *)name.c_str(), moviePtr) ) return false; } bool bDoWeAlreadyHaveAGworld = false; if (width != 0 && height != 0) { bDoWeAlreadyHaveAGworld = true; } Rect movieRect; GetMovieBox(moviePtr, &(movieRect)); if (bDoWeAlreadyHaveAGworld) { // is the gworld the same size, then lets *not* de-allocate and reallocate: if (width == movieRect.right && height == movieRect.bottom) { SetMovieGWorld (moviePtr, offscreenGWorld, nil); } else { width = movieRect.right; height = movieRect.bottom; pixels.clear(); delete [] offscreenGWorldPixels; if ((offscreenGWorld)) DisposeGWorld((offscreenGWorld)); createImgMemAndGWorld(); } } else { width = movieRect.right; height = movieRect.bottom; createImgMemAndGWorld(); } if (moviePtr == NULL) { return false; } //----------------- callback method myDrawCompleteProc = NewMovieDrawingCompleteUPP (DrawCompleteProc); SetMovieDrawingCompleteProc (moviePtr, movieDrawingCallWhenChanged, myDrawCompleteProc, (long)this); // ------------- get the total # of frames: nFrames = 0; TimeValue curMovieTime; curMovieTime = 0; TimeValue duration; //OSType whichMediaType = VIDEO_TYPE; // mingw chokes on this OSType whichMediaType = FOUR_CHAR_CODE('vide'); short flags = nextTimeMediaSample + nextTimeEdgeOK; while( curMovieTime >= 0 ) { nFrames++; GetMovieNextInterestingTime(moviePtr,flags,1,&whichMediaType,curMovieTime,0,&curMovieTime,&duration); flags = nextTimeMediaSample; } nFrames--; // there's an extra time step at the end of themovie // ------------- get some pixels in there ------ GoToBeginningOfMovie(moviePtr); SetMovieActiveSegment(moviePtr, -1,-1); MoviesTask(moviePtr,0); #if defined(TARGET_OSX) && defined(__BIG_ENDIAN__) convertPixels(offscreenGWorldPixels, pixels.getPixels(), width, height); #endif bStarted = false; bLoaded = true; bPlaying = false; bHavePixelsChanged = false; speed = 1; return true; //-------------------------------------- #endif //-------------------------------------- }
//--------------------------------------------------------------------------- bool ofVideoPlayer::loadMovie(string name){ //-------------------------------------- #ifdef OF_VIDEO_PLAYER_QUICKTIME //-------------------------------------- initializeQuicktime(); // init quicktime closeMovie(); // if we have a movie open, close it bLoaded = false; // try to load now if( name.substr(0, 7) == "http://"){ if(! createMovieFromURL(name, moviePtr) ) return false; }else{ name = ofToDataPath(name); if( !createMovieFromPath((char *)name.c_str(), moviePtr) ) return false; } bool bDoWeAlreadyHaveAGworld = false; if (width != 0 && height != 0){ bDoWeAlreadyHaveAGworld = true; } Rect movieRect; GetMovieBox(moviePtr, &(movieRect)); if (bDoWeAlreadyHaveAGworld){ // is the gworld the same size, then lets *not* de-allocate and reallocate: if (width == movieRect.right && height == movieRect.bottom){ SetMovieGWorld (moviePtr, offscreenGWorld, nil); } else { width = movieRect.right; height = movieRect.bottom; delete(pixels); delete(offscreenGWorldPixels); if ((offscreenGWorld)) DisposeGWorld((offscreenGWorld)); createImgMemAndGWorld(); } } else { width = movieRect.right; height = movieRect.bottom; createImgMemAndGWorld(); } if (moviePtr == NULL){ return false; } //----------------- callback method MovieDrawingCompleteUPP myDrawCompleteProc; myDrawCompleteProc = NewMovieDrawingCompleteUPP (DrawCompleteProc); SetMovieDrawingCompleteProc (moviePtr, movieDrawingCallWhenChanged, myDrawCompleteProc, (long)this); // ------------- get some pixels in there ------ GoToBeginningOfMovie(moviePtr); SetMovieActiveSegment(moviePtr, -1,-1); MoviesTask(moviePtr,0); convertPixels(offscreenGWorldPixels, pixels, width, height); if (bUseTexture == true){ tex.loadData(pixels, width, height, GL_RGB); } bStarted = false; bLoaded = true; bPlaying = false; bHavePixelsChanged = false; speed = 1; return true; //-------------------------------------- #else //-------------------------------------- bLoaded = false; bPaused = false; speed = 1.0f; bHavePixelsChanged = false; name = ofToDataPath(name); fobsDecoder = new omnividea::fobs::Decoder(name.c_str()); omnividea::fobs::ReturnCode error = fobsDecoder->open(); width = fobsDecoder->getWidth(); height = fobsDecoder->getHeight(); pixels = new unsigned char[width*height*3]; if (!fobsDecoder->isVideoPresent()){ return false; } bLoaded = true; if (bUseTexture){ // create the texture, set the pixels to black and // upload them to the texture (so at least we see nothing black the callback) tex.allocate(width,height,GL_RGB); memset(pixels, 0, width*height*3); tex.loadData(pixels, width, height, GL_RGB); } error = fobsDecoder->setFrame(0); if(error == omnividea::fobs::NoFrameError) { error = omnividea::fobs::OkCode; printf("NoFrameError\n"); } if(omnividea::fobs::isOk(error)){ // get some pixels in: unsigned char *rgb = fobsDecoder->getRGB(); if(rgb == NULL) error = omnividea::fobs::GenericError; if(isOk(error)) { memcpy(pixels, rgb, width*height*3); tex.loadData(pixels, width, height, GL_RGB); } } iTotalFrames = (int)(fobsDecoder->getFrameRate()*fobsDecoder->getDurationSeconds()); positionPct = 0; timeLastIdle = ofGetElapsedTimef(); durationMillis = fobsDecoder->getDurationSeconds() * 1000.0f; return true; //-------------------------------------- #endif //-------------------------------------- }
/* * PsychQTGetTextureFromMovie() -- Create an OpenGL texture map from a specific videoframe from given movie object. * * win = Window pointer of onscreen window for which a OpenGL texture should be created. * moviehandle = Handle to the movie object. * checkForImage = true == Just check if new image available, false == really retrieve the image, blocking if necessary. * timeindex = When not in playback mode, this allows specification of a requested frame by presentation time. * If set to -1, or if in realtime playback mode, this parameter is ignored and the next video frame is returned. * out_texture = Pointer to the Psychtoolbox texture-record where the new texture should be stored. * presentation_timestamp = A ptr to a double variable, where the presentation timestamp of the returned frame should be stored. * * Returns true (1) on success, false (0) if no new image available, -1 if no new image available and there won't be any in future. */ int PsychQTGetTextureFromMovie(PsychWindowRecordType *win, int moviehandle, int checkForImage, double timeindex, PsychWindowRecordType *out_texture, double *presentation_timestamp) { static TimeValue myNextTimeCached = -2; static TimeValue nextFramesTimeCached = -2; TimeValue myCurrTime; TimeValue myNextTime; TimeValue nextFramesTime=0; short myFlags; OSType myTypes[1]; OSErr error = noErr; Movie theMovie; CVOpenGLTextureRef newImage = NULL; QTVisualContextRef theMoviecontext; unsigned int failcount=0; float lowerLeft[2]; float lowerRight[2]; float upperRight[2]; float upperLeft[2]; GLuint texid; Rect rect; float rate; double targetdelta, realdelta, frames; PsychRectType outRect; if (!PsychIsOnscreenWindow(win)) { PsychErrorExitMsg(PsychError_user, "Need onscreen window ptr!!!"); } // Activate OpenGL context of target window: PsychSetGLContext(win); // Explicitely disable Apple's Client storage extensions. For now they are not really useful to us. glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_FALSE); if (moviehandle < 0 || moviehandle >= PSYCH_MAX_MOVIES) { PsychErrorExitMsg(PsychError_user, "Invalid moviehandle provided."); } if ((timeindex!=-1) && (timeindex < 0 || timeindex >= 10000.0)) { PsychErrorExitMsg(PsychError_user, "Invalid timeindex provided."); } if (NULL == out_texture && !checkForImage) { PsychErrorExitMsg(PsychError_internal, "NULL-Ptr instead of out_texture ptr passed!!!"); } // Fetch references to objects we need: theMovie = movieRecordBANK[moviehandle].theMovie; theMoviecontext = movieRecordBANK[moviehandle].QTMovieContext; if (theMovie == NULL) { PsychErrorExitMsg(PsychError_user, "Invalid moviehandle provided. No movie associated with this handle."); } // Check if end of movie is reached. Rewind, if so... if (IsMovieDone(theMovie) && movieRecordBANK[moviehandle].loopflag > 0) { if (GetMovieRate(theMovie)>0) { GoToBeginningOfMovie(theMovie); } else { GoToEndOfMovie(theMovie); } } // Is movie actively playing (automatic async playback, possibly with synced sound)? // If so, then we ignore the 'timeindex' parameter, because the automatic playback // process determines which frames should be delivered to PTB when. This function will // simply wait or poll for arrival/presence of a new frame that hasn't been fetched // in previous calls. if (0 == GetMovieRate(theMovie)) { // Movie playback inactive. We are in "manual" mode: No automatic async playback, // no synced audio output. The user just wants to manually fetch movie frames into // textures for manual playback in a standard Matlab-loop. // First pass - checking for new image? if (checkForImage) { // Image for specific point in time requested? if (timeindex >= 0) { // Yes. We try to retrieve the next possible image for requested timeindex. myCurrTime = (TimeValue) ((timeindex * (double) GetMovieTimeScale(theMovie)) + 0.5f); } else { // No. We just retrieve the next frame, given the current movie time. myCurrTime = GetMovieTime(theMovie, NULL); } // Retrieve timeindex of the closest image sample after myCurrTime: myFlags = nextTimeStep + nextTimeEdgeOK; // We want the next frame in the movie's media. myTypes[0] = VisualMediaCharacteristic; // We want video samples. GetMovieNextInterestingTime(theMovie, myFlags, 1, myTypes, myCurrTime, FloatToFixed(1), &myNextTime, &nextFramesTime); error = GetMoviesError(); if (error != noErr) { PsychErrorExitMsg(PsychError_internal, "Failed to fetch texture from movie for given timeindex!"); } // Found useful event? if (myNextTime == -1) { if (PsychPrefStateGet_Verbosity() > 3) printf("PTB-WARNING: Bogus timevalue in movie track for movie %i. Trying to keep going.\n", moviehandle); // No. Just push timestamp to current time plus a little bit in the hope // this will get us unstuck: myNextTime = myCurrTime + (TimeValue) 1; nextFramesTime = (TimeValue) 0; } if (myNextTime != myNextTimeCached) { // Set movies current time to myNextTime, so the next frame will be fetched from there: SetMovieTimeValue(theMovie, myNextTime); // nextFramesTime is the timeindex to which we need to advance for retrieval of next frame: (see code below) nextFramesTime=myNextTime + nextFramesTime; if (PsychPrefStateGet_Verbosity() > 5) printf("PTB-DEBUG: Current timevalue in movie track for movie %i is %lf secs.\n", moviehandle, (double) myNextTime / (double) GetMovieTimeScale(theMovie)); if (PsychPrefStateGet_Verbosity() > 5) printf("PTB-DEBUG: Next timevalue in movie track for movie %i is %lf secs.\n", moviehandle, (double) nextFramesTime / (double) GetMovieTimeScale(theMovie)); // Cache values for 2nd pass: myNextTimeCached = myNextTime; nextFramesTimeCached = nextFramesTime; } else { // Somehow got stuck? Do nothing... if (PsychPrefStateGet_Verbosity() > 5) printf("PTB-DEBUG: Seem to be a bit stuck at timevalue [for movie %i] of %lf secs. Nudging a bit forward...\n", moviehandle, (double) myNextTime / (double) GetMovieTimeScale(theMovie)); // Nudge the timeindex a bit forware in the hope that this helps: SetMovieTimeValue(theMovie, GetMovieTime(theMovie, NULL) + 1); } } else { // This is the 2nd pass: Image fetching. Use cached values from first pass: // Caching in a static works because we're always called immediately for 2nd // pass after successfull return from 1st pass, and we're not multi-threaded, // i.e., don't need to be reentrant or thread-safe here: myNextTime = myNextTimeCached; nextFramesTime = nextFramesTimeCached; myNextTimeCached = -2; } } else { // myNextTime unavailable if in autoplayback-mode: myNextTime=-1; } // Presentation timestamp requested? if (presentation_timestamp) { // Already available? if (myNextTime==-1) { // Retrieve the exact presentation timestamp of the retrieved frame (in movietime): myFlags = nextTimeStep + nextTimeEdgeOK; // We want the next frame in the movie's media. myTypes[0] = VisualMediaCharacteristic; // We want video samples. // We search backward for the closest available image for the current time. Either we get the current time // if we happen to fetch a frame exactly when it becomes ready, or we get a bit earlier timestamp, which is // the optimal presentation timestamp for this frame: GetMovieNextInterestingTime(theMovie, myFlags, 1, myTypes, GetMovieTime(theMovie, NULL), FloatToFixed(-1), &myNextTime, NULL); } // Convert pts (in Quicktime ticks) to pts in seconds since start of movie and return it: *presentation_timestamp = (double) myNextTime / (double) GetMovieTimeScale(theMovie); } // Allow quicktime visual context task to do its internal bookkeeping and cleanup work: if (theMoviecontext) QTVisualContextTask(theMoviecontext); // Perform decompress-operation: if (checkForImage) MoviesTask(theMovie, 0); // Should we just check for new image? If so, just return availability status: if (checkForImage) { if (PSYCH_USE_QT_GWORLDS) { // We use GWorlds. In this case we either suceed immediately due to the // synchronous nature of GWorld rendering, or we fail completely at end // of non-looping movie: if (IsMovieDone(theMovie) && movieRecordBANK[moviehandle].loopflag == 0) { // No new frame available and there won't be any in the future, because this is a non-looping // movie that has reached its end. return(-1); } // Is this the special case of a movie without video, but only sound? In that case, // we always return a 'false' because there ain't no image to return. if (movieRecordBANK[moviehandle].QTMovieGWorld == NULL) return(false); // Success! return(true); } // Code which uses QTVisualContextTasks... if (QTVisualContextIsNewImageAvailable(theMoviecontext, NULL)) { // New frame ready! return(true); } else if (IsMovieDone(theMovie) && movieRecordBANK[moviehandle].loopflag == 0) { // No new frame available and there won't be any in the future, because this is a non-looping // movie that has reached its end. return(-1); } else { // No new frame available yet: return(false); } } if (!PSYCH_USE_QT_GWORLDS) { // Blocking wait-code for non-GWorld mode: // Try up to 1000 iterations for arrival of requested image data in wait-mode: failcount=0; while ((failcount < 1000) && !QTVisualContextIsNewImageAvailable(theMoviecontext, NULL)) { PsychWaitIntervalSeconds(0.005); MoviesTask(theMovie, 0); failcount++; } // No new frame available and there won't be any in the future, because this is a non-looping // movie that has reached its end. if ((failcount>=1000) && IsMovieDone(theMovie) && (movieRecordBANK[moviehandle].loopflag == 0)) { return(-1); } // Fetch new OpenGL texture with the new movie image frame: error = QTVisualContextCopyImageForTime(theMoviecontext, kCFAllocatorDefault, NULL, &newImage); if ((error!=noErr) || newImage == NULL) { PsychErrorExitMsg(PsychError_internal, "OpenGL<->Quicktime texture fetch failed!!!"); } // Disable client storage, if it was enabled: glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_FALSE); // Build a standard PTB texture record: CVOpenGLTextureGetCleanTexCoords (newImage, lowerLeft, lowerRight, upperRight, upperLeft); texid = CVOpenGLTextureGetName(newImage); // Assign texture rectangle: PsychMakeRect(outRect, upperLeft[0], upperLeft[1], lowerRight[0], lowerRight[1]); // Set texture orientation as if it were an inverted Offscreen window: Upside-down. out_texture->textureOrientation = (CVOpenGLTextureIsFlipped(newImage)) ? 3 : 4; // Assign OpenGL texture id: out_texture->textureNumber = texid; // Store special texture object as part of the PTB texture record: out_texture->targetSpecific.QuickTimeGLTexture = newImage; } else { // Synchronous texture fetch code for GWorld rendering mode: // At this point, the GWorld should contain the source image for creating a // standard OpenGL texture: // Disable client storage, if it was enabled: glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_FALSE); // Build a standard PTB texture record: // Assign texture rectangle: GetMovieBox(theMovie, &rect); // Hack: Need to extend rect by 4 pixels, because GWorlds are 4 pixels-aligned via // image row padding: rect.right = rect.right + 4; PsychMakeRect(out_texture->rect, rect.left, rect.top, rect.right, rect.bottom); // Set NULL - special texture object as part of the PTB texture record: out_texture->targetSpecific.QuickTimeGLTexture = NULL; // Set texture orientation as if it were an inverted Offscreen window: Upside-down. out_texture->textureOrientation = 3; // Setup a pointer to our GWorld as texture data pointer: out_texture->textureMemorySizeBytes = 0; // Quicktime textures are aligned on 4 Byte boundaries: out_texture->textureByteAligned = 4; // Lock GWorld: if(!LockPixels(GetGWorldPixMap(movieRecordBANK[moviehandle].QTMovieGWorld))) { // Locking surface failed! We abort. PsychErrorExitMsg(PsychError_internal, "PsychQTGetTextureFromMovie(): Locking GWorld pixmap surface failed!!!"); } // This will retrieve an OpenGL compatible pointer to the GWorlds pixel data and assign it to our texmemptr: out_texture->textureMemory = (GLuint*) GetPixBaseAddr(GetGWorldPixMap(movieRecordBANK[moviehandle].QTMovieGWorld)); // Let PsychCreateTexture() do the rest of the job of creating, setting up and // filling an OpenGL texture with GWorlds content: PsychCreateTexture(out_texture); // Undo hack from above after texture creation: Now we need the real width of the // texture for proper texture coordinate assignments in drawing code et al. rect.right = rect.right - 4; PsychMakeRect(outRect, rect.left, rect.top, rect.right, rect.bottom); // Unlock GWorld surface. We do a glFinish() before, for safety reasons... //glFinish(); UnlockPixels(GetGWorldPixMap(movieRecordBANK[moviehandle].QTMovieGWorld)); // Ready to use the texture... We're done. } // Normalize texture rectangle and assign it: PsychNormalizeRect(outRect, out_texture->rect); rate = FixedToFloat(GetMovieRate(theMovie)); // Detection of dropped frames: This is a heuristic. We'll see how well it works out... if (rate && presentation_timestamp) { // Try to check for dropped frames in playback mode: // Expected delta between successive presentation timestamps: targetdelta = 1.0f / (movieRecordBANK[moviehandle].fps * rate); // Compute real delta, given rate and playback direction: if (rate>0) { realdelta = *presentation_timestamp - movieRecordBANK[moviehandle].last_pts; if (realdelta<0) realdelta = 0; } else { realdelta = -1.0 * (*presentation_timestamp - movieRecordBANK[moviehandle].last_pts); if (realdelta<0) realdelta = 0; } frames = realdelta / targetdelta; // Dropped frames? if (frames > 1 && movieRecordBANK[moviehandle].last_pts>=0) { movieRecordBANK[moviehandle].nr_droppedframes += (int) (frames - 1 + 0.5); } movieRecordBANK[moviehandle].last_pts = *presentation_timestamp; } // Manually advance movie time, if in fetch mode: if (0 == GetMovieRate(theMovie)) { // We are in manual fetch mode: Need to manually advance movie time to next // media sample: if (nextFramesTime == myNextTime) { // Invalid value? Try to hack something that gets us unstuck: myNextTime = GetMovieTime(theMovie, NULL); nextFramesTime = myNextTime + (TimeValue) 1; } SetMovieTimeValue(theMovie, nextFramesTime); } // Check if end of movie is reached. Rewind, if so... if (IsMovieDone(theMovie) && movieRecordBANK[moviehandle].loopflag > 0) { if (GetMovieRate(theMovie)>0) { GoToBeginningOfMovie(theMovie); } else { GoToEndOfMovie(theMovie); } } return(TRUE); }
/* * PsychQTCreateMovie() -- Create a movie object. * * This function tries to open a Quicktime-Moviefile and create an * associated movie object for it. * * win = Pointer to window record of associated onscreen window. * moviename = char* with the name of the moviefile. * preloadSecs = How many seconds of the movie should be preloaded/prefetched into RAM at movie open time? * moviehandle = handle to the new movie. */ void PsychQTCreateMovie(PsychWindowRecordType *win, const char* moviename, double preloadSecs, int* moviehandle) { Movie theMovie = NULL; QTVisualContextRef QTMovieContext = NULL; QTAudioContextRef QTAudioContext = NULL; int i, slotid; OSErr error; CFStringRef movieLocation; CFURLRef movieURLLocation; CFStringRef coreAudioDeviceUID; psych_bool trueValue = TRUE; QTNewMoviePropertyElement newMovieProperties[4] = {0}; int propcount = 0; char msgerr[10000]; char errdesc[1000]; Rect movierect; psych_bool printErrors; // Suppress output of error-messages if moviehandle == 1000. That means we // run in our own Posix-Thread, not in the Matlab-Thread. Printing via Matlabs // printing facilities would likely cause a terrible crash. printErrors = (*moviehandle == -1000) ? FALSE : TRUE; // Set movie handle to "failed" initially: *moviehandle = -1; // We startup the Quicktime subsystem only on first invocation. if (firsttime) { #if PSYCH_SYSTEM == PSYCH_WINDOWS // Initialize Quicktime for Windows compatibility layer: This will fail if // QT isn't installed on the Windows machine... error = InitializeQTML(0); if (error!=noErr) { if (printErrors) { PsychErrorExitMsg(PsychError_internal, "Quicktime Media Layer initialization failed: Quicktime not properly installed?!?"); } else return; } #endif // Initialize Quicktime-Subsystem: error = EnterMovies(); if (error!=noErr) { if (printErrors) PsychErrorExitMsg(PsychError_internal, "Quicktime EnterMovies() failed!!!"); else return; } firsttime = FALSE; } if (!PsychIsOnscreenWindow(win)) { if (printErrors) PsychErrorExitMsg(PsychError_user, "Provided windowPtr is not an onscreen window."); else return; } if (NULL==moviename) { if (printErrors) PsychErrorExitMsg(PsychError_internal, "NULL-Ptr instead of moviename passed!"); else return; } if (numMovieRecords >= PSYCH_MAX_MOVIES) { *moviehandle = -2; if (printErrors) PsychErrorExitMsg(PsychError_user, "Allowed maximum number of simultaneously open movies exceeded!"); else return; } // Search first free slot in movieRecordBANK: for (i=0; (i < PSYCH_MAX_MOVIES) && (movieRecordBANK[i].theMovie); i++); if (i>=PSYCH_MAX_MOVIES) { *moviehandle = -2; if (printErrors) PsychErrorExitMsg(PsychError_user, "Allowed maximum number of simultaneously open movies exceeded!"); else return; } // Slot slotid will contain the movie record for our new movie object: slotid=i; // Create name-string for moviename: movieLocation = CFStringCreateWithCString (kCFAllocatorDefault, moviename, kCFStringEncodingASCII); // Zero-out new record in moviebank: movieRecordBANK[slotid].theMovie=NULL; movieRecordBANK[slotid].QTMovieContext=NULL; movieRecordBANK[slotid].QTAudioContext=NULL; movieRecordBANK[slotid].QTMovieGWorld=NULL; if (!PSYCH_USE_QT_GWORLDS) { // Create QTGLTextureContext: #if PSYCH_SYSTEM != PSYCH_WINDOWS error = QTOpenGLTextureContextCreate (kCFAllocatorDefault, win->targetSpecific.contextObject, win->targetSpecific.pixelFormatObject, NULL, &QTMovieContext); #endif if (error!=noErr) { if (printErrors) PsychErrorExitMsg(PsychError_internal, "OpenGL Quicktime visual context creation failed!!!"); else return; } } // The Movie location newMovieProperties[propcount].propClass = kQTPropertyClass_DataLocation; if (strstr(moviename, "http:") || strstr(moviename, "ftp:")) { // Open movie from URL, e.g., http- or ftp- server: movieURLLocation = CFURLCreateWithString(kCFAllocatorDefault, movieLocation, NULL); newMovieProperties[propcount].propID = kQTDataLocationPropertyID_CFURL; newMovieProperties[propcount].propValueSize = sizeof(movieURLLocation); newMovieProperties[propcount++].propValueAddress = (void*) &movieURLLocation; } else { // Open movie file from filesystem: newMovieProperties[propcount].propID = kQTDataLocationPropertyID_CFStringPosixPath; newMovieProperties[propcount].propValueSize = sizeof(CFStringRef); newMovieProperties[propcount++].propValueAddress = &movieLocation; } if (!PSYCH_USE_QT_GWORLDS) { // The Movie visual context newMovieProperties[propcount].propClass = kQTPropertyClass_Context; newMovieProperties[propcount].propID = kQTContextPropertyID_VisualContext; newMovieProperties[propcount].propValueSize = sizeof(QTVisualContextRef); newMovieProperties[propcount++].propValueAddress = &QTMovieContext; } if (TRUE) { // Create QTAudioContext for default CoreAudio device: coreAudioDeviceUID = NULL; // Use default audio-output device. error =QTAudioContextCreateForAudioDevice (kCFAllocatorDefault, coreAudioDeviceUID, NULL, &QTAudioContext); if (error!=noErr) { if (printErrors) PsychErrorExitMsg(PsychError_internal, "Quicktime audio context creation failed!!!"); else return; } // The Movie audio context newMovieProperties[propcount].propClass = kQTPropertyClass_Context; newMovieProperties[propcount].propID = kQTContextPropertyID_AudioContext; newMovieProperties[propcount].propValueSize = sizeof(QTAudioContextRef); newMovieProperties[propcount++].propValueAddress = &QTAudioContext; } // The Movie active newMovieProperties[propcount].propClass = kQTPropertyClass_NewMovieProperty; newMovieProperties[propcount].propID = kQTNewMoviePropertyID_Active; newMovieProperties[propcount].propValueSize = sizeof(trueValue); newMovieProperties[propcount++].propValueAddress = &trueValue; // Instantiate the Movie error = NewMovieFromProperties(propcount, newMovieProperties, 0, NULL, &theMovie); if (error!=noErr) { QTVisualContextRelease(QTMovieContext); QTAudioContextRelease(QTAudioContext); switch(error) { case -2000: case -50: case -43: sprintf(errdesc, "File not found."); break; case -2048: sprintf(errdesc, "This is not a file that Quicktime understands."); break; case -2003: sprintf(errdesc, "Can't find media handler (codec) for this movie."); break; default: sprintf(errdesc, "Unknown: Check http://developer.apple.com/documentation/QuickTime/APIREF/ErrorCodes.htm#//apple_ref/doc/constant_group/Error_Codes"); } sprintf(msgerr, "Couldn't load movie %s! Quicktime error code %i [%s]", moviename, (int) error, errdesc); *moviehandle = (int) error; if (printErrors) PsychErrorExitMsg(PsychError_user, msgerr); else return; } CFRelease(movieLocation); if (PSYCH_USE_QT_GWORLDS) { // Determine size of images in movie: GetMovieBox(theMovie, &movierect); // Only create a GWorld if movie frames contain at least 1 pixel. This way we skip GWorld // setup on "movies" which only consist of sound tracks. if ((movierect.right - movierect.left != 0) && (movierect.bottom - movierect.top != 0)) { // Create GWorld for this movie object: // error = QTNewGWorld(&movieRecordBANK[slotid].QTMovieGWorld, k32ABGRPixelFormat, &movierect, NULL, NULL, 0); error = QTNewGWorld(&movieRecordBANK[slotid].QTMovieGWorld, 0, &movierect, NULL, NULL, 0); if (error!=noErr) { QTAudioContextRelease(QTAudioContext); DisposeMovie(movieRecordBANK[slotid].theMovie); movieRecordBANK[slotid].theMovie=NULL; if (printErrors) PsychErrorExitMsg(PsychError_internal, "Quicktime GWorld creation failed!!!"); else return; } // Attach this GWorld as rendering target for Quicktime: SetMovieGWorld(theMovie, movieRecordBANK[slotid].QTMovieGWorld, NULL); } } // Preload preloadSecs seconds of movie into system RAM for faster playback: if (preloadSecs > 0) LoadMovieIntoRam(theMovie, 0, ((long) preloadSecs + 0.5) * GetMovieTimeScale(theMovie), keepInRam); // Special setting - 1 means: Load whole movie into RAM: if (preloadSecs == -1) LoadMovieIntoRam(theMovie, 0, GetMovieDuration(theMovie), keepInRam); // We don't preroll: Didn't help for async playback, but leads to failure in // manual playback mode: PrerollMovie(theMovie, 0, FloatToFixed(1)); // MoviesTask() it to make sure start of plaback will be as stutter-free as possible: MoviesTask(theMovie, 10000); // Assign new record in moviebank: movieRecordBANK[slotid].theMovie=theMovie; movieRecordBANK[slotid].QTMovieContext=QTMovieContext; movieRecordBANK[slotid].QTAudioContext=QTAudioContext; movieRecordBANK[slotid].loopflag = 0; *moviehandle = slotid; // Increase counter: numMovieRecords++; // Compute basic movie properties - Duration and fps as well as image size: // Compute duration in seconds: movieRecordBANK[slotid].movieduration = (double) GetMovieDuration(theMovie) / (double) GetMovieTimeScale(theMovie); // Compute expected framerate, assuming a linear spacing between frames: It is derived as // reciprocal of the duration of the first video frame in the movie: movieRecordBANK[slotid].fps = PsychDetermineMovieFramecountAndFps(theMovie, NULL); // Determine size of images in movie: GetMovieBox(theMovie, &movierect); movieRecordBANK[slotid].width = movierect.right - movierect.left; movieRecordBANK[slotid].height = movierect.bottom - movierect.top; // We set nrframes == -1 to indicate that this value is not yet available. // Will do counting on first query for this parameter as it is very time-consuming: movieRecordBANK[slotid].nrframes = -1; return; }
void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { if (nlhs < 0) { mexErrMsgTxt("Too few output arguments."); return; } if (nlhs > 1) { mexErrMsgTxt("Too many output arguments."); return; } if (nrhs < 1) { mexErrMsgTxt("Too few input arguments."); return; } if (nrhs > 3) { mexErrMsgTxt("Too many input arguments."); return; } TimeValue duration; TimeRecord myTimeRecord; Rect bounds; OSErr result = 0; short resRefNum = -1; short actualResId = DoTheRightThing; FSSpec theFSSpec; GWorldPtr offWorld; Movie theMovie = nil; MovieController thePlayer = nil; MovieDrawingCompleteUPP myDrawCompleteProc; long frame_end; long myStep = 1; char location[PATH_BUFFER_SIZE]; long frame_count; mwSize cdims[2]; mxGetString(prhs[0], location, PATH_BUFFER_SIZE); if (nrhs > 2) { frame_start = rint(mxGetScalar(prhs[1])); frame_end = rint(mxGetScalar(prhs[2])); } else if (nrhs > 1) { frame_start = 1; frame_end = rint(mxGetScalar(prhs[1])); } else { frame_start = 1; frame_end = 0; } if (frame_start < 1) { mexErrMsgTxt("Error: the starting frame must be positive\n"); return; } if (frame_end < 0) { mexErrMsgTxt("Error: the ending frame must be positive\n"); return; } if (frame_end != 0 && frame_end < frame_start) { mexErrMsgTxt("Error: the ending frame must not be less than the starting frame\n"); return; } myDrawCompleteProc = NewMovieDrawingCompleteUPP(DrawCompleteProc); EnterMovies(); if (NativePathNameToFSSpec(location, &theFSSpec, 0) || OpenMovieFile(&theFSSpec, &resRefNum, 0) || NewMovieFromFile(&theMovie, resRefNum, &actualResId, 0, 0, 0)) { mexErrMsgTxt("Error: failed to open movie\n"); return; } if (resRefNum != -1) CloseMovieFile(resRefNum); GetMovieBox(theMovie, &bounds); QTNewGWorld(&offWorld, k32ARGBPixelFormat, &bounds, NULL, NULL, 0); LockPixels(GetGWorldPixMap(offWorld)); SetGWorld(offWorld, NULL); thePlayer = NewMovieController(theMovie, &bounds, mcTopLeftMovie | mcNotVisible); SetMovieGWorld(theMovie, offWorld, NULL); SetMovieActive(theMovie, true); SetMovieDrawingCompleteProc(theMovie, movieDrawingCallWhenChanged, myDrawCompleteProc, (long) offWorld); GetMovieTime(theMovie, &myTimeRecord); duration = GetMovieDuration(theMovie); // Compute the number of frames for allocation of output structure frame_count = 0; while ((frame_end == 0 || frame_count < frame_end) && GetMovieTime(theMovie, NULL) < duration) { frame_count++; MCDoAction(thePlayer, mcActionStep, (Ptr) myStep); } SetMovieTime(theMovie, &myTimeRecord); // Ignore frames greater than those in the file if (frame_end == 0 || frame_count < frame_end) frame_end = frame_count; cdims[0] = frame_end - frame_start + 1; // Indices are one-based cdims[1] = 1; plhs[0] = mxCreateCellArray(2, cdims); // Step through the movie and save the frame when in the chosen interval // Note: the step size seems to be handled as a short internally. // Using anything greater than 32758 will seek to an incorrect frame frame_num = 1; while (frame_num <= frame_end) { MCDoAction(thePlayer, mcActionStep, (Ptr) myStep); if (frame_num >= frame_start) { MCIdle(thePlayer); mxSetCell(plhs[0], frame_num - frame_start, framedata); } frame_num++; } UnlockPixels(GetGWorldPixMap (offWorld)); DisposeGWorld(offWorld); DisposeMovieController (thePlayer); DisposeMovie(theMovie); DisposeMovieDrawingCompleteUPP(myDrawCompleteProc); ExitMovies(); return; }
boolean playeropenmovieinwindow ( ptrfilespec f ) { // // 2006-06-23 creedon: FSRef-zed // // 7.0b4 PBS: open a movie in the QuickTime Player window and display it. If the window isn't already open, open it. // short movieresref; OSErr err; hdlwindowinfo hinfo; FSSpec fs; if (macgetfsspec (f, &fs) != noErr) return (false); if (!findplayerwindow (&hinfo)) playeropencommand (); // If the Player window doesn't exist, create a new one. getwindowinfo (playerwindow, &playerwindowinfo); playerdisposecurrentmovie (); // make sure the current movie has been disposed SetGWorld (GetWindowPort (playerwindow), nil); err = OpenMovieFile ( &fs, &movieresref, fsRdPerm); if (err != noErr) return (false); NewMovieFromFile (¤tmovie, movieresref, nil, nil, newMovieActive, nil); CloseMovieFile (movieresref); SetMovieGWorld (currentmovie, GetWindowPort (playerwindow), nil); GetMovieBox (currentmovie, ¤tmovierect); OffsetRect (¤tmovierect, -currentmovierect.left, -currentmovierect.top); SetMovieBox (currentmovie, ¤tmovierect); currentcontroller = NewMovieController (currentmovie, ¤tmovierect, mcTopLeftMovie); MCGetControllerBoundsRect (currentcontroller, ¤tmovierect); //MCSetActionFilterWithRefCon (currentcontroller, NewMCActionFilterWithRefConProc(playermoviecontrollereventfilter), (long) playerwindow); SizeWindow (playerwindow, currentmovierect.right, currentmovierect.bottom, false); (**playerwindowinfo).contentrect = currentmovierect; AlignWindow (playerwindow, false, nil, nil); // position for optimal playback //MCDoAction (currentcontroller, mcActionSetKeysEnabled, (void *) true); /*enable keyboard input*/ /*No!*/ MoviesTask (nil, 0); MCIdle (currentcontroller); return (true); } // playeropenmovieinwindow
void QTCmpr_CompressSequence (WindowObject theWindowObject) { ComponentInstance myComponent = NULL; GWorldPtr myImageWorld = NULL; // the graphics world we draw the images in PixMapHandle myPixMap = NULL; Movie mySrcMovie = NULL; Track mySrcTrack = NULL; Movie myDstMovie = NULL; Track myDstTrack = NULL; Media myDstMedia = NULL; Rect myRect; PicHandle myPicture = NULL; CGrafPtr mySavedPort = NULL; GDHandle mySavedDevice = NULL; SCTemporalSettings myTimeSettings; SCDataRateSettings myRateSettings; FSSpec myFile; Boolean myIsSelected = false; Boolean myIsReplacing = false; short myRefNum = -1; StringPtr myMoviePrompt = QTUtils_ConvertCToPascalString(kQTCSaveMoviePrompt); StringPtr myMovieFileName = QTUtils_ConvertCToPascalString(kQTCSaveMovieFileName); MatrixRecord myMatrix; ImageDescriptionHandle myImageDesc = NULL; TimeValue myCurMovieTime = 0L; TimeValue myOrigMovieTime = 0L; // current movie time, when compression is begun short myFrameNum; long myFlags = 0L; long myNumFrames = 0L; long mySrcMovieDuration = 0L; // duration of source movie OSErr myErr = noErr; #if USE_ASYNC_COMPRESSION ICMCompletionProcRecord myICMComplProcRec; ICMCompletionProcRecordPtr myICMComplProcPtr = NULL; OSErr myICMComplProcErr = noErr; myICMComplProcRec.completionProc = NULL; myICMComplProcRec.completionRefCon = 0L; #endif if (theWindowObject == NULL) goto bail; ////////// // // get the movie and the first video track in the movie // ////////// mySrcMovie = (**theWindowObject).fMovie; if (mySrcMovie == NULL) goto bail; mySrcTrack = GetMovieIndTrackType(mySrcMovie, 1, VideoMediaType, movieTrackMediaType); if (mySrcTrack == NULL) goto bail; // stop the movie; we don't want it to be playing while we're (re)compressing it SetMovieRate(mySrcMovie, (Fixed)0L); // get the current movie time, when compression is begun; we'll restore this later myOrigMovieTime = GetMovieTime(mySrcMovie, NULL); ////////// // // configure and display the Standard Image Compression dialog box // ////////// // open an instance of the Standard Image Compression dialog component myComponent = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType); if (myComponent == NULL) goto bail; // turn off "best depth" option in the compression dialog, because all of our // buffering is done at 32-bits (regardless of the depth of the source data) // // a more ambitious approach would be to loop through each of the video sample // descriptions in each of the video tracks looking for the deepest depth, and // using that for the best depth; better yet, we could find out which compressors // were used and set one of those as the default in the compression dialog SCGetInfo(myComponent, scPreferenceFlagsType, &myFlags); myFlags &= ~scShowBestDepth; SCSetInfo(myComponent, scPreferenceFlagsType, &myFlags); // because we are recompressing a movie that may have a variable frame rate, // we want to allow the user to leave the frame rate text field blank (in which // case we can preserve the frame durations of the source movie); if the user // enters a number, we will resample the movie at a new frame rate; if we don't // clear this flag, the compression dialog will not allow zero in the frame rate field // // NOTE: we could have set this flag above when we cleared the scShowBestDepth flag; // it is done here for clarity. SCGetInfo(myComponent, scPreferenceFlagsType, &myFlags); myFlags |= scAllowZeroFrameRate; SCSetInfo(myComponent, scPreferenceFlagsType, &myFlags); // get the number of video frames in the movie myNumFrames = QTUtils_GetFrameCount(mySrcTrack); // get the bounding rectangle of the movie, create a 32-bit GWorld with those // dimensions, and draw the movie poster picture into it; this GWorld will be // used for the test image in the compression dialog box and for rendering movie // frames myPicture = GetMoviePosterPict(mySrcMovie); if (myPicture == NULL) goto bail; GetMovieBox(mySrcMovie, &myRect); myErr = NewGWorld(&myImageWorld, 32, &myRect, NULL, NULL, 0L); if (myErr != noErr) goto bail; // get the pixmap of the GWorld; we'll lock the pixmap, just to be safe myPixMap = GetGWorldPixMap(myImageWorld); if (!LockPixels(myPixMap)) goto bail; // draw the movie poster image into the GWorld GetGWorld(&mySavedPort, &mySavedDevice); SetGWorld(myImageWorld, NULL); EraseRect(&myRect); DrawPicture(myPicture, &myRect); KillPicture(myPicture); SetGWorld(mySavedPort, mySavedDevice); // set the picture to be displayed in the dialog box; passing NULL for the rect // means use the entire image; passing 0 for the flags means to use the default // system method of displaying the test image, which is currently a combination // of cropping and scaling; personally, I prefer scaling (your mileage may vary) SCSetTestImagePixMap(myComponent, myPixMap, NULL, scPreferScaling); // install the custom procs, if requested // we can install two kinds of custom procedures for use in connection with // the standard dialog box: (1) a modal-dialog filter function, and (2) a hook // function to handle the custom button in the dialog box if (gUseExtendedProcs) QTCmpr_InstallExtendedProcs(myComponent, (long)myPixMap); // set up some default settings for the compression dialog SCDefaultPixMapSettings(myComponent, myPixMap, true); // clear out the default frame rate chosen by Standard Compression (a frame rate // of 0 means to use the rate of the source movie) myErr = SCGetInfo(myComponent, scTemporalSettingsType, &myTimeSettings); if (myErr != noErr) goto bail; myTimeSettings.frameRate = 0; SCSetInfo(myComponent, scTemporalSettingsType, &myTimeSettings); // request image compression settings from the user; in other words, put up the dialog box myErr = SCRequestSequenceSettings(myComponent); if (myErr == scUserCancelled) goto bail; // get a copy of the temporal settings the user entered; we'll need them for some // of our calculations (in a simpler application, we'd never have to look at them) SCGetInfo(myComponent, scTemporalSettingsType, &myTimeSettings); ////////// // // adjust the data rate [to be supplied][relevant only for movies that have sound tracks] // ////////// ////////// // // adjust the sample count // // if the user wants to resample the frame rate of the movie (as indicated a non-zero // value in the frame rate field) calculate the number of frames and duration for the new movie // ////////// if (myTimeSettings.frameRate != 0) { long myDuration = GetMovieDuration(mySrcMovie); long myTimeScale = GetMovieTimeScale(mySrcMovie); float myFloat = (float)myDuration * myTimeSettings.frameRate; myNumFrames = myFloat / myTimeScale / 65536; if (myNumFrames == 0) myNumFrames = 1; } ////////// // // get the name and location of the new movie file // ////////// // prompt the user for a file to put the compressed image into; in theory, the name // should have a file extension appropriate to the type of compressed data selected by the user; // this is left as an exercise for the reader QTFrame_PutFile(myMoviePrompt, myMovieFileName, &myFile, &myIsSelected, &myIsReplacing); if (!myIsSelected) goto bail; // delete any existing file of that name if (myIsReplacing) { myErr = DeleteMovieFile(&myFile); if (myErr != noErr) goto bail; } ////////// // // create the target movie // ////////// myErr = CreateMovieFile(&myFile, sigMoviePlayer, smSystemScript, createMovieFileDeleteCurFile | createMovieFileDontCreateResFile, &myRefNum, &myDstMovie); if (myErr != noErr) goto bail; // create a new video movie track with the same dimensions as the entire source movie myDstTrack = NewMovieTrack(myDstMovie, (long)(myRect.right - myRect.left) << 16, (long)(myRect.bottom - myRect.top) << 16, kNoVolume); if (myDstTrack == NULL) goto bail; // create a media for the new track with the same time scale as the source movie; // because the time scales are the same, we don't have to do any time scale conversions. myDstMedia = NewTrackMedia(myDstTrack, VIDEO_TYPE, GetMovieTimeScale(mySrcMovie), 0, 0); if (myDstMedia == NULL) goto bail; // copy the user data and settings from the source to the dest movie CopyMovieSettings(mySrcMovie, myDstMovie); // set movie matrix to identity and clear the movie clip region (because the conversion // process transforms and composites all video tracks into one untransformed video track) SetIdentityMatrix(&myMatrix); SetMovieMatrix(myDstMovie, &myMatrix); SetMovieClipRgn(myDstMovie, NULL); // set the movie to highest quality imaging SetMoviePlayHints(mySrcMovie, hintsHighQuality, hintsHighQuality); myImageDesc = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription)); if (myImageDesc == NULL) goto bail; // prepare for adding frames to the movie myErr = BeginMediaEdits(myDstMedia); if (myErr != noErr) goto bail; ////////// // // compress the image sequence // // we are going to step through the source movie, compress each frame, and then add // the compressed frame to the destination movie // ////////// myErr = SCCompressSequenceBegin(myComponent, myPixMap, NULL, &myImageDesc); if (myErr != noErr) goto bail; #if USE_ASYNC_COMPRESSION myFlags = codecFlagUpdatePrevious + codecFlagUpdatePreviousComp + codecFlagLiveGrab; SCSetInfo(myComponent, scCodecFlagsType, &myFlags); #endif // clear out our image GWorld and set movie to draw into it SetGWorld(myImageWorld, NULL); EraseRect(&myRect); SetMovieGWorld(mySrcMovie, myImageWorld, GetGWorldDevice(myImageWorld)); // set current time value to beginning of the source movie myCurMovieTime = 0; // get a value we'll need inside the loop mySrcMovieDuration = GetMovieDuration(mySrcMovie); // loop through all of the interesting times we counted above for (myFrameNum = 0; myFrameNum < myNumFrames; myFrameNum++) { short mySyncFlag; TimeValue myDuration; long myDataSize; Handle myCompressedData; ////////// // // get the next frame of the source movie // ////////// // if we are resampling the movie, step to the next frame if (myTimeSettings.frameRate) { myCurMovieTime = myFrameNum * mySrcMovieDuration / (myNumFrames - 1); myDuration = mySrcMovieDuration / myNumFrames; } else { OSType myMediaType = VIDEO_TYPE; myFlags = nextTimeMediaSample; // if this is the first frame, include the frame we are currently on if (myFrameNum == 0) myFlags |= nextTimeEdgeOK; // if we are maintaining the frame durations of the source movie, // skip to the next interesting time and get the duration for that frame GetMovieNextInterestingTime(mySrcMovie, myFlags, 1, &myMediaType, myCurMovieTime, 0, &myCurMovieTime, &myDuration); } SetMovieTimeValue(mySrcMovie, myCurMovieTime); MoviesTask(mySrcMovie, 0); MoviesTask(mySrcMovie, 0); MoviesTask(mySrcMovie, 0); // if data rate constraining is being done, tell Standard Compression the // duration of the current frame in milliseconds; we only need to do this // if the frames have variable durations if (!SCGetInfo(myComponent, scDataRateSettingsType, &myRateSettings)) { myRateSettings.frameDuration = myDuration * 1000 / GetMovieTimeScale(mySrcMovie); SCSetInfo(myComponent, scDataRateSettingsType, &myRateSettings); } ////////// // // compress the current frame of the source movie and add it to the destination movie // ////////// // if SCCompressSequenceFrame completes successfully, myCompressedData will hold // a handle to the newly-compressed image data and myDataSize will be the size of // the compressed data (which will usually be different from the size of the handle); // also mySyncFlag will be a value that that indicates whether or not the frame is a // key frame (and which we pass directly to AddMediaSample); note that we do not need // to dispose of myCompressedData, since SCCompressSequenceEnd will do that for us #if !USE_ASYNC_COMPRESSION myErr = SCCompressSequenceFrame(myComponent, myPixMap, &myRect, &myCompressedData, &myDataSize, &mySyncFlag); if (myErr != noErr) goto bail; #else if (myICMComplProcPtr == NULL) { myICMComplProcRec.completionProc = NewICMCompletionProc(QTCmpr_CompletionProc); myICMComplProcRec.completionRefCon = (long)&myICMComplProcErr; myICMComplProcPtr = &myICMComplProcRec; } myICMComplProcErr = kAsyncDefaultValue; myErr = SCCompressSequenceFrameAsync(myComponent, myPixMap, &myRect, &myCompressedData, &myDataSize, &mySyncFlag, myICMComplProcPtr); if (myErr != noErr) goto bail; // spin our wheels while we're waiting for the compress call to complete while (myICMComplProcErr == kAsyncDefaultValue) { EventRecord myEvent; WaitNextEvent(0, &myEvent, 60, NULL); SCAsyncIdle(myComponent); } myErr = myICMComplProcErr; #endif myErr = AddMediaSample(myDstMedia, myCompressedData, 0, myDataSize, myDuration, (SampleDescriptionHandle)myImageDesc, 1, mySyncFlag, NULL); if (myErr != noErr) goto bail; } // close the compression sequence; this will dispose of the image description // and compressed data handles allocated by SCCompressSequenceBegin SCCompressSequenceEnd(myComponent); ////////// // // add the media data to the destination movie // ////////// myErr = EndMediaEdits(myDstMedia); if (myErr != noErr) goto bail; InsertMediaIntoTrack(myDstTrack, 0, 0, GetMediaDuration(myDstMedia), fixed1); // add the movie resource to the dst movie file. myErr = AddMovieResource(myDstMovie, myRefNum, NULL, NULL); if (myErr != noErr) goto bail; // flatten the movie data [to be supplied] // close the movie file CloseMovieFile(myRefNum); bail: // close the Standard Compression component if (myComponent != NULL) CloseComponent(myComponent); if (mySrcMovie != NULL) { // restore the source movie's original graphics port and device SetMovieGWorld(mySrcMovie, mySavedPort, mySavedDevice); // restore the source movie's original movie time SetMovieTimeValue(mySrcMovie, myOrigMovieTime); } // restore the original graphics port and device SetGWorld(mySavedPort, mySavedDevice); // delete the GWorld we were drawing frames into if (myImageWorld != NULL) DisposeGWorld(myImageWorld); #if USE_ASYNC_COMPRESSION if (myICMComplProcRec.completionProc != NULL) DisposeICMCompletionUPP(myICMComplProcRec.completionProc); #endif free(myMoviePrompt); free(myMovieFileName); }