Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1096 lines
32 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "quicktime_video.h"
  7. #include "quicktime_common.h"
  8. #include "quicktime_material.h"
  9. #include "quicktime_recorder.h"
  10. #include "filesystem.h"
  11. #include "tier0/icommandline.h"
  12. #include "tier1/strtools.h"
  13. #include "tier1/utllinkedlist.h"
  14. #include "tier1/KeyValues.h"
  15. #include "materialsystem/imaterial.h"
  16. #include "materialsystem/imaterialsystem.h"
  17. #include "materialsystem/MaterialSystemUtil.h"
  18. #include "materialsystem/itexture.h"
  19. #include "vtf/vtf.h"
  20. #include "pixelwriter.h"
  21. #include "tier2/tier2.h"
  22. #include "platform.h"
  23. #if defined ( WIN32 )
  24. #include <WinDef.h>
  25. #include <../dx9sdk/include/dsound.h>
  26. #endif
  27. #include "tier0/memdbgon.h"
  28. // ===========================================================================
  29. // Singleton to expose Quicktime video subsystem
  30. // ===========================================================================
  31. static CQuickTimeVideoSubSystem g_QuickTimeSystem;
  32. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CQuickTimeVideoSubSystem, IVideoSubSystem, VIDEO_SUBSYSTEM_INTERFACE_VERSION, g_QuickTimeSystem );
  33. // ===========================================================================
  34. // Convars used by Quicktime
  35. // - these need to be referenced somewhere to keep the compiler from
  36. // optimizing them away
  37. // ===========================================================================
  38. 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 );
  39. 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 );
  40. // ===========================================================================
  41. // List of file extensions and features supported by this subsystem
  42. // ===========================================================================
  43. VideoFileExtensionInfo_t s_QuickTimeExtensions[] =
  44. {
  45. { ".mov", VideoSystem::QUICKTIME, VideoSystemFeature::FULL_PLAYBACK | VideoSystemFeature::FULL_ENCODE },
  46. { ".mp4", VideoSystem::QUICKTIME, VideoSystemFeature::FULL_PLAYBACK | VideoSystemFeature::FULL_ENCODE },
  47. };
  48. const int s_QuickTimeExtensionCount = ARRAYSIZE( s_QuickTimeExtensions );
  49. const VideoSystemFeature_t CQuickTimeVideoSubSystem::DEFAULT_FEATURE_SET = VideoSystemFeature::FULL_PLAYBACK | VideoSystemFeature::FULL_ENCODE;
  50. #ifdef OSX
  51. PFNGetGWorldPixMap GetGWorldPixMap = NULL;
  52. PFNGetPixBaseAddr GetPixBaseAddr = NULL;
  53. PFNLockPixels LockPixels = NULL;
  54. PFNUnlockPixels UnlockPixels = NULL;
  55. PFNDisposeGWorld DisposeGWorld = NULL;
  56. PFNSetGWorld SetGWorld = NULL;
  57. PFNGetPixRowBytes GetPixRowBytes = NULL;
  58. #endif
  59. // ===========================================================================
  60. // CQuickTimeVideoSubSystem class
  61. // ===========================================================================
  62. CQuickTimeVideoSubSystem::CQuickTimeVideoSubSystem() :
  63. m_bQuickTimeInitialized( false ),
  64. m_LastResult( VideoResult::SUCCESS ),
  65. m_CurrentStatus( VideoSystemStatus::NOT_INITIALIZED ),
  66. m_AvailableFeatures( CQuickTimeVideoSubSystem::DEFAULT_FEATURE_SET ),
  67. m_pCommonServices( nullptr )
  68. {
  69. }
  70. CQuickTimeVideoSubSystem::~CQuickTimeVideoSubSystem()
  71. {
  72. ShutdownQuickTime(); // Super redundant safety check
  73. }
  74. // ===========================================================================
  75. // IAppSystem methods
  76. // ===========================================================================
  77. bool CQuickTimeVideoSubSystem::Connect( CreateInterfaceFn factory )
  78. {
  79. if ( !BaseClass::Connect( factory ) )
  80. {
  81. return false;
  82. }
  83. if ( g_pFullFileSystem == nullptr || materials == nullptr )
  84. {
  85. Msg( "QuickTime video subsystem failed to connect to missing a required system\n" );
  86. return false;
  87. }
  88. return true;
  89. }
  90. void CQuickTimeVideoSubSystem::Disconnect()
  91. {
  92. BaseClass::Disconnect();
  93. }
  94. void* CQuickTimeVideoSubSystem::QueryInterface( const char *pInterfaceName )
  95. {
  96. if ( IS_NOT_EMPTY( pInterfaceName ) )
  97. {
  98. if ( V_strncmp( pInterfaceName, VIDEO_SUBSYSTEM_INTERFACE_VERSION, Q_strlen( VIDEO_SUBSYSTEM_INTERFACE_VERSION ) + 1) == STRINGS_MATCH )
  99. {
  100. return (IVideoSubSystem*) this;
  101. }
  102. }
  103. return nullptr;
  104. }
  105. InitReturnVal_t CQuickTimeVideoSubSystem::Init()
  106. {
  107. InitReturnVal_t nRetVal = BaseClass::Init();
  108. if ( nRetVal != INIT_OK )
  109. {
  110. return nRetVal;
  111. }
  112. return INIT_OK;
  113. }
  114. void CQuickTimeVideoSubSystem::Shutdown()
  115. {
  116. // Make sure we shut down quicktime
  117. ShutdownQuickTime();
  118. BaseClass::Shutdown();
  119. }
  120. // ===========================================================================
  121. // IVideoSubSystem identification methods
  122. // ===========================================================================
  123. VideoSystem_t CQuickTimeVideoSubSystem::GetSystemID()
  124. {
  125. return VideoSystem::QUICKTIME;
  126. }
  127. VideoSystemStatus_t CQuickTimeVideoSubSystem::GetSystemStatus()
  128. {
  129. return m_CurrentStatus;
  130. }
  131. VideoSystemFeature_t CQuickTimeVideoSubSystem::GetSupportedFeatures()
  132. {
  133. return m_AvailableFeatures;
  134. }
  135. const char* CQuickTimeVideoSubSystem::GetVideoSystemName()
  136. {
  137. return "Quicktime";
  138. }
  139. // ===========================================================================
  140. // IVideoSubSystem setup and shutdown services
  141. // ===========================================================================
  142. bool CQuickTimeVideoSubSystem::InitializeVideoSystem( IVideoCommonServices *pCommonServices )
  143. {
  144. m_AvailableFeatures = DEFAULT_FEATURE_SET; // Put here because of issue with static const int, binary OR and DEBUG builds
  145. AssertPtr( pCommonServices );
  146. m_pCommonServices = pCommonServices;
  147. #ifdef OSX
  148. if ( !GetGWorldPixMap )
  149. GetGWorldPixMap = (PFNGetGWorldPixMap)dlsym( RTLD_DEFAULT, "GetGWorldPixMap" );
  150. if ( !GetPixBaseAddr )
  151. GetPixBaseAddr = (PFNGetPixBaseAddr)dlsym( RTLD_DEFAULT, "GetPixBaseAddr" );
  152. if ( !LockPixels )
  153. LockPixels = (PFNLockPixels)dlsym( RTLD_DEFAULT, "LockPixels" );
  154. if ( !UnlockPixels )
  155. UnlockPixels = (PFNUnlockPixels)dlsym( RTLD_DEFAULT, "UnlockPixels" );
  156. if ( !DisposeGWorld )
  157. DisposeGWorld = (PFNDisposeGWorld)dlsym( RTLD_DEFAULT, "DisposeGWorld" );
  158. if ( !SetGWorld )
  159. SetGWorld = (PFNSetGWorld)dlsym( RTLD_DEFAULT, "SetGWorld" );
  160. if ( !GetPixRowBytes )
  161. GetPixRowBytes = (PFNGetPixRowBytes)dlsym( RTLD_DEFAULT, "GetPixRowBytes" );
  162. if ( !GetGWorldPixMap || !GetPixBaseAddr || !LockPixels || !UnlockPixels || !DisposeGWorld || !SetGWorld || !GetPixRowBytes )
  163. return false;
  164. #endif
  165. return ( m_bQuickTimeInitialized ) ? true : SetupQuickTime();
  166. }
  167. bool CQuickTimeVideoSubSystem::ShutdownVideoSystem()
  168. {
  169. return ( m_bQuickTimeInitialized ) ? ShutdownQuickTime() : true;
  170. }
  171. VideoResult_t CQuickTimeVideoSubSystem::VideoSoundDeviceCMD( VideoSoundDeviceOperation_t operation, void *pDevice, void *pData )
  172. {
  173. switch ( operation )
  174. {
  175. case VideoSoundDeviceOperation::SET_DIRECT_SOUND_DEVICE:
  176. {
  177. return SetResult( VideoResult::OPERATION_NOT_SUPPORTED );
  178. }
  179. case VideoSoundDeviceOperation::SET_MILES_SOUND_DEVICE:
  180. case VideoSoundDeviceOperation::HOOK_X_AUDIO:
  181. {
  182. return SetResult( VideoResult::OPERATION_NOT_SUPPORTED );
  183. }
  184. default:
  185. {
  186. return SetResult( VideoResult::UNKNOWN_OPERATION );
  187. }
  188. }
  189. }
  190. // ===========================================================================
  191. // IVideoSubSystem supported extensions & features
  192. // ===========================================================================
  193. int CQuickTimeVideoSubSystem::GetSupportedFileExtensionCount()
  194. {
  195. return s_QuickTimeExtensionCount;
  196. }
  197. const char* CQuickTimeVideoSubSystem::GetSupportedFileExtension( int num )
  198. {
  199. return ( num < 0 || num >= s_QuickTimeExtensionCount ) ? nullptr : s_QuickTimeExtensions[num].m_FileExtension;
  200. }
  201. VideoSystemFeature_t CQuickTimeVideoSubSystem::GetSupportedFileExtensionFeatures( int num )
  202. {
  203. return ( num < 0 || num >= s_QuickTimeExtensionCount ) ? VideoSystemFeature::NO_FEATURES : s_QuickTimeExtensions[num].m_VideoFeatures;
  204. }
  205. // ===========================================================================
  206. // IVideoSubSystem Video Playback and Recording Services
  207. // ===========================================================================
  208. VideoResult_t CQuickTimeVideoSubSystem::PlayVideoFileFullScreen( const char *filename, void *mainWindow, int windowWidth, int windowHeight, int desktopWidth, int desktopHeight, bool windowed, float forcedMinTime, VideoPlaybackFlags_t playbackFlags )
  209. {
  210. OSErr status;
  211. // See if the caller is asking for a feature we can not support....
  212. VideoPlaybackFlags_t unsupportedFeatures = VideoPlaybackFlags::PRELOAD_VIDEO;
  213. if ( playbackFlags & unsupportedFeatures )
  214. {
  215. return SetResult( VideoResult::FEATURE_NOT_AVAILABLE );
  216. }
  217. // Make sure we are initialized and ready
  218. if ( !m_bQuickTimeInitialized )
  219. {
  220. SetupQuickTime();
  221. }
  222. AssertExitV( m_CurrentStatus == VideoSystemStatus::OK, SetResult( VideoResult::SYSTEM_NOT_AVAILABLE ) );
  223. // Set graphics port
  224. #if defined ( WIN32 )
  225. SetGWorld ( (CGrafPtr) GetNativeWindowPort( nil ), nil );
  226. #elif defined ( OSX )
  227. SystemUIMode oldMode;
  228. SystemUIOptions oldOptions;
  229. GetSystemUIMode( &oldMode, &oldOptions );
  230. if ( !windowed )
  231. {
  232. status = SetSystemUIMode( kUIModeAllHidden, (SystemUIOptions) 0 );
  233. Assert( status == noErr );
  234. }
  235. SetGWorld( nil, nil );
  236. #endif
  237. // -------------------------------------------------
  238. // Open the quicktime file with audio
  239. Movie theQTMovie = NULL;
  240. Rect theQTMovieRect;
  241. QTAudioContextRef theAudioContext = NULL;
  242. Handle MovieFileDataRef = nullptr;
  243. OSType MovieFileDataRefType = 0;
  244. CFStringRef imageStrRef = CFStringCreateWithCString ( NULL, filename, 0 );
  245. AssertExitV( imageStrRef != nullptr, SetResult( VideoResult::SYSTEM_ERROR_OCCURED ) );
  246. status = QTNewDataReferenceFromFullPathCFString( imageStrRef, (QTPathStyle) kQTNativeDefaultPathStyle, 0, &MovieFileDataRef, &MovieFileDataRefType );
  247. AssertExitV( status == noErr, SetResult( VideoResult::FILE_ERROR_OCCURED ) );
  248. CFRelease( imageStrRef );
  249. status = NewMovieFromDataRef( &theQTMovie, newMovieActive, nil, MovieFileDataRef, MovieFileDataRefType );
  250. SAFE_DISPOSE_HANDLE( MovieFileDataRef );
  251. if ( status != noErr )
  252. {
  253. #if defined ( OSX )
  254. SetSystemUIMode( oldMode, oldOptions );
  255. #endif
  256. Assert( false );
  257. return SetResult( VideoResult::FILE_ERROR_OCCURED );
  258. }
  259. // Get info about the video
  260. GetMovieNaturalBoundsRect(theQTMovie, &theQTMovieRect);
  261. TimeValue theQTMovieDuration = GetMovieDuration( theQTMovie );
  262. // what size do we set the output rect to?
  263. // Integral scaling is much faster, so always scale the video as such
  264. int nNewWidth = (int) theQTMovieRect.right;
  265. int nNewHeight = (int) theQTMovieRect.bottom;
  266. // Determine the window we are rendering video into
  267. int displayWidth = windowWidth;
  268. int displayHeight = windowHeight;
  269. // on mac OSX, if we are fullscreen, quicktime is bypassing our targets and going to the display directly, so use its dimensions
  270. if ( IsOSX() && windowed == false )
  271. {
  272. displayWidth = desktopWidth;
  273. displayHeight = desktopHeight;
  274. }
  275. // get the size of the target video output
  276. int nBufferWidth = nNewWidth;
  277. int nBufferHeight = nNewHeight;
  278. int displayXOffset = 0;
  279. int displayYOffset = 0;
  280. if ( !m_pCommonServices->CalculateVideoDimensions( nNewWidth, nNewHeight, displayWidth, displayHeight, playbackFlags, &nBufferWidth, &nBufferHeight, &displayXOffset, &displayYOffset ) )
  281. {
  282. #if defined ( OSX )
  283. SetSystemUIMode( oldMode, oldOptions );
  284. #endif
  285. return SetResult( VideoResult::VIDEO_ERROR_OCCURED );
  286. }
  287. theQTMovieRect.left = (short) displayXOffset;
  288. theQTMovieRect.right = (short) ( displayXOffset + nBufferWidth );
  289. theQTMovieRect.top = (short) displayYOffset;
  290. theQTMovieRect.bottom = (short) ( displayYOffset + nBufferHeight );
  291. SetMovieBox( theQTMovie, &theQTMovieRect );
  292. // Check to see if we should include audio playback
  293. bool enableMovieAudio = !BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::NO_AUDIO );
  294. if ( !CreateMovieAudioContext( enableMovieAudio, theQTMovie, &theAudioContext, true) )
  295. {
  296. #if defined ( OSX )
  297. SetSystemUIMode( oldMode, oldOptions );
  298. #endif
  299. return SetResult( VideoResult::AUDIO_ERROR_OCCURED );
  300. }
  301. // need to get the graphics port associated with the main window
  302. #if defined( WIN32 )
  303. CreatePortAssociation( mainWindow, NULL, 0 );
  304. GrafPtr theGrafPtr = GetNativeWindowPort( mainWindow );
  305. #elif defined( OSX )
  306. GrafPtr theGrafPtr = GetWindowPort( (OpaqueWindowPtr*)mainWindow );
  307. #endif
  308. // Setup the playback gamma according to the convar
  309. SetGWorldDecodeGamma( (CGrafPtr) theGrafPtr, VideoPlaybackGamma::USE_GAMMA_CONVAR );
  310. // Assign the GWorld to this movie
  311. SetMovieGWorld( theQTMovie, (CGrafPtr) theGrafPtr, NULL );
  312. // Setup the keyboard and message handler for fullscreen playback
  313. if ( SetResult( m_pCommonServices->InitFullScreenPlaybackInputHandler( playbackFlags, forcedMinTime, windowed ) ) != VideoResult::SUCCESS )
  314. {
  315. #if defined ( OSX )
  316. SetSystemUIMode( oldMode, oldOptions );
  317. #endif
  318. return GetLastResult();
  319. }
  320. // Other Movie playback state init
  321. bool bPaused = false;
  322. // Init Movie info
  323. TimeRecord movieStartTime;
  324. TimeRecord moviePauseTime;
  325. GoToBeginningOfMovie( theQTMovie );
  326. GetMovieTime( theQTMovie, &movieStartTime );
  327. // Start movie playback
  328. StartMovie( theQTMovie );
  329. // loop while movie is playing
  330. while ( true )
  331. {
  332. bool bAbortEvent, bPauseEvent, bQuitEvent;
  333. if ( m_pCommonServices->ProcessFullScreenInput( bAbortEvent, bPauseEvent, bQuitEvent ) )
  334. {
  335. // check for aborting the movie
  336. if ( bAbortEvent || bQuitEvent )
  337. {
  338. goto abort_playback;
  339. }
  340. // check for pausing the movie?
  341. if ( bPauseEvent )
  342. {
  343. if ( bPaused == false ) // pausing the movie?
  344. {
  345. GetMovieTime( theQTMovie, &moviePauseTime );
  346. StopMovie( theQTMovie );
  347. bPaused = true;
  348. }
  349. else // unpause the movie
  350. {
  351. SetMovieTime( theQTMovie, &moviePauseTime );
  352. StartMovie( theQTMovie );
  353. bPaused = false;
  354. }
  355. }
  356. }
  357. // hit the end of the movie?
  358. TimeValue now = GetMovieTime( theQTMovie, nullptr );
  359. if ( now >= theQTMovieDuration )
  360. {
  361. // Loop this movie until aborted?
  362. if ( playbackFlags & BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::LOOP_VIDEO ) )
  363. {
  364. 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
  365. StopMovie( theQTMovie );
  366. SetMovieTime( theQTMovie, &movieStartTime );
  367. StartMovie( theQTMovie );
  368. }
  369. else
  370. {
  371. break; // finished actually, exit loop
  372. }
  373. }
  374. // if the movie is paused, sleep for 5ms to keeps the CPU from spinning so hard
  375. if ( bPaused )
  376. {
  377. ThreadSleep( 1 );
  378. }
  379. else
  380. {
  381. // Keep the movie running....
  382. MoviesTask( theQTMovie, 0L );
  383. }
  384. }
  385. // Close it all down
  386. abort_playback:
  387. StopMovie( theQTMovie );
  388. SAFE_RELEASE_AUDIOCONTEXT( theAudioContext );
  389. SAFE_DISPOSE_MOVIE( theQTMovie );
  390. m_pCommonServices->TerminateFullScreenPlaybackInputHandler();
  391. #if defined ( OSX )
  392. SetSystemUIMode( oldMode, oldOptions );
  393. #endif
  394. return SetResult( VideoResult::SUCCESS );
  395. }
  396. // ===========================================================================
  397. // IVideoSubSystem Video Material Services
  398. // note that the filename is absolute and has already resolved any paths
  399. // ===========================================================================
  400. IVideoMaterial* CQuickTimeVideoSubSystem::CreateVideoMaterial( const char *pMaterialName, const char *pVideoFileName, VideoPlaybackFlags_t flags )
  401. {
  402. SetResult( VideoResult::BAD_INPUT_PARAMETERS );
  403. AssertExitN( m_CurrentStatus == VideoSystemStatus::OK && IS_NOT_EMPTY( pMaterialName ) || IS_NOT_EMPTY( pVideoFileName ) );
  404. CQuickTimeMaterial *pVideoMaterial = new CQuickTimeMaterial();
  405. if ( pVideoMaterial == nullptr || pVideoMaterial->Init( pMaterialName, pVideoFileName, flags ) == false )
  406. {
  407. SAFE_DELETE( pVideoMaterial );
  408. SetResult( VideoResult::VIDEO_ERROR_OCCURED );
  409. return nullptr;
  410. }
  411. IVideoMaterial *pInterface = (IVideoMaterial*) pVideoMaterial;
  412. m_MaterialList.AddToTail( pInterface );
  413. SetResult( VideoResult::SUCCESS );
  414. return pInterface;
  415. }
  416. VideoResult_t CQuickTimeVideoSubSystem::DestroyVideoMaterial( IVideoMaterial *pVideoMaterial )
  417. {
  418. AssertExitV( m_CurrentStatus == VideoSystemStatus::OK, SetResult( VideoResult::SYSTEM_NOT_AVAILABLE ) );
  419. AssertPtrExitV( pVideoMaterial, SetResult( VideoResult::BAD_INPUT_PARAMETERS ) );
  420. if ( m_MaterialList.Find( pVideoMaterial ) != -1 )
  421. {
  422. CQuickTimeMaterial *pObject = (CQuickTimeMaterial*) pVideoMaterial;
  423. pObject->Shutdown();
  424. delete pObject;
  425. m_MaterialList.FindAndFastRemove( pVideoMaterial );
  426. return SetResult( VideoResult::SUCCESS );
  427. }
  428. return SetResult (VideoResult::MATERIAL_NOT_FOUND );
  429. }
  430. // ===========================================================================
  431. // IVideoSubSystem Video Recorder Services
  432. // ===========================================================================
  433. IVideoRecorder* CQuickTimeVideoSubSystem::CreateVideoRecorder()
  434. {
  435. SetResult( VideoResult::SYSTEM_NOT_AVAILABLE );
  436. AssertExitN( m_CurrentStatus == VideoSystemStatus::OK );
  437. CQuickTimeVideoRecorder *pVideoRecorder = new CQuickTimeVideoRecorder();
  438. if ( pVideoRecorder != nullptr )
  439. {
  440. IVideoRecorder *pInterface = (IVideoRecorder*) pVideoRecorder;
  441. m_RecorderList.AddToTail( pInterface );
  442. SetResult( VideoResult::SUCCESS );
  443. return pInterface;
  444. }
  445. SetResult( VideoResult::VIDEO_ERROR_OCCURED );
  446. return nullptr;
  447. }
  448. VideoResult_t CQuickTimeVideoSubSystem::DestroyVideoRecorder( IVideoRecorder *pRecorder )
  449. {
  450. AssertExitV( m_CurrentStatus == VideoSystemStatus::OK, SetResult( VideoResult::SYSTEM_NOT_AVAILABLE ) );
  451. AssertPtrExitV( pRecorder, SetResult( VideoResult::BAD_INPUT_PARAMETERS ) );
  452. if ( m_RecorderList.Find( pRecorder ) != -1 )
  453. {
  454. CQuickTimeVideoRecorder *pVideoRecorder = (CQuickTimeVideoRecorder*) pRecorder;
  455. delete pVideoRecorder;
  456. m_RecorderList.FindAndFastRemove( pRecorder );
  457. return SetResult( VideoResult::SUCCESS );
  458. }
  459. return SetResult( VideoResult::RECORDER_NOT_FOUND );
  460. }
  461. VideoResult_t CQuickTimeVideoSubSystem::CheckCodecAvailability( VideoEncodeCodec_t codec )
  462. {
  463. AssertExitV( m_CurrentStatus == VideoSystemStatus::OK, SetResult( VideoResult::SYSTEM_NOT_AVAILABLE ) );
  464. AssertExitV( codec >= VideoEncodeCodec::DEFAULT_CODEC && codec < VideoEncodeCodec::CODEC_COUNT, SetResult( VideoResult::BAD_INPUT_PARAMETERS ) );
  465. // map the requested codec in
  466. CodecType theCodec;
  467. switch( codec )
  468. {
  469. case VideoEncodeCodec::MPEG2_CODEC:
  470. {
  471. theCodec = kMpegYUV420CodecType;
  472. break;
  473. }
  474. case VideoEncodeCodec::MPEG4_CODEC:
  475. {
  476. theCodec = kMPEG4VisualCodecType;
  477. break;
  478. }
  479. case VideoEncodeCodec::H261_CODEC:
  480. {
  481. theCodec = kH261CodecType;
  482. break;
  483. }
  484. case VideoEncodeCodec::H263_CODEC:
  485. {
  486. theCodec = kH263CodecType;
  487. break;
  488. }
  489. case VideoEncodeCodec::H264_CODEC:
  490. {
  491. theCodec = kH264CodecType;
  492. break;
  493. }
  494. case VideoEncodeCodec::MJPEG_A_CODEC:
  495. {
  496. theCodec = kMotionJPEGACodecType;
  497. break;
  498. }
  499. case VideoEncodeCodec::MJPEG_B_CODEC:
  500. {
  501. theCodec = kMotionJPEGBCodecType;
  502. break;
  503. }
  504. case VideoEncodeCodec::SORENSON3_CODEC:
  505. {
  506. theCodec = kSorenson3CodecType;
  507. break;
  508. }
  509. case VideoEncodeCodec::CINEPACK_CODEC:
  510. {
  511. theCodec = kCinepakCodecType;
  512. break;
  513. }
  514. default: // should never hit this because we are already range checked
  515. {
  516. theCodec = CQTVideoFileComposer::DEFAULT_CODEC;
  517. break;
  518. }
  519. }
  520. // Determine if codec is available...
  521. CodecInfo theInfo;
  522. OSErr status = GetCodecInfo( &theInfo, theCodec, 0 );
  523. if ( status == noCodecErr )
  524. {
  525. return SetResult( VideoResult::CODEC_NOT_AVAILABLE );;
  526. }
  527. AssertExitV( status == noErr, SetResult( VideoResult::CODEC_NOT_AVAILABLE ) );
  528. return SetResult( VideoResult::SUCCESS );
  529. }
  530. // ===========================================================================
  531. // Status support
  532. // ===========================================================================
  533. VideoResult_t CQuickTimeVideoSubSystem::GetLastResult()
  534. {
  535. return m_LastResult;
  536. }
  537. VideoResult_t CQuickTimeVideoSubSystem::SetResult( VideoResult_t status )
  538. {
  539. m_LastResult = status;
  540. return status;
  541. }
  542. // ===========================================================================
  543. // Quicktime Initialization & Shutdown
  544. // ===========================================================================
  545. bool CQuickTimeVideoSubSystem::SetupQuickTime()
  546. {
  547. SetResult( VideoResult::INITIALIZATION_ERROR_OCCURED);
  548. AssertExitF( m_bQuickTimeInitialized == false );
  549. // This is set early to indicate we have already been through here, even if we error out for some reason
  550. m_bQuickTimeInitialized = true;
  551. m_CurrentStatus = VideoSystemStatus::NOT_INITIALIZED;
  552. m_AvailableFeatures = VideoSystemFeature::NO_FEATURES;
  553. if ( CommandLine()->FindParm( "-noquicktime" ) )
  554. {
  555. // Don't even try. leave status as NOT_INITIALIZED
  556. return true;
  557. }
  558. // Windows PC build
  559. #if defined ( WIN32 )
  560. OSErr status = InitializeQTML( 0 );
  561. // if -2903 (qtmlDllLoadErr) then quicktime not installed on this system
  562. if ( status != noErr )
  563. {
  564. if ( status == qtmlDllLoadErr )
  565. {
  566. m_CurrentStatus = VideoSystemStatus::NOT_INITIALIZED;
  567. return true;
  568. }
  569. Msg( "Unknown QuickTime Initialization Error = %d \n", (int) status );
  570. Assert( false );
  571. m_CurrentStatus = VideoSystemStatus::NOT_INITIALIZED;
  572. return true;
  573. }
  574. // Make sure we have version 7.04 or greater of quicktime
  575. long version = 0;
  576. status = Gestalt( gestaltQuickTime, &version );
  577. if ( ( status != noErr ) || ( version < 0x07608000 ) )
  578. {
  579. TerminateQTML();
  580. m_CurrentStatus = VideoSystemStatus::NOT_CURRENT_VERSION;
  581. Msg( "QuickTime Version reports to be ver= %8.8x, less than 7.6 (07608000) required\n", version );
  582. Assert( false );
  583. return true;
  584. }
  585. #endif
  586. // Windows PC, or Mac OSX build
  587. OSErr status2 = EnterMovies(); // Initialize QuickTime Movie Toolbox
  588. if ( status2 != noErr )
  589. {
  590. // Windows PC -- shutdown Quicktime
  591. #if defined ( WIN32 )
  592. TerminateQTML();
  593. #endif
  594. Msg( "Quicktime Error when attempting to EnterMovies, err = %d \n", (int) status2 );
  595. Assert( false );
  596. m_CurrentStatus = VideoSystemStatus::INITIALIZATION_ERROR;
  597. return true;
  598. }
  599. m_CurrentStatus = VideoSystemStatus::OK;
  600. m_AvailableFeatures = DEFAULT_FEATURE_SET;
  601. // if the Library load didn't hook up the compression functions, remove them from our feature list
  602. #pragma warning ( disable : 4551 )
  603. if ( !CompressImage )
  604. {
  605. m_AvailableFeatures = m_AvailableFeatures & ~( VideoSystemFeature::ENCODE_VIDEO_TO_FILE | VideoSystemFeature::ENCODE_AUDIO_TO_FILE );
  606. }
  607. #pragma warning ( default : 4551 )
  608. // Note that we are now open for business....
  609. m_bQuickTimeInitialized = true;
  610. SetResult( VideoResult::SUCCESS );
  611. return true;
  612. }
  613. bool CQuickTimeVideoSubSystem::ShutdownQuickTime()
  614. {
  615. if ( m_bQuickTimeInitialized && m_CurrentStatus == VideoSystemStatus::OK )
  616. {
  617. ExitMovies(); // Terminate QuickTime
  618. // Windows PC only shutdown
  619. #if defined ( WIN32 )
  620. TerminateQTML(); // Terminate QTML
  621. #endif
  622. }
  623. m_bQuickTimeInitialized = false;
  624. m_CurrentStatus = VideoSystemStatus::NOT_INITIALIZED;
  625. m_AvailableFeatures = VideoSystemFeature::NO_FEATURES;
  626. SetResult( VideoResult::SUCCESS );
  627. return true;
  628. }
  629. // ===========================================================================
  630. // Functions defined in Quicktime Common
  631. // ===========================================================================
  632. // makes a copy of a string
  633. char *COPY_STRING( const char *pString )
  634. {
  635. if ( pString == nullptr )
  636. {
  637. return nullptr;
  638. }
  639. size_t strLen = V_strlen( pString );
  640. char *pNewStr = new char[ strLen+ 1 ];
  641. if ( strLen > 0 )
  642. {
  643. V_memcpy( pNewStr, pString, strLen );
  644. }
  645. pNewStr[strLen] = nullchar;
  646. return pNewStr;
  647. }
  648. //-----------------------------------------------------------------------------
  649. // Adapted from QuickTime Frame Rate code from Apple OSX Reference Library
  650. // found at http://developer.apple.com/library/mac/#qa/qa2001/qa1262.html
  651. //-----------------------------------------------------------------------------
  652. #define kCharacteristicHasVideoFrameRate FOUR_CHAR_CODE('vfrr')
  653. #define kCharacteristicIsAnMpegTrack FOUR_CHAR_CODE('mpeg')
  654. bool MediaGetStaticFrameRate( Media inMovieMedia, VideoFrameRate_t &theFrameRate, bool AssumeConstantIntervals );
  655. //-----------------------------------------------------------------------------
  656. bool MovieGetStaticFrameRate( Movie inMovie, VideoFrameRate_t &theFrameRate )
  657. {
  658. theFrameRate.Clear();
  659. AssertExitF( inMovie != nullptr );
  660. Boolean isMPEG = false;
  661. Media movieMedia = nullptr;
  662. MediaHandler movieMediaHandler = nullptr;
  663. bool success = false;
  664. // get the media identifier for the media that contains the first
  665. // video track's sample data, and also get the media handler for this media.
  666. Track videoTrack = GetMovieIndTrackType( inMovie, 1, kCharacteristicHasVideoFrameRate, movieTrackCharacteristic | movieTrackEnabledOnly ); // get first video track
  667. if ( videoTrack == nullptr || GetMoviesError() != noErr )
  668. goto error_out;
  669. // get media ref. for track's sample data
  670. movieMedia = GetTrackMedia( videoTrack );
  671. if ( movieMedia == nullptr || GetMoviesError() != noErr )
  672. goto error_out;
  673. // get a reference to the media handler component
  674. movieMediaHandler = GetMediaHandler( movieMedia );
  675. if ( movieMediaHandler == nullptr || GetMoviesError() != noErr )
  676. goto error_out;
  677. // is this the MPEG-1/MPEG-2 media handler?
  678. if ( MediaHasCharacteristic( movieMediaHandler, kCharacteristicIsAnMpegTrack, &isMPEG ) != noErr )
  679. goto error_out;
  680. if (isMPEG) // working with MPEG-1/MPEG-2 media
  681. {
  682. MHInfoEncodedFrameRateRecord encodedFrameRate;
  683. Size encodedFrameRateSize = sizeof( encodedFrameRate );
  684. // get the static frame rate
  685. if ( MediaGetPublicInfo( movieMediaHandler, kMHInfoEncodedFrameRate, &encodedFrameRate, &encodedFrameRateSize ) != noErr )
  686. goto error_out;
  687. TimeScale MovieTimeScale = GetMovieTimeScale( inMovie );
  688. Assert( MovieTimeScale > 0 && encodedFrameRate.encodedFrameRate > 0 );
  689. theFrameRate.SetRaw( MovieTimeScale, int ( (double) MovieTimeScale / Fix2X( encodedFrameRate.encodedFrameRate ) + 0.5 ) );
  690. }
  691. else // working with non-MPEG-1/MPEG-2 media
  692. {
  693. if ( !MediaGetStaticFrameRate( movieMedia, theFrameRate, true ) )
  694. goto error_out;
  695. }
  696. success = true;
  697. error_out:
  698. return success;
  699. }
  700. // ============================================================================
  701. // Given a reference to the media that contains the sample data for a track,
  702. // calculate the static frame rate.
  703. // ============================================================================
  704. bool MediaGetStaticFrameRate( Media inMovieMedia, VideoFrameRate_t &theFrameRate, bool AssumeConstantIntervals )
  705. {
  706. Assert( inMovieMedia != nullptr );
  707. theFrameRate.Clear();
  708. // Method #1 - from Apple
  709. if ( !AssumeConstantIntervals )
  710. {
  711. // get the number of samples in the media
  712. long sampleCount = GetMediaSampleCount( inMovieMedia );
  713. if ( GetMoviesError() != noErr || sampleCount == 0 )
  714. return false;
  715. // find the media duration
  716. TimeValue64 duration = GetMediaDisplayDuration( inMovieMedia );
  717. if ( GetMoviesError() != noErr || duration == 0 )
  718. return false;
  719. // get the media time scale
  720. TimeValue64 timeScale = GetMediaTimeScale( inMovieMedia );
  721. if ( GetMoviesError() != noErr || timeScale == 0 )
  722. return false;
  723. // calculate the frame rate: = (sample count * media time scale) / media duration
  724. float FPS = (double) sampleCount * (double) timeScale / (double) duration;
  725. theFrameRate.SetFPS( FPS );
  726. return true;
  727. }
  728. // FPS rate method #2 - assumes all frames are at a constant interval
  729. // gets FPS in terms of units per second (preferred)
  730. TimeValue64 sample_time = 0;
  731. TimeValue64 sample_duration = -1;
  732. GetMediaNextInterestingDisplayTime( inMovieMedia, nextTimeMediaSample | nextTimeEdgeOK, (TimeValue64) 0 , fixed1, &sample_time, &sample_duration );
  733. if ( sample_time == -1 || sample_duration == 0 || GetMoviesError() != noErr )
  734. return false;
  735. TimeValue64 sample_time2 = 0;
  736. TimeValue64 sample_duration2 = -1;
  737. GetMediaNextInterestingDisplayTime( inMovieMedia, nextTimeMediaSample | nextTimeEdgeOK, sample_time + sample_duration , fixed1, &sample_time2, &sample_duration2 );
  738. if ( sample_time2 == -1 || sample_duration2 == 0 || GetMoviesError() != noErr )
  739. return false;
  740. TimeScale MediaTimeScale = GetMediaTimeScale( inMovieMedia );
  741. if ( MediaTimeScale <= 0 || GetMoviesError() != noErr )
  742. return false;
  743. Assert( sample_time2 == sample_time + sample_duration );
  744. Assert( sample_duration == sample_duration2 );
  745. theFrameRate.SetRaw( MediaTimeScale, (int) sample_duration );
  746. return true;
  747. }
  748. // ============================================================================
  749. // SetGWorldDecodeGamma - configure a GWorld to perform any needed gamma
  750. // correction
  751. //
  752. // kQTUsePlatformDefaultGammaLevel = 0, /* When decompressing into this PixMap, gamma-correct to the platform's standard gamma. */
  753. // kQTUseSourceGammaLevel = -1L, /* When decompressing into this PixMap, don't perform gamma-correction. */
  754. // kQTCCIR601VideoGammaLevel = 0x00023333 /* 2.2, standard television video gamma.*/
  755. // Fixed cGamma1_8 = 0x0001CCCC; // Gamma 1.8
  756. // Fixed cGamma2_5 = 0x00028000; // Gamma 2.5
  757. // ============================================================================
  758. bool SetGWorldDecodeGamma( CGrafPtr theGWorld, VideoPlaybackGamma_t gamma )
  759. {
  760. AssertExitF( theGWorld != nullptr );
  761. AssertIncRange( gamma, VideoPlaybackGamma::USE_GAMMA_CONVAR, VideoPlaybackGamma::GAMMA_2_5 );
  762. Fixed decodeGamma = kQTUseSourceGammaLevel;
  763. if ( gamma == VideoPlaybackGamma::USE_GAMMA_CONVAR )
  764. {
  765. int useGamma = QuickTime_PlaybackGamma.GetInt();
  766. if ( useGamma < (int) VideoPlaybackGamma::NO_GAMMA_ADJUST || useGamma >= VideoPlaybackGamma::GAMMA_COUNT )
  767. return false;
  768. gamma = (VideoPlaybackGamma_t) useGamma;
  769. }
  770. switch( gamma )
  771. {
  772. case VideoPlaybackGamma::NO_GAMMA_ADJUST:
  773. {
  774. decodeGamma = kQTUseSourceGammaLevel;
  775. break;
  776. }
  777. case VideoPlaybackGamma::PLATFORM_DEFAULT_GAMMMA:
  778. {
  779. decodeGamma = kQTUsePlatformDefaultGammaLevel;
  780. break;
  781. }
  782. case VideoPlaybackGamma::GAMMA_1_8:
  783. {
  784. decodeGamma = 0x0001CCCC; // Gamma 1.8
  785. break;
  786. }
  787. case VideoPlaybackGamma::GAMMA_2_2:
  788. {
  789. decodeGamma = 0x00023333; // Gamma 2.2
  790. break;
  791. }
  792. case VideoPlaybackGamma::GAMMA_2_5:
  793. {
  794. decodeGamma = 0x00028000; // Gamma 2.5
  795. break;
  796. }
  797. default:
  798. {
  799. Assert( false );
  800. break;
  801. }
  802. }
  803. // Get the pix map for the GWorld and adjust the gamma correction on it
  804. PixMapHandle thePixMap = GetGWorldPixMap( theGWorld );
  805. AssertExitF( thePixMap != nullptr );
  806. // Set the Gamma level for the pixmap
  807. OSErr Status = QTSetPixMapHandleGammaLevel( thePixMap, decodeGamma );
  808. AssertExitF( Status == noErr );
  809. // Set the Requested Gamma level for the pixmap
  810. Status = QTSetPixMapHandleRequestedGammaLevel( thePixMap, decodeGamma );
  811. AssertExitF( Status == noErr );
  812. return true;
  813. }
  814. // ============================================================================
  815. // Setup a quicktime Audio Context for a movie
  816. // ============================================================================
  817. bool CreateMovieAudioContext( bool enableAudio, Movie inMovie, QTAudioContextRef *pAudioContext, bool setVolume, float *pCurrentVolume )
  818. {
  819. AssertExitF( inMovie != nullptr && pAudioContext != nullptr );
  820. if ( enableAudio )
  821. {
  822. #if defined ( WIN32 )
  823. WCHAR strGUID[39];
  824. int numBytes = StringFromGUID2( DSDEVID_DefaultPlayback, (LPOLESTR) strGUID, 39); // CLSID_DirectSound is not what you want here
  825. // create the audio context
  826. CFStringRef deviceNameStrRef = CFStringCreateWithCharacters(kCFAllocatorDefault, (const UniChar*) strGUID, (CFIndex) (numBytes -1) );
  827. OSStatus result = QTAudioContextCreateForAudioDevice( NULL, deviceNameStrRef, NULL, pAudioContext );
  828. AssertExitF( result == noErr );
  829. SAFE_RELEASE_CFREF( deviceNameStrRef );
  830. #elif defined ( OSX )
  831. OSStatus result = QTAudioContextCreateForAudioDevice( NULL, NULL, NULL, pAudioContext );
  832. AssertExitF( result == noErr );
  833. #endif
  834. // valid?
  835. AssertPtr( *pAudioContext );
  836. }
  837. else // no audio
  838. {
  839. *pAudioContext = nullptr;
  840. }
  841. // Set the audio context
  842. OSStatus result = SetMovieAudioContext( inMovie, *pAudioContext );
  843. AssertExitF( result == noErr );
  844. if ( setVolume && *pAudioContext != nullptr )
  845. {
  846. ConVarRef volumeConVar( "volume" );
  847. float sysVolume = ( volumeConVar.IsValid() ) ? volumeConVar.GetFloat() : 1.0f;
  848. clamp( sysVolume, 0.0f, 1.0f );
  849. if ( pCurrentVolume != nullptr )
  850. {
  851. *pCurrentVolume = sysVolume;
  852. }
  853. short movieVolume = (short) ( sysVolume * 256.0f );
  854. SetMovieVolume( inMovie, movieVolume );
  855. }
  856. return true;
  857. }