// CompressRLE // Main compress routine, this function will call the appropriate RLE compression // method depending on the pixel depth of the source image. OSErr CompressPixMapRLE(PixMapHandle pixMapHdl, Ptr compressBuffer, Size *compressBufferSizePtr) { Handle hdl = NULL; Ptr tempPtr = NULL,srcData; Ptr pixBaseAddr = GetPixBaseAddr(pixMapHdl); OSType pixelFormat = GETPIXMAPPIXELFORMAT(*pixMapHdl); int depth = QTGetPixelSize(pixelFormat); long rowBytes = QTGetPixMapHandleRowBytes(pixMapHdl); int width = (**pixMapHdl).bounds.right - (**pixMapHdl).bounds.left; int i, height = (**pixMapHdl).bounds.bottom - (**pixMapHdl).bounds.top; Size widthByteSize = (depth * (long)width + 7) >> 3; OSErr err = noErr; // need to remove padding between rows? if(widthByteSize != rowBytes){ // Make a temp buffer for the source hdl = NewHandle(height * widthByteSize); err = MemError(); if (err) goto bail; HLock(hdl); srcData = tempPtr = *hdl; // Get rid of row bytes padding for (i = 0; i < height; i++) { BlockMoveData(pixBaseAddr, tempPtr, widthByteSize); tempPtr += widthByteSize; pixBaseAddr += rowBytes; } }else srcData = pixBaseAddr; // Compress switch (depth) { case 1: CompressRLE8((UInt8*)srcData, height * widthByteSize, compressBuffer, compressBufferSizePtr); break; case 8: CompressRLE8((UInt8*)srcData, height * widthByteSize, compressBuffer, compressBufferSizePtr); break; case 16: CompressRLE16((UInt16*)srcData, height * (widthByteSize >> 1), compressBuffer, compressBufferSizePtr); break; case 32: CompressRLE32((UInt32*)srcData, height * (widthByteSize >> 2), compressBuffer, compressBufferSizePtr); break; } bail: if (hdl) DisposeHandle(hdl); return err; }
//////////////////////////////////////////////////////////////////////////////// // virtual int LLMediaImplQuickTime::getMediaDataWidth() const { if ( mGWorldHandle ) { int depth = getMediaDepth(); if (depth < 1) depth = 1; // ALWAYS use the row bytes from the PixMap if we have a GWorld because // sometimes it's not the same as mMediaDepth * mMediaWidth ! PixMapHandle pix_map_handle = GetGWorldPixMap( mGWorldHandle ); return QTGetPixMapHandleRowBytes( pix_map_handle ) / depth; } else { return LLMediaImplCommon::getMediaDataWidth(); } }
//////////////////////////////////////////////////////////////////////////////// // virtual bool LLMediaImplQuickTime::sizeChanged() { if ( ! mMovieHandle ) return false; // sanitize size of movie Rect movie_rect; setMovieBoxEnhanced( &movie_rect ); // we need this later int width = ( movie_rect.right - movie_rect.left ); int height = ( movie_rect.bottom - movie_rect.top ); std::cout << "LLMEDIA> size changed to " << width << " x " << height << std::endl; setMediaSize( width, height ); // media depth won't change int depth_bits = getMediaDepth() * 8; GWorldPtr old_gworld_handle = mGWorldHandle; if (old_gworld_handle) { GWorldFlags result = UpdateGWorld( &mGWorldHandle, depth_bits, &movie_rect, NULL, NULL, 0 ); if ( gwFlagErr == result ) { // TODO: unrecoverable?? throw exception? return something? return false; } } else { OSErr result = NewGWorld( &mGWorldHandle, depth_bits, &movie_rect, NULL, NULL, keepLocal | pixelsLocked ); if ( noErr != result ) { // ATODO: unrecoverable?? throw exception? return something? return false; } // clear memory in GWorld to avoid random screen visual fuzz from uninitialized texture data if ( mGWorldHandle ) { PixMapHandle pix_map_handle = GetGWorldPixMap( mGWorldHandle ); unsigned char* ptr = ( unsigned char* )GetPixBaseAddr( pix_map_handle ); memset( ptr, 0x00, height * QTGetPixMapHandleRowBytes( pix_map_handle ) ); } } // point movie at GWorld if it's new if ( mMovieHandle && ! old_gworld_handle ) { SetMovieGWorld( mMovieHandle, mGWorldHandle, GetGWorldDevice ( mGWorldHandle ) ); } // update movie controller if ( mMovieController ) { MCSetControllerPort( mMovieController, mGWorldHandle ); MCPositionController( mMovieController, &movie_rect, &movie_rect, mcTopLeftMovie | mcPositionDontInvalidate ); MCMovieChanged( mMovieController, mMovieHandle ); } // Emit event with size change so the calling app knows about it too LLMediaEvent event( this ); mEventEmitter.update( &LLMediaObserver::onMediaSizeChange, event ); return true; }
// ###################################################################### OSErr QuickTimeGrabber::Impl::grabData(SGChannel c, Ptr p, long len, TimeValue time) { if (itsGotFrame) { LDEBUG("already got a frame on this iteration"); return noErr; } // we only care about the video if (c != itsSGChanVideo.it) { return noErr; } // reset frame and time counters after a stop/start if (time < itsPrevTime) { LDEBUG("resetting frame/time counters (current=%ld, last=%ld)", (long) time, (long) itsPrevTime); itsPrevTime = 0; itsFrameCount = 0; } if (itsTimeScale == 0) { LDEBUG("setting up time scale & timebase"); Fixed framesPerSecond; long milliSecPerFrameIgnore, bytesPerSecondIgnore; // first time here so get the time scale & timebase if (noErr != SGGetChannelTimeScale(c, &itsTimeScale)) { itsErrorMsg = "SGGetChannelTimeScale() failed"; return OSErr(-1); } if (noErr != SGGetTimeBase(itsSeqGrab.it, &itsTimeBase)) { itsErrorMsg = "SGGetTimeBase() failed"; return OSErr(-1); } if (noErr != VDGetDataRate(SGGetVideoDigitizerComponent(c), &milliSecPerFrameIgnore, &framesPerSecond, &bytesPerSecondIgnore)) { itsErrorMsg = "VDGetDataRate() failed"; return OSErr(-1); } } if (itsDrawSeq == 0) { LDEBUG("setting up decompression sequence"); // set up decompression sequence ImageDescriptionHandle imageDesc = (ImageDescriptionHandle)NewHandle(0); // retrieve a channel's current sample description, the channel // returns a sample description that is appropriate to the type // of data being captured if (noErr != SGGetChannelSampleDescription(c, (Handle)imageDesc)) { itsErrorMsg = "SGGetChannelSampleDescription() failed"; return OSErr(-1); } // make a scaling matrix for the sequence Rect sourceRect = { 0, 0 }; sourceRect.right = (**imageDesc).width; sourceRect.bottom = (**imageDesc).height; Rect scaleRect; GetPixBounds(GetGWorldPixMap(itsGWorld), &scaleRect); // if DV do high quality 720x480 both fields CodecFlags cFlags = (kDVCNTSCCodecType == (**imageDesc).cType) ? codecHighQuality : codecNormalQuality; MatrixRecord scaleMatrix; RectMatrix(&scaleMatrix, &sourceRect, &scaleRect); LINFO("sourceRect = %dx%d, scaleRect = %dx%d", sourceRect.right - sourceRect.left, sourceRect.bottom - sourceRect.top, scaleRect.right - scaleRect.left, scaleRect.bottom - scaleRect.top); // begin the process of decompressing a sequence of frames this // is a set-up call and is only called once for the sequence - // the ICM will interrogate different codecs and construct a // suitable decompression chain, as this is a time consuming // process we don't want to do this once per frame (eg. by using // DecompressImage) for more information see Ice Floe #8 // http://developer.apple.com/quicktime/icefloe/dispatch008.html // the destination is specified as the GWorld CGrafPtr dest = itsGWorld; if (noErr != DecompressSequenceBeginS (&itsDrawSeq, // pointer to field to receive unique ID for sequence imageDesc, // handle to image description structure p, // points to the compressed image data len, // size of the data buffer dest, // port for the DESTINATION image NULL, // graphics device handle, if port is set, set to NULL NULL, // decompress the entire source image - no source extraction &scaleMatrix, // transformation matrix srcCopy, // transfer mode specifier (RgnHandle)NULL, // clipping region in dest. coordinate system to use as a mask 0, // flags cFlags, // accuracy in decompression bestSpeedCodec)) // compressor identifier or special identifiers ie. bestSpeedCodec { itsErrorMsg = "DSeqBegin failed"; return OSErr(-1); } DisposeHandle((Handle)imageDesc); } // itsDrawSeq == 0 // get the TimeBase time and figure out the delta between that time // and this frame time const TimeValue timeBaseTime = GetTimeBaseTime(itsTimeBase, itsTimeScale, NULL); const TimeValue timeBaseDelta = timeBaseTime - time; const TimeValue frameTimeDelta = time - itsPrevTime; if (timeBaseDelta < 0) { itsErrorMsg = "bogus timeBaseDelta"; return OSErr(-1); } // if we have more than one queued frame and our capture rate drops // below 10 frames, skip the frame to try and catch up if ((itsQueuedFrameCount > 1) && ((itsTimeScale / frameTimeDelta) < 10) && (itsSkipFrameCount < 15)) { LDEBUG("dropping frame"); ++itsSkipFrameCount; ++itsSkipFrameCountTotal; } else { itsFrameCount++; CodecFlags ignore; // decompress a frame into the window - can queue a frame for async decompression when passed in a completion proc if (noErr != DecompressSequenceFrameS (itsDrawSeq, // sequence ID returned by DecompressSequenceBegin p, // pointer to compressed image data len, // size of the buffer 0, // in flags &ignore, // out flags NULL)) // async completion proc { itsErrorMsg = "DSeqFrameS failed"; return OSErr(-1); } // get the information we need from the GWorld Rect pbound; GetPixBounds(GetGWorldPixMap(itsGWorld), &pbound); char* const baseAddr = GetPixBaseAddr(GetGWorldPixMap(itsGWorld)); const long rowBytes = QTGetPixMapHandleRowBytes(GetGWorldPixMap(itsGWorld)); itsCurrentImage.resize(Dims(pbound.right - pbound.left, pbound.bottom - pbound.top)); Image<PixRGB<byte> >::iterator itr = itsCurrentImage.beginw(); for (int y = pbound.top; y < pbound.bottom; ++y) { char* p = baseAddr + rowBytes * (y-pbound.top); for (int x = pbound.left; x < pbound.right; ++x) { const UInt32 color = *((UInt32*)(p) + x - pbound.left); const UInt32 R = (color & 0x00FF0000) >> 16; const UInt32 G = (color & 0x0000FF00) >> 8; const UInt32 B = (color & 0x000000FF) >> 0; *itr++ = PixRGB<byte>(R,G,B); } } itsSkipFrameCount = 0; itsPrevTime = time; itsGotFrame = true; } // status information const float fps = (float)itsTimeScale / (float)frameTimeDelta; const float averagefps = ((float)itsFrameCount * (float)itsTimeScale) / (float)time; const UInt8 minutes = (time / itsTimeScale) / 60; const UInt8 seconds = (time / itsTimeScale) % 60; const UInt8 frames = (time % itsTimeScale) / frameTimeDelta; LDEBUG("#%06ld t:%ld nq:%u, %02d:%02d.%02d, fps:%5.1f av:%5.1f", itsFrameCount, time, itsQueuedFrameCount, minutes, seconds, frames, fps, averagefps); return noErr; }