//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //============================================================================= #include "quicktime_video.h" #include "quicktime_common.h" #include "quicktime_material.h" #include "quicktime_recorder.h" #include "filesystem.h" #include "tier0/icommandline.h" #include "tier1/strtools.h" #include "tier1/utllinkedlist.h" #include "tier1/KeyValues.h" #include "materialsystem/imaterial.h" #include "materialsystem/imaterialsystem.h" #include "materialsystem/MaterialSystemUtil.h" #include "materialsystem/itexture.h" #include "vtf/vtf.h" #include "pixelwriter.h" #include "tier2/tier2.h" #include "platform.h" #if defined ( WIN32 ) #include #include <../dx9sdk/include/dsound.h> #endif #include "tier0/memdbgon.h" // =========================================================================== // Singleton to expose Quicktime video subsystem // =========================================================================== static CQuickTimeVideoSubSystem g_QuickTimeSystem; EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CQuickTimeVideoSubSystem, IVideoSubSystem, VIDEO_SUBSYSTEM_INTERFACE_VERSION, g_QuickTimeSystem ); // =========================================================================== // Convars used by Quicktime // - these need to be referenced somewhere to keep the compiler from // optimizing them away // =========================================================================== ConVar QuickTime_EncodeGamma( "video_quicktime_encode_gamma", "3", FCVAR_ARCHIVE , "QuickTime Video Encode Gamma Target- 0=no gamma adjust 1=platform default gamma 2 = gamma 1.8 3 = gamma 2.2 4 = gamma 2.5", true, 0.0f, true, 4.0f ); ConVar QuickTime_PlaybackGamma( "video_quicktime_decode_gamma", "0", FCVAR_ARCHIVE , "QuickTime Video Playback Gamma Target- 0=no gamma adjust 1=platform default gamma 2 = gamma 1.8 3 = gamma 2.2 4 = gamma 2.5", true, 0.0f, true, 4.0f ); // =========================================================================== // List of file extensions and features supported by this subsystem // =========================================================================== VideoFileExtensionInfo_t s_QuickTimeExtensions[] = { { ".mov", VideoSystem::QUICKTIME, VideoSystemFeature::FULL_PLAYBACK | VideoSystemFeature::FULL_ENCODE }, { ".mp4", VideoSystem::QUICKTIME, VideoSystemFeature::FULL_PLAYBACK | VideoSystemFeature::FULL_ENCODE }, }; const int s_QuickTimeExtensionCount = ARRAYSIZE( s_QuickTimeExtensions ); const VideoSystemFeature_t CQuickTimeVideoSubSystem::DEFAULT_FEATURE_SET = VideoSystemFeature::FULL_PLAYBACK | VideoSystemFeature::FULL_ENCODE; #ifdef OSX PFNGetGWorldPixMap GetGWorldPixMap = NULL; PFNGetPixBaseAddr GetPixBaseAddr = NULL; PFNLockPixels LockPixels = NULL; PFNUnlockPixels UnlockPixels = NULL; PFNDisposeGWorld DisposeGWorld = NULL; PFNSetGWorld SetGWorld = NULL; PFNGetPixRowBytes GetPixRowBytes = NULL; #endif // =========================================================================== // CQuickTimeVideoSubSystem class // =========================================================================== CQuickTimeVideoSubSystem::CQuickTimeVideoSubSystem() : m_bQuickTimeInitialized( false ), m_LastResult( VideoResult::SUCCESS ), m_CurrentStatus( VideoSystemStatus::NOT_INITIALIZED ), m_AvailableFeatures( CQuickTimeVideoSubSystem::DEFAULT_FEATURE_SET ), m_pCommonServices( nullptr ) { } CQuickTimeVideoSubSystem::~CQuickTimeVideoSubSystem() { ShutdownQuickTime(); // Super redundant safety check } // =========================================================================== // IAppSystem methods // =========================================================================== bool CQuickTimeVideoSubSystem::Connect( CreateInterfaceFn factory ) { if ( !BaseClass::Connect( factory ) ) { return false; } if ( g_pFullFileSystem == nullptr || materials == nullptr ) { Msg( "QuickTime video subsystem failed to connect to missing a required system\n" ); return false; } return true; } void CQuickTimeVideoSubSystem::Disconnect() { BaseClass::Disconnect(); } void* CQuickTimeVideoSubSystem::QueryInterface( const char *pInterfaceName ) { if ( IS_NOT_EMPTY( pInterfaceName ) ) { if ( V_strncmp( pInterfaceName, VIDEO_SUBSYSTEM_INTERFACE_VERSION, Q_strlen( VIDEO_SUBSYSTEM_INTERFACE_VERSION ) + 1) == STRINGS_MATCH ) { return (IVideoSubSystem*) this; } } return nullptr; } InitReturnVal_t CQuickTimeVideoSubSystem::Init() { InitReturnVal_t nRetVal = BaseClass::Init(); if ( nRetVal != INIT_OK ) { return nRetVal; } return INIT_OK; } void CQuickTimeVideoSubSystem::Shutdown() { // Make sure we shut down quicktime ShutdownQuickTime(); BaseClass::Shutdown(); } // =========================================================================== // IVideoSubSystem identification methods // =========================================================================== VideoSystem_t CQuickTimeVideoSubSystem::GetSystemID() { return VideoSystem::QUICKTIME; } VideoSystemStatus_t CQuickTimeVideoSubSystem::GetSystemStatus() { return m_CurrentStatus; } VideoSystemFeature_t CQuickTimeVideoSubSystem::GetSupportedFeatures() { return m_AvailableFeatures; } const char* CQuickTimeVideoSubSystem::GetVideoSystemName() { return "Quicktime"; } // =========================================================================== // IVideoSubSystem setup and shutdown services // =========================================================================== bool CQuickTimeVideoSubSystem::InitializeVideoSystem( IVideoCommonServices *pCommonServices ) { m_AvailableFeatures = DEFAULT_FEATURE_SET; // Put here because of issue with static const int, binary OR and DEBUG builds AssertPtr( pCommonServices ); m_pCommonServices = pCommonServices; #ifdef OSX if ( !GetGWorldPixMap ) GetGWorldPixMap = (PFNGetGWorldPixMap)dlsym( RTLD_DEFAULT, "GetGWorldPixMap" ); if ( !GetPixBaseAddr ) GetPixBaseAddr = (PFNGetPixBaseAddr)dlsym( RTLD_DEFAULT, "GetPixBaseAddr" ); if ( !LockPixels ) LockPixels = (PFNLockPixels)dlsym( RTLD_DEFAULT, "LockPixels" ); if ( !UnlockPixels ) UnlockPixels = (PFNUnlockPixels)dlsym( RTLD_DEFAULT, "UnlockPixels" ); if ( !DisposeGWorld ) DisposeGWorld = (PFNDisposeGWorld)dlsym( RTLD_DEFAULT, "DisposeGWorld" ); if ( !SetGWorld ) SetGWorld = (PFNSetGWorld)dlsym( RTLD_DEFAULT, "SetGWorld" ); if ( !GetPixRowBytes ) GetPixRowBytes = (PFNGetPixRowBytes)dlsym( RTLD_DEFAULT, "GetPixRowBytes" ); if ( !GetGWorldPixMap || !GetPixBaseAddr || !LockPixels || !UnlockPixels || !DisposeGWorld || !SetGWorld || !GetPixRowBytes ) return false; #endif return ( m_bQuickTimeInitialized ) ? true : SetupQuickTime(); } bool CQuickTimeVideoSubSystem::ShutdownVideoSystem() { return ( m_bQuickTimeInitialized ) ? ShutdownQuickTime() : true; } VideoResult_t CQuickTimeVideoSubSystem::VideoSoundDeviceCMD( VideoSoundDeviceOperation_t operation, void *pDevice, void *pData ) { switch ( operation ) { case VideoSoundDeviceOperation::SET_DIRECT_SOUND_DEVICE: { return SetResult( VideoResult::OPERATION_NOT_SUPPORTED ); } case VideoSoundDeviceOperation::SET_MILES_SOUND_DEVICE: case VideoSoundDeviceOperation::HOOK_X_AUDIO: { return SetResult( VideoResult::OPERATION_NOT_SUPPORTED ); } default: { return SetResult( VideoResult::UNKNOWN_OPERATION ); } } } // =========================================================================== // IVideoSubSystem supported extensions & features // =========================================================================== int CQuickTimeVideoSubSystem::GetSupportedFileExtensionCount() { return s_QuickTimeExtensionCount; } const char* CQuickTimeVideoSubSystem::GetSupportedFileExtension( int num ) { return ( num < 0 || num >= s_QuickTimeExtensionCount ) ? nullptr : s_QuickTimeExtensions[num].m_FileExtension; } VideoSystemFeature_t CQuickTimeVideoSubSystem::GetSupportedFileExtensionFeatures( int num ) { return ( num < 0 || num >= s_QuickTimeExtensionCount ) ? VideoSystemFeature::NO_FEATURES : s_QuickTimeExtensions[num].m_VideoFeatures; } // =========================================================================== // IVideoSubSystem Video Playback and Recording Services // =========================================================================== VideoResult_t CQuickTimeVideoSubSystem::PlayVideoFileFullScreen( const char *filename, void *mainWindow, int windowWidth, int windowHeight, int desktopWidth, int desktopHeight, bool windowed, float forcedMinTime, VideoPlaybackFlags_t playbackFlags ) { OSErr status; // See if the caller is asking for a feature we can not support.... VideoPlaybackFlags_t unsupportedFeatures = VideoPlaybackFlags::PRELOAD_VIDEO; if ( playbackFlags & unsupportedFeatures ) { return SetResult( VideoResult::FEATURE_NOT_AVAILABLE ); } // Make sure we are initialized and ready if ( !m_bQuickTimeInitialized ) { SetupQuickTime(); } AssertExitV( m_CurrentStatus == VideoSystemStatus::OK, SetResult( VideoResult::SYSTEM_NOT_AVAILABLE ) ); // Set graphics port #if defined ( WIN32 ) SetGWorld ( (CGrafPtr) GetNativeWindowPort( nil ), nil ); #elif defined ( OSX ) SystemUIMode oldMode; SystemUIOptions oldOptions; GetSystemUIMode( &oldMode, &oldOptions ); if ( !windowed ) { status = SetSystemUIMode( kUIModeAllHidden, (SystemUIOptions) 0 ); Assert( status == noErr ); } SetGWorld( nil, nil ); #endif // ------------------------------------------------- // Open the quicktime file with audio Movie theQTMovie = NULL; Rect theQTMovieRect; QTAudioContextRef theAudioContext = NULL; Handle MovieFileDataRef = nullptr; OSType MovieFileDataRefType = 0; CFStringRef imageStrRef = CFStringCreateWithCString ( NULL, filename, 0 ); AssertExitV( imageStrRef != nullptr, SetResult( VideoResult::SYSTEM_ERROR_OCCURED ) ); status = QTNewDataReferenceFromFullPathCFString( imageStrRef, (QTPathStyle) kQTNativeDefaultPathStyle, 0, &MovieFileDataRef, &MovieFileDataRefType ); AssertExitV( status == noErr, SetResult( VideoResult::FILE_ERROR_OCCURED ) ); CFRelease( imageStrRef ); status = NewMovieFromDataRef( &theQTMovie, newMovieActive, nil, MovieFileDataRef, MovieFileDataRefType ); SAFE_DISPOSE_HANDLE( MovieFileDataRef ); if ( status != noErr ) { #if defined ( OSX ) SetSystemUIMode( oldMode, oldOptions ); #endif Assert( false ); return SetResult( VideoResult::FILE_ERROR_OCCURED ); } // Get info about the video GetMovieNaturalBoundsRect(theQTMovie, &theQTMovieRect); TimeValue theQTMovieDuration = GetMovieDuration( theQTMovie ); // what size do we set the output rect to? // Integral scaling is much faster, so always scale the video as such int nNewWidth = (int) theQTMovieRect.right; int nNewHeight = (int) theQTMovieRect.bottom; // Determine the window we are rendering video into int displayWidth = windowWidth; int displayHeight = windowHeight; // on mac OSX, if we are fullscreen, quicktime is bypassing our targets and going to the display directly, so use its dimensions if ( IsOSX() && windowed == false ) { displayWidth = desktopWidth; displayHeight = desktopHeight; } // get the size of the target video output int nBufferWidth = nNewWidth; int nBufferHeight = nNewHeight; int displayXOffset = 0; int displayYOffset = 0; if ( !m_pCommonServices->CalculateVideoDimensions( nNewWidth, nNewHeight, displayWidth, displayHeight, playbackFlags, &nBufferWidth, &nBufferHeight, &displayXOffset, &displayYOffset ) ) { #if defined ( OSX ) SetSystemUIMode( oldMode, oldOptions ); #endif return SetResult( VideoResult::VIDEO_ERROR_OCCURED ); } theQTMovieRect.left = (short) displayXOffset; theQTMovieRect.right = (short) ( displayXOffset + nBufferWidth ); theQTMovieRect.top = (short) displayYOffset; theQTMovieRect.bottom = (short) ( displayYOffset + nBufferHeight ); SetMovieBox( theQTMovie, &theQTMovieRect ); // Check to see if we should include audio playback bool enableMovieAudio = !BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::NO_AUDIO ); if ( !CreateMovieAudioContext( enableMovieAudio, theQTMovie, &theAudioContext, true) ) { #if defined ( OSX ) SetSystemUIMode( oldMode, oldOptions ); #endif return SetResult( VideoResult::AUDIO_ERROR_OCCURED ); } // need to get the graphics port associated with the main window #if defined( WIN32 ) CreatePortAssociation( mainWindow, NULL, 0 ); GrafPtr theGrafPtr = GetNativeWindowPort( mainWindow ); #elif defined( OSX ) GrafPtr theGrafPtr = GetWindowPort( (OpaqueWindowPtr*)mainWindow ); #endif // Setup the playback gamma according to the convar SetGWorldDecodeGamma( (CGrafPtr) theGrafPtr, VideoPlaybackGamma::USE_GAMMA_CONVAR ); // Assign the GWorld to this movie SetMovieGWorld( theQTMovie, (CGrafPtr) theGrafPtr, NULL ); // Setup the keyboard and message handler for fullscreen playback if ( SetResult( m_pCommonServices->InitFullScreenPlaybackInputHandler( playbackFlags, forcedMinTime, windowed ) ) != VideoResult::SUCCESS ) { #if defined ( OSX ) SetSystemUIMode( oldMode, oldOptions ); #endif return GetLastResult(); } // Other Movie playback state init bool bPaused = false; // Init Movie info TimeRecord movieStartTime; TimeRecord moviePauseTime; GoToBeginningOfMovie( theQTMovie ); GetMovieTime( theQTMovie, &movieStartTime ); // Start movie playback StartMovie( theQTMovie ); // loop while movie is playing while ( true ) { bool bAbortEvent, bPauseEvent, bQuitEvent; if ( m_pCommonServices->ProcessFullScreenInput( bAbortEvent, bPauseEvent, bQuitEvent ) ) { // check for aborting the movie if ( bAbortEvent || bQuitEvent ) { goto abort_playback; } // check for pausing the movie? if ( bPauseEvent ) { if ( bPaused == false ) // pausing the movie? { GetMovieTime( theQTMovie, &moviePauseTime ); StopMovie( theQTMovie ); bPaused = true; } else // unpause the movie { SetMovieTime( theQTMovie, &moviePauseTime ); StartMovie( theQTMovie ); bPaused = false; } } } // hit the end of the movie? TimeValue now = GetMovieTime( theQTMovie, nullptr ); if ( now >= theQTMovieDuration ) { // Loop this movie until aborted? if ( playbackFlags & BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::LOOP_VIDEO ) ) { Assert( ANY_BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::ABORT_ON_ESC | VideoPlaybackFlags::ABORT_ON_RETURN | VideoPlaybackFlags::ABORT_ON_SPACE | VideoPlaybackFlags::ABORT_ON_ANY_KEY ) ); // Movie will loop forever otherwise StopMovie( theQTMovie ); SetMovieTime( theQTMovie, &movieStartTime ); StartMovie( theQTMovie ); } else { break; // finished actually, exit loop } } // if the movie is paused, sleep for 5ms to keeps the CPU from spinning so hard if ( bPaused ) { ThreadSleep( 1 ); } else { // Keep the movie running.... MoviesTask( theQTMovie, 0L ); } } // Close it all down abort_playback: StopMovie( theQTMovie ); SAFE_RELEASE_AUDIOCONTEXT( theAudioContext ); SAFE_DISPOSE_MOVIE( theQTMovie ); m_pCommonServices->TerminateFullScreenPlaybackInputHandler(); #if defined ( OSX ) SetSystemUIMode( oldMode, oldOptions ); #endif return SetResult( VideoResult::SUCCESS ); } // =========================================================================== // IVideoSubSystem Video Material Services // note that the filename is absolute and has already resolved any paths // =========================================================================== IVideoMaterial* CQuickTimeVideoSubSystem::CreateVideoMaterial( const char *pMaterialName, const char *pVideoFileName, VideoPlaybackFlags_t flags ) { SetResult( VideoResult::BAD_INPUT_PARAMETERS ); AssertExitN( m_CurrentStatus == VideoSystemStatus::OK && IS_NOT_EMPTY( pMaterialName ) || IS_NOT_EMPTY( pVideoFileName ) ); CQuickTimeMaterial *pVideoMaterial = new CQuickTimeMaterial(); if ( pVideoMaterial == nullptr || pVideoMaterial->Init( pMaterialName, pVideoFileName, flags ) == false ) { SAFE_DELETE( pVideoMaterial ); SetResult( VideoResult::VIDEO_ERROR_OCCURED ); return nullptr; } IVideoMaterial *pInterface = (IVideoMaterial*) pVideoMaterial; m_MaterialList.AddToTail( pInterface ); SetResult( VideoResult::SUCCESS ); return pInterface; } VideoResult_t CQuickTimeVideoSubSystem::DestroyVideoMaterial( IVideoMaterial *pVideoMaterial ) { AssertExitV( m_CurrentStatus == VideoSystemStatus::OK, SetResult( VideoResult::SYSTEM_NOT_AVAILABLE ) ); AssertPtrExitV( pVideoMaterial, SetResult( VideoResult::BAD_INPUT_PARAMETERS ) ); if ( m_MaterialList.Find( pVideoMaterial ) != -1 ) { CQuickTimeMaterial *pObject = (CQuickTimeMaterial*) pVideoMaterial; pObject->Shutdown(); delete pObject; m_MaterialList.FindAndFastRemove( pVideoMaterial ); return SetResult( VideoResult::SUCCESS ); } return SetResult (VideoResult::MATERIAL_NOT_FOUND ); } // =========================================================================== // IVideoSubSystem Video Recorder Services // =========================================================================== IVideoRecorder* CQuickTimeVideoSubSystem::CreateVideoRecorder() { SetResult( VideoResult::SYSTEM_NOT_AVAILABLE ); AssertExitN( m_CurrentStatus == VideoSystemStatus::OK ); CQuickTimeVideoRecorder *pVideoRecorder = new CQuickTimeVideoRecorder(); if ( pVideoRecorder != nullptr ) { IVideoRecorder *pInterface = (IVideoRecorder*) pVideoRecorder; m_RecorderList.AddToTail( pInterface ); SetResult( VideoResult::SUCCESS ); return pInterface; } SetResult( VideoResult::VIDEO_ERROR_OCCURED ); return nullptr; } VideoResult_t CQuickTimeVideoSubSystem::DestroyVideoRecorder( IVideoRecorder *pRecorder ) { AssertExitV( m_CurrentStatus == VideoSystemStatus::OK, SetResult( VideoResult::SYSTEM_NOT_AVAILABLE ) ); AssertPtrExitV( pRecorder, SetResult( VideoResult::BAD_INPUT_PARAMETERS ) ); if ( m_RecorderList.Find( pRecorder ) != -1 ) { CQuickTimeVideoRecorder *pVideoRecorder = (CQuickTimeVideoRecorder*) pRecorder; delete pVideoRecorder; m_RecorderList.FindAndFastRemove( pRecorder ); return SetResult( VideoResult::SUCCESS ); } return SetResult( VideoResult::RECORDER_NOT_FOUND ); } VideoResult_t CQuickTimeVideoSubSystem::CheckCodecAvailability( VideoEncodeCodec_t codec ) { AssertExitV( m_CurrentStatus == VideoSystemStatus::OK, SetResult( VideoResult::SYSTEM_NOT_AVAILABLE ) ); AssertExitV( codec >= VideoEncodeCodec::DEFAULT_CODEC && codec < VideoEncodeCodec::CODEC_COUNT, SetResult( VideoResult::BAD_INPUT_PARAMETERS ) ); // map the requested codec in CodecType theCodec; switch( codec ) { case VideoEncodeCodec::MPEG2_CODEC: { theCodec = kMpegYUV420CodecType; break; } case VideoEncodeCodec::MPEG4_CODEC: { theCodec = kMPEG4VisualCodecType; break; } case VideoEncodeCodec::H261_CODEC: { theCodec = kH261CodecType; break; } case VideoEncodeCodec::H263_CODEC: { theCodec = kH263CodecType; break; } case VideoEncodeCodec::H264_CODEC: { theCodec = kH264CodecType; break; } case VideoEncodeCodec::MJPEG_A_CODEC: { theCodec = kMotionJPEGACodecType; break; } case VideoEncodeCodec::MJPEG_B_CODEC: { theCodec = kMotionJPEGBCodecType; break; } case VideoEncodeCodec::SORENSON3_CODEC: { theCodec = kSorenson3CodecType; break; } case VideoEncodeCodec::CINEPACK_CODEC: { theCodec = kCinepakCodecType; break; } default: // should never hit this because we are already range checked { theCodec = CQTVideoFileComposer::DEFAULT_CODEC; break; } } // Determine if codec is available... CodecInfo theInfo; OSErr status = GetCodecInfo( &theInfo, theCodec, 0 ); if ( status == noCodecErr ) { return SetResult( VideoResult::CODEC_NOT_AVAILABLE );; } AssertExitV( status == noErr, SetResult( VideoResult::CODEC_NOT_AVAILABLE ) ); return SetResult( VideoResult::SUCCESS ); } // =========================================================================== // Status support // =========================================================================== VideoResult_t CQuickTimeVideoSubSystem::GetLastResult() { return m_LastResult; } VideoResult_t CQuickTimeVideoSubSystem::SetResult( VideoResult_t status ) { m_LastResult = status; return status; } // =========================================================================== // Quicktime Initialization & Shutdown // =========================================================================== bool CQuickTimeVideoSubSystem::SetupQuickTime() { SetResult( VideoResult::INITIALIZATION_ERROR_OCCURED); AssertExitF( m_bQuickTimeInitialized == false ); // This is set early to indicate we have already been through here, even if we error out for some reason m_bQuickTimeInitialized = true; m_CurrentStatus = VideoSystemStatus::NOT_INITIALIZED; m_AvailableFeatures = VideoSystemFeature::NO_FEATURES; if ( CommandLine()->FindParm( "-noquicktime" ) ) { // Don't even try. leave status as NOT_INITIALIZED return true; } // Windows PC build #if defined ( WIN32 ) OSErr status = InitializeQTML( 0 ); // if -2903 (qtmlDllLoadErr) then quicktime not installed on this system if ( status != noErr ) { if ( status == qtmlDllLoadErr ) { m_CurrentStatus = VideoSystemStatus::NOT_INITIALIZED; return true; } Msg( "Unknown QuickTime Initialization Error = %d \n", (int) status ); Assert( false ); m_CurrentStatus = VideoSystemStatus::NOT_INITIALIZED; return true; } // Make sure we have version 7.04 or greater of quicktime long version = 0; status = Gestalt( gestaltQuickTime, &version ); if ( ( status != noErr ) || ( version < 0x07608000 ) ) { TerminateQTML(); m_CurrentStatus = VideoSystemStatus::NOT_CURRENT_VERSION; Msg( "QuickTime Version reports to be ver= %8.8x, less than 7.6 (07608000) required\n", version ); Assert( false ); return true; } #endif // Windows PC, or Mac OSX build OSErr status2 = EnterMovies(); // Initialize QuickTime Movie Toolbox if ( status2 != noErr ) { // Windows PC -- shutdown Quicktime #if defined ( WIN32 ) TerminateQTML(); #endif Msg( "Quicktime Error when attempting to EnterMovies, err = %d \n", (int) status2 ); Assert( false ); m_CurrentStatus = VideoSystemStatus::INITIALIZATION_ERROR; return true; } m_CurrentStatus = VideoSystemStatus::OK; m_AvailableFeatures = DEFAULT_FEATURE_SET; // if the Library load didn't hook up the compression functions, remove them from our feature list #pragma warning ( disable : 4551 ) if ( !CompressImage ) { m_AvailableFeatures = m_AvailableFeatures & ~( VideoSystemFeature::ENCODE_VIDEO_TO_FILE | VideoSystemFeature::ENCODE_AUDIO_TO_FILE ); } #pragma warning ( default : 4551 ) // Note that we are now open for business.... m_bQuickTimeInitialized = true; SetResult( VideoResult::SUCCESS ); return true; } bool CQuickTimeVideoSubSystem::ShutdownQuickTime() { if ( m_bQuickTimeInitialized && m_CurrentStatus == VideoSystemStatus::OK ) { ExitMovies(); // Terminate QuickTime // Windows PC only shutdown #if defined ( WIN32 ) TerminateQTML(); // Terminate QTML #endif } m_bQuickTimeInitialized = false; m_CurrentStatus = VideoSystemStatus::NOT_INITIALIZED; m_AvailableFeatures = VideoSystemFeature::NO_FEATURES; SetResult( VideoResult::SUCCESS ); return true; } // =========================================================================== // Functions defined in Quicktime Common // =========================================================================== // makes a copy of a string char *COPY_STRING( const char *pString ) { if ( pString == nullptr ) { return nullptr; } size_t strLen = V_strlen( pString ); char *pNewStr = new char[ strLen+ 1 ]; if ( strLen > 0 ) { V_memcpy( pNewStr, pString, strLen ); } pNewStr[strLen] = nullchar; return pNewStr; } //----------------------------------------------------------------------------- // Adapted from QuickTime Frame Rate code from Apple OSX Reference Library // found at http://developer.apple.com/library/mac/#qa/qa2001/qa1262.html //----------------------------------------------------------------------------- #define kCharacteristicHasVideoFrameRate FOUR_CHAR_CODE('vfrr') #define kCharacteristicIsAnMpegTrack FOUR_CHAR_CODE('mpeg') bool MediaGetStaticFrameRate( Media inMovieMedia, VideoFrameRate_t &theFrameRate, bool AssumeConstantIntervals ); //----------------------------------------------------------------------------- bool MovieGetStaticFrameRate( Movie inMovie, VideoFrameRate_t &theFrameRate ) { theFrameRate.Clear(); AssertExitF( inMovie != nullptr ); Boolean isMPEG = false; Media movieMedia = nullptr; MediaHandler movieMediaHandler = nullptr; bool success = false; // get the media identifier for the media that contains the first // video track's sample data, and also get the media handler for this media. Track videoTrack = GetMovieIndTrackType( inMovie, 1, kCharacteristicHasVideoFrameRate, movieTrackCharacteristic | movieTrackEnabledOnly ); // get first video track if ( videoTrack == nullptr || GetMoviesError() != noErr ) goto error_out; // get media ref. for track's sample data movieMedia = GetTrackMedia( videoTrack ); if ( movieMedia == nullptr || GetMoviesError() != noErr ) goto error_out; // get a reference to the media handler component movieMediaHandler = GetMediaHandler( movieMedia ); if ( movieMediaHandler == nullptr || GetMoviesError() != noErr ) goto error_out; // is this the MPEG-1/MPEG-2 media handler? if ( MediaHasCharacteristic( movieMediaHandler, kCharacteristicIsAnMpegTrack, &isMPEG ) != noErr ) goto error_out; if (isMPEG) // working with MPEG-1/MPEG-2 media { MHInfoEncodedFrameRateRecord encodedFrameRate; Size encodedFrameRateSize = sizeof( encodedFrameRate ); // get the static frame rate if ( MediaGetPublicInfo( movieMediaHandler, kMHInfoEncodedFrameRate, &encodedFrameRate, &encodedFrameRateSize ) != noErr ) goto error_out; TimeScale MovieTimeScale = GetMovieTimeScale( inMovie ); Assert( MovieTimeScale > 0 && encodedFrameRate.encodedFrameRate > 0 ); theFrameRate.SetRaw( MovieTimeScale, int ( (double) MovieTimeScale / Fix2X( encodedFrameRate.encodedFrameRate ) + 0.5 ) ); } else // working with non-MPEG-1/MPEG-2 media { if ( !MediaGetStaticFrameRate( movieMedia, theFrameRate, true ) ) goto error_out; } success = true; error_out: return success; } // ============================================================================ // Given a reference to the media that contains the sample data for a track, // calculate the static frame rate. // ============================================================================ bool MediaGetStaticFrameRate( Media inMovieMedia, VideoFrameRate_t &theFrameRate, bool AssumeConstantIntervals ) { Assert( inMovieMedia != nullptr ); theFrameRate.Clear(); // Method #1 - from Apple if ( !AssumeConstantIntervals ) { // get the number of samples in the media long sampleCount = GetMediaSampleCount( inMovieMedia ); if ( GetMoviesError() != noErr || sampleCount == 0 ) return false; // find the media duration TimeValue64 duration = GetMediaDisplayDuration( inMovieMedia ); if ( GetMoviesError() != noErr || duration == 0 ) return false; // get the media time scale TimeValue64 timeScale = GetMediaTimeScale( inMovieMedia ); if ( GetMoviesError() != noErr || timeScale == 0 ) return false; // calculate the frame rate: = (sample count * media time scale) / media duration float FPS = (double) sampleCount * (double) timeScale / (double) duration; theFrameRate.SetFPS( FPS ); return true; } // FPS rate method #2 - assumes all frames are at a constant interval // gets FPS in terms of units per second (preferred) TimeValue64 sample_time = 0; TimeValue64 sample_duration = -1; GetMediaNextInterestingDisplayTime( inMovieMedia, nextTimeMediaSample | nextTimeEdgeOK, (TimeValue64) 0 , fixed1, &sample_time, &sample_duration ); if ( sample_time == -1 || sample_duration == 0 || GetMoviesError() != noErr ) return false; TimeValue64 sample_time2 = 0; TimeValue64 sample_duration2 = -1; GetMediaNextInterestingDisplayTime( inMovieMedia, nextTimeMediaSample | nextTimeEdgeOK, sample_time + sample_duration , fixed1, &sample_time2, &sample_duration2 ); if ( sample_time2 == -1 || sample_duration2 == 0 || GetMoviesError() != noErr ) return false; TimeScale MediaTimeScale = GetMediaTimeScale( inMovieMedia ); if ( MediaTimeScale <= 0 || GetMoviesError() != noErr ) return false; Assert( sample_time2 == sample_time + sample_duration ); Assert( sample_duration == sample_duration2 ); theFrameRate.SetRaw( MediaTimeScale, (int) sample_duration ); return true; } // ============================================================================ // SetGWorldDecodeGamma - configure a GWorld to perform any needed gamma // correction // // kQTUsePlatformDefaultGammaLevel = 0, /* When decompressing into this PixMap, gamma-correct to the platform's standard gamma. */ // kQTUseSourceGammaLevel = -1L, /* When decompressing into this PixMap, don't perform gamma-correction. */ // kQTCCIR601VideoGammaLevel = 0x00023333 /* 2.2, standard television video gamma.*/ // Fixed cGamma1_8 = 0x0001CCCC; // Gamma 1.8 // Fixed cGamma2_5 = 0x00028000; // Gamma 2.5 // ============================================================================ bool SetGWorldDecodeGamma( CGrafPtr theGWorld, VideoPlaybackGamma_t gamma ) { AssertExitF( theGWorld != nullptr ); AssertIncRange( gamma, VideoPlaybackGamma::USE_GAMMA_CONVAR, VideoPlaybackGamma::GAMMA_2_5 ); Fixed decodeGamma = kQTUseSourceGammaLevel; if ( gamma == VideoPlaybackGamma::USE_GAMMA_CONVAR ) { int useGamma = QuickTime_PlaybackGamma.GetInt(); if ( useGamma < (int) VideoPlaybackGamma::NO_GAMMA_ADJUST || useGamma >= VideoPlaybackGamma::GAMMA_COUNT ) return false; gamma = (VideoPlaybackGamma_t) useGamma; } switch( gamma ) { case VideoPlaybackGamma::NO_GAMMA_ADJUST: { decodeGamma = kQTUseSourceGammaLevel; break; } case VideoPlaybackGamma::PLATFORM_DEFAULT_GAMMMA: { decodeGamma = kQTUsePlatformDefaultGammaLevel; break; } case VideoPlaybackGamma::GAMMA_1_8: { decodeGamma = 0x0001CCCC; // Gamma 1.8 break; } case VideoPlaybackGamma::GAMMA_2_2: { decodeGamma = 0x00023333; // Gamma 2.2 break; } case VideoPlaybackGamma::GAMMA_2_5: { decodeGamma = 0x00028000; // Gamma 2.5 break; } default: { Assert( false ); break; } } // Get the pix map for the GWorld and adjust the gamma correction on it PixMapHandle thePixMap = GetGWorldPixMap( theGWorld ); AssertExitF( thePixMap != nullptr ); // Set the Gamma level for the pixmap OSErr Status = QTSetPixMapHandleGammaLevel( thePixMap, decodeGamma ); AssertExitF( Status == noErr ); // Set the Requested Gamma level for the pixmap Status = QTSetPixMapHandleRequestedGammaLevel( thePixMap, decodeGamma ); AssertExitF( Status == noErr ); return true; } // ============================================================================ // Setup a quicktime Audio Context for a movie // ============================================================================ bool CreateMovieAudioContext( bool enableAudio, Movie inMovie, QTAudioContextRef *pAudioContext, bool setVolume, float *pCurrentVolume ) { AssertExitF( inMovie != nullptr && pAudioContext != nullptr ); if ( enableAudio ) { #if defined ( WIN32 ) WCHAR strGUID[39]; int numBytes = StringFromGUID2( DSDEVID_DefaultPlayback, (LPOLESTR) strGUID, 39); // CLSID_DirectSound is not what you want here // create the audio context CFStringRef deviceNameStrRef = CFStringCreateWithCharacters(kCFAllocatorDefault, (const UniChar*) strGUID, (CFIndex) (numBytes -1) ); OSStatus result = QTAudioContextCreateForAudioDevice( NULL, deviceNameStrRef, NULL, pAudioContext ); AssertExitF( result == noErr ); SAFE_RELEASE_CFREF( deviceNameStrRef ); #elif defined ( OSX ) OSStatus result = QTAudioContextCreateForAudioDevice( NULL, NULL, NULL, pAudioContext ); AssertExitF( result == noErr ); #endif // valid? AssertPtr( *pAudioContext ); } else // no audio { *pAudioContext = nullptr; } // Set the audio context OSStatus result = SetMovieAudioContext( inMovie, *pAudioContext ); AssertExitF( result == noErr ); if ( setVolume && *pAudioContext != nullptr ) { ConVarRef volumeConVar( "volume" ); float sysVolume = ( volumeConVar.IsValid() ) ? volumeConVar.GetFloat() : 1.0f; clamp( sysVolume, 0.0f, 1.0f ); if ( pCurrentVolume != nullptr ) { *pCurrentVolume = sysVolume; } short movieVolume = (short) ( sysVolume * 256.0f ); SetMovieVolume( inMovie, movieVolume ); } return true; }