summaryrefslogtreecommitdiff
path: root/video
diff options
context:
space:
mode:
Diffstat (limited to 'video')
-rw-r--r--video/quicktime_common.h100
-rw-r--r--video/quicktime_material.cpp962
-rw-r--r--video/quicktime_material.h221
-rw-r--r--video/quicktime_recorder.cpp2146
-rw-r--r--video/quicktime_recorder.h263
-rw-r--r--video/quicktime_video.cpp1096
-rw-r--r--video/quicktime_video.h128
-rw-r--r--video/video_macros.h114
-rw-r--r--video/video_quicktime.vpc84
-rw-r--r--video/video_services.vpc72
-rw-r--r--video/video_webm.vpc86
-rw-r--r--video/videoservices.cpp1464
-rw-r--r--video/videoservices.h198
-rw-r--r--video/videosubsystem.h97
-rw-r--r--video/webm_common.h48
-rw-r--r--video/webm_material.cpp962
-rw-r--r--video/webm_material.h221
-rw-r--r--video/webm_recorder.cpp811
-rw-r--r--video/webm_recorder.h111
-rw-r--r--video/webm_video.cpp353
-rw-r--r--video/webm_video.h133
21 files changed, 9670 insertions, 0 deletions
diff --git a/video/quicktime_common.h b/video/quicktime_common.h
new file mode 100644
index 0000000..d6fefff
--- /dev/null
+++ b/video/quicktime_common.h
@@ -0,0 +1,100 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// File: quicktime_common.h
+//
+// QuickTime limits and constants shared among all QuickTime functions
+//
+//=============================================================================
+
+
+#ifndef QUICKTIME_COMMON_H
+#define QUICKTIME_COMMON_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#ifdef OSX
+// The OSX 10.7 SDK dropped support for the functions below, so we manually pull them in
+#include <dlfcn.h>
+typedef PixMapHandle
+(*PFNGetGWorldPixMap)(GWorldPtr offscreenGWorld);
+typedef Ptr
+(*PFNGetPixBaseAddr)(PixMapHandle pm);
+typedef Boolean
+(*PFNLockPixels)(PixMapHandle pm);
+typedef void
+(*PFNUnlockPixels)(PixMapHandle pm);
+typedef void
+(*PFNDisposeGWorld)(GWorldPtr offscreenGWorld);
+typedef void
+(*PFNSetGWorld)(CGrafPtr port,GDHandle gdh);
+typedef SInt32
+(*PFNGetPixRowBytes)(PixMapHandle pm);
+
+
+extern PFNGetGWorldPixMap GetGWorldPixMap;
+extern PFNGetPixBaseAddr GetPixBaseAddr;
+extern PFNLockPixels LockPixels;
+extern PFNUnlockPixels UnlockPixels;
+extern PFNDisposeGWorld DisposeGWorld;
+extern PFNSetGWorld SetGWorld;
+extern PFNGetPixRowBytes GetPixRowBytes;
+#endif
+
+
+// constant that define the bounds of various inputs
+static const int cMinVideoFrameWidth = 16;
+static const int cMinVideoFrameHeight = 16;
+static const int cMaxVideoFrameWidth = 2 * 2048;
+static const int cMaxVideoFrameHeight = 2 * 2048;
+
+static const int cMinFPS = 1;
+static const int cMaxFPS = 600;
+
+static const float cMinDuration = 0.016666666f; // 1/60th second
+static const float cMaxDuration = 3600.0f; // 1 Hour
+
+static const int cMinSampleRate = 11025; // 1/4 CD sample rate
+static const int cMaxSampleRate = 88200; // 2x CD rate
+
+#define NO_MORE_INTERESTING_TIMES -2
+#define END_OF_QUICKTIME_MOVIE -1
+
+// ===========================================================================
+// Macros & Utility functions
+// ===========================================================================
+
+#define SAFE_DISPOSE_HANDLE( _handle ) if ( _handle != nullptr ) { DisposeHandle( (Handle) _handle ); _handle = nullptr; }
+#define SAFE_DISPOSE_GWORLD( _gworld ) if ( _gworld != nullptr ) { DisposeGWorld( _gworld ); _gworld = nullptr; }
+#define SAFE_DISPOSE_MOVIE( _movie ) if ( _movie != nullptr ) { DisposeMovie( _movie ); ThreadSleep(10); Assert( GetMoviesError() == noErr ); _movie = nullptr; }
+#define SAFE_RELEASE_AUDIOCONTEXT( _cxt ) if ( _cxt != nullptr ) { QTAudioContextRelease( _cxt ); _cxt = nullptr; }
+#define SAFE_RELEASE_CFREF( _ref ) if ( _ref != nullptr ) { CFRelease( _ref ); _ref = nullptr; }
+// Utility functions
+
+extern char *COPY_STRING( const char *pString );
+
+bool MovieGetStaticFrameRate( Movie inMovie, VideoFrameRate_t &theFrameRate );
+
+bool SetGWorldDecodeGamma( CGrafPtr theGWorld, VideoPlaybackGamma_t gamma );
+
+bool CreateMovieAudioContext( bool enableAudio, Movie inMovie, QTAudioContextRef *pAudioConectext, bool setVolume = false, float *pCurrentVolume = nullptr );
+
+VideoPlaybackGamma_t GetSystemPlaybackGamma();
+
+
+//-----------------------------------------------------------------------------
+// Computes a power of two at least as big as the passed-in number
+//-----------------------------------------------------------------------------
+static inline int ComputeGreaterPowerOfTwo( int n )
+{
+ int i = 1;
+ while ( i < n )
+ {
+ i <<= 1;
+ }
+ return i;
+}
+
+
+#endif // QUICKTIME_COMMON_H
diff --git a/video/quicktime_material.cpp b/video/quicktime_material.cpp
new file mode 100644
index 0000000..75d39bf
--- /dev/null
+++ b/video/quicktime_material.cpp
@@ -0,0 +1,962 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+
+#include "filesystem.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 "tier3/tier3.h"
+#include "platform.h"
+
+
+#include "quicktime_material.h"
+
+#if defined ( WIN32 )
+ #include <WinDef.h>
+ #include <../dx9sdk/include/dsound.h>
+#endif
+
+#include "tier0/memdbgon.h"
+
+
+// ===========================================================================
+// CQuicktimeMaterialRGBTextureRegenerator - Inherited from ITextureRegenerator
+// Copies and converts the buffer bits to texture bits
+// Currently only supports 32-bit BGRA
+// ===========================================================================
+CQuicktimeMaterialRGBTextureRegenerator::CQuicktimeMaterialRGBTextureRegenerator() :
+ m_SrcGWorld( nullptr ),
+ m_nSourceWidth( 0 ),
+ m_nSourceHeight( 0 )
+{
+}
+
+
+CQuicktimeMaterialRGBTextureRegenerator::~CQuicktimeMaterialRGBTextureRegenerator()
+{
+ // nothing to do
+}
+
+
+void CQuicktimeMaterialRGBTextureRegenerator::SetSourceGWorld( GWorldPtr theGWorld, int nWidth, int nHeight )
+{
+ m_SrcGWorld = theGWorld;
+ m_nSourceWidth = nWidth;
+ m_nSourceHeight = nHeight;
+}
+
+
+void CQuicktimeMaterialRGBTextureRegenerator::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect )
+{
+ AssertExit( pVTFTexture != nullptr );
+
+ // Error condition, should only have 1 frame, 1 face, 1 mip level
+ if ( ( pVTFTexture->FrameCount() > 1 ) || ( pVTFTexture->FaceCount() > 1 ) || ( pVTFTexture->MipCount() > 1 ) || ( pVTFTexture->Depth() > 1 ) )
+ {
+ WarningAssert( "Texture Properties Incorrect ");
+ memset( pVTFTexture->ImageData(), 0xAA, pVTFTexture->ComputeTotalSize() );
+ return;
+ }
+
+ // Make sure we have a valid video image source
+ if ( m_SrcGWorld == nullptr )
+ {
+ WarningAssert( "Video texture source not set" );
+ memset( pVTFTexture->ImageData(), 0xCC, pVTFTexture->ComputeTotalSize() );
+ return;
+ }
+
+ // Verify the destination texture is set up correctly
+ Assert( pVTFTexture->Format() == IMAGE_FORMAT_BGRA8888 );
+ Assert( pVTFTexture->RowSizeInBytes( 0 ) >= pVTFTexture->Width() * 4 );
+ Assert( pVTFTexture->Width() >= m_nSourceWidth );
+ Assert( pVTFTexture->Height() >= m_nSourceHeight );
+
+ // Copy directly from the Quicktime GWorld
+ PixMapHandle thePixMap = GetGWorldPixMap( m_SrcGWorld );
+
+ if ( LockPixels( thePixMap ) )
+ {
+ BYTE *pImageData = pVTFTexture->ImageData();
+ int dstStride = pVTFTexture->RowSizeInBytes( 0 );
+ BYTE *pSrcData = (BYTE*) GetPixBaseAddr( thePixMap );
+ long srcStride = QTGetPixMapHandleRowBytes( thePixMap );
+ int rowSize = m_nSourceWidth * 4;
+
+ for (int y = 0; y < m_nSourceHeight; y++ )
+ {
+ memcpy( pImageData, pSrcData, rowSize );
+ pImageData+= dstStride;
+ pSrcData+= srcStride;
+ }
+
+ UnlockPixels( thePixMap );
+ }
+ else
+ {
+ WarningAssert( "LockPixels Failed" );
+ }
+}
+
+
+void CQuicktimeMaterialRGBTextureRegenerator::Release()
+{
+ // we don't invoke the destructor here, we're not using the no-release extensions
+}
+
+
+
+// ===========================================================================
+// CQuickTimeMaterial class - creates a material, opens a QuickTime movie
+// and plays the movie onto the material
+// ===========================================================================
+
+//-----------------------------------------------------------------------------
+// CQuickTimeMaterial Constructor
+//-----------------------------------------------------------------------------
+CQuickTimeMaterial::CQuickTimeMaterial() :
+ m_pFileName( nullptr ),
+ m_MovieGWorld( nullptr ),
+ m_QTMovie( nullptr ),
+ m_AudioContext( nullptr ),
+ m_bInitCalled( false )
+{
+ Reset();
+}
+
+
+//-----------------------------------------------------------------------------
+// CQuickTimeMaterial Destructor
+//-----------------------------------------------------------------------------
+CQuickTimeMaterial::~CQuickTimeMaterial()
+{
+ Reset();
+}
+
+
+void CQuickTimeMaterial::Reset()
+{
+ SetQTFileName( nullptr );
+
+ DestroyProceduralTexture();
+ DestroyProceduralMaterial();
+
+ m_TexCordU = 0.0f;
+ m_TexCordV = 0.0f;
+
+ m_VideoFrameWidth = 0;
+ m_VideoFrameHeight = 0;
+
+ m_PlaybackFlags = VideoPlaybackFlags::NO_PLAYBACK_OPTIONS;
+
+ m_bMovieInitialized = false;
+ m_bMoviePlaying = false;
+ m_bMovieFinishedPlaying = false;
+ m_bMoviePaused = false;
+ m_bLoopMovie = false;
+
+ m_bHasAudio = false;
+ m_bMuted = false;
+
+ m_CurrentVolume = 0.0f;
+
+ m_QTMovieTimeScale = 0;
+ m_QTMovieDuration = 0;
+ m_QTMovieDurationinSec = 0.0f;
+ m_QTMovieFrameRate.SetFPS( 0, false );
+
+ SAFE_RELEASE_AUDIOCONTEXT( m_AudioContext );
+ SAFE_DISPOSE_GWORLD( m_MovieGWorld );
+ SAFE_DISPOSE_MOVIE( m_QTMovie );
+
+ m_LastResult = VideoResult::SUCCESS;
+}
+
+
+void CQuickTimeMaterial::SetQTFileName( const char *theQTMovieFileName )
+{
+ SAFE_DELETE_ARRAY( m_pFileName );
+
+ if ( theQTMovieFileName != nullptr )
+ {
+ AssertMsg( V_strlen( theQTMovieFileName ) <= MAX_QT_FILENAME_LEN, "Bad Quicktime Movie Filename" );
+ m_pFileName = COPY_STRING( theQTMovieFileName );
+ }
+
+}
+
+
+VideoResult_t CQuickTimeMaterial::SetResult( VideoResult_t status )
+{
+ m_LastResult = status;
+ return status;
+}
+
+
+//-----------------------------------------------------------------------------
+// Video information functions
+//-----------------------------------------------------------------------------
+
+
+//-----------------------------------------------------------------------------
+// Returns the resolved filename of the video, as it might differ from
+// what the user supplied, (also with absolute path)
+//-----------------------------------------------------------------------------
+const char *CQuickTimeMaterial::GetVideoFileName()
+{
+ return m_pFileName;
+}
+
+
+VideoFrameRate_t &CQuickTimeMaterial::GetVideoFrameRate()
+{
+ return m_QTMovieFrameRate;
+}
+
+
+VideoResult_t CQuickTimeMaterial::GetLastResult()
+{
+ return m_LastResult;
+}
+
+
+//-----------------------------------------------------------------------------
+// Audio Functions
+//-----------------------------------------------------------------------------
+bool CQuickTimeMaterial::HasAudio()
+{
+ return m_bHasAudio;
+}
+
+
+bool CQuickTimeMaterial::SetVolume( float fVolume )
+{
+ clamp( fVolume, 0.0f, 1.0f );
+
+ m_CurrentVolume = fVolume;
+
+ if ( m_AudioContext != nullptr && m_bHasAudio )
+ {
+ short movieVolume = (short) ( m_CurrentVolume * 256.0f );
+
+ SetMovieVolume( m_QTMovie, movieVolume );
+
+ SetResult( VideoResult::SUCCESS );
+ return true;
+ }
+
+ SetResult( VideoResult::AUDIO_ERROR_OCCURED );
+ return false;
+}
+
+
+float CQuickTimeMaterial::GetVolume()
+{
+ return m_CurrentVolume;
+}
+
+
+void CQuickTimeMaterial::SetMuted( bool bMuteState )
+{
+ AssertExitFunc( m_bMoviePlaying, SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE) );
+
+ SetResult( VideoResult::SUCCESS );
+
+ if ( bMuteState == m_bMuted ) // no change?
+ {
+ return;
+ }
+
+ m_bMuted = bMuteState;
+
+ if ( m_bHasAudio )
+ {
+ OSStatus result = SetMovieAudioMute( m_QTMovie, m_bMuted, 0 );
+ AssertExitFunc( result == noErr, SetResult( VideoResult::AUDIO_ERROR_OCCURED) );
+ }
+
+ SetResult( VideoResult::SUCCESS );
+}
+
+
+bool CQuickTimeMaterial::IsMuted()
+{
+ return m_bMuted;
+}
+
+
+VideoResult_t CQuickTimeMaterial::SoundDeviceCommand( VideoSoundDeviceOperation_t operation, void *pDevice, void *pData )
+{
+ AssertExitV( m_bMovieInitialized || m_bMoviePlaying, VideoResult::OPERATION_OUT_OF_SEQUENCE );
+
+ switch( operation )
+ {
+ // On win32, we try and create an audio context from a GUID
+ case VideoSoundDeviceOperation::SET_DIRECT_SOUND_DEVICE:
+ {
+#if defined ( WIN32 )
+ SAFE_RELEASE_AUDIOCONTEXT( m_AudioContext );
+ return ( CreateMovieAudioContext( m_bHasAudio, m_QTMovie, &m_AudioContext ) ? SetResult( VideoResult::SUCCESS ) : SetResult( VideoResult::AUDIO_ERROR_OCCURED ) );
+#else
+ // On any other OS, we don't support this operation
+ return SetResult( VideoResult::OPERATION_NOT_SUPPORTED );
+#endif
+ }
+ case VideoSoundDeviceOperation::SET_SOUND_MANAGER_DEVICE:
+ {
+#if defined ( OSX )
+ SAFE_RELEASE_AUDIOCONTEXT( m_AudioContext );
+ return ( CreateMovieAudioContext( m_bHasAudio, m_QTMovie, &m_AudioContext ) ? SetResult( VideoResult::SUCCESS ) : SetResult( VideoResult::AUDIO_ERROR_OCCURED ) );
+#else
+ // On any other OS, we don't support this operation
+ return SetResult( VideoResult::OPERATION_NOT_SUPPORTED );
+#endif
+ }
+
+ case VideoSoundDeviceOperation::SET_LIB_AUDIO_DEVICE:
+ case VideoSoundDeviceOperation::HOOK_X_AUDIO:
+ case VideoSoundDeviceOperation::SET_MILES_SOUND_DEVICE:
+ {
+ return SetResult( VideoResult::OPERATION_NOT_SUPPORTED );
+ }
+ default:
+ {
+ return SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ }
+ }
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Initializes the video material
+//-----------------------------------------------------------------------------
+bool CQuickTimeMaterial::Init( const char *pMaterialName, const char *pFileName, VideoPlaybackFlags_t flags )
+{
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( IS_NOT_EMPTY( pFileName ) );
+ AssertExitF( m_bInitCalled == false );
+
+ m_PlaybackFlags = flags;
+
+ OpenQTMovie( pFileName ); // Open up the Quicktime file
+
+ if ( !m_bMovieInitialized )
+ {
+ return false; // Something bad happened when we went to open
+ }
+
+ // Now we can properly setup our regenerators
+ m_TextureRegen.SetSourceGWorld( m_MovieGWorld, m_VideoFrameWidth, m_VideoFrameHeight );
+
+ CreateProceduralTexture( pMaterialName );
+ CreateProceduralMaterial( pMaterialName );
+
+ // Start movie playback
+ if ( !BITFLAGS_SET( m_PlaybackFlags, VideoPlaybackFlags::DONT_AUTO_START_VIDEO ) )
+ {
+ StartVideo();
+ }
+
+ m_bInitCalled = true; // Look, if you only got one shot...
+
+ return true;
+}
+
+
+void CQuickTimeMaterial::Shutdown( void )
+{
+ StopVideo();
+ Reset();
+}
+
+
+//-----------------------------------------------------------------------------
+// Video playback state functions
+//-----------------------------------------------------------------------------
+bool CQuickTimeMaterial::IsVideoReadyToPlay()
+{
+ return m_bMovieInitialized;
+}
+
+
+bool CQuickTimeMaterial::IsVideoPlaying()
+{
+ return m_bMoviePlaying;
+}
+
+
+//-----------------------------------------------------------------------------
+// Checks to see if the video has a new frame ready to be rendered and
+// downloaded into the texture and eventually display
+//-----------------------------------------------------------------------------
+bool CQuickTimeMaterial::IsNewFrameReady( void )
+{
+ // Are we waiting to start playing the first frame? if so, tell them we are ready!
+ if ( m_bMovieInitialized == true )
+ {
+ return true;
+ }
+
+ // We better be playing the movie
+ AssertExitF( m_bMoviePlaying );
+
+ // paused?
+ if ( m_bMoviePaused )
+ {
+ return false;
+ }
+
+ TimeValue curMovieTime = GetMovieTime( m_QTMovie, nullptr );
+
+ if ( curMovieTime >= m_QTMovieDuration || m_NextInterestingTimeToPlay == NO_MORE_INTERESTING_TIMES )
+ {
+ // if we are looping, we have another frame, otherwise no
+ return m_bLoopMovie;
+ }
+
+ // Enough time passed to get to next frame??
+ if ( curMovieTime < m_NextInterestingTimeToPlay )
+ {
+ // nope.. use the previous frame
+ return false;
+ }
+
+ // we have a new frame we want then..
+ return true;
+}
+
+
+bool CQuickTimeMaterial::IsFinishedPlaying()
+{
+ return m_bMovieFinishedPlaying;
+}
+
+
+void CQuickTimeMaterial::SetLooping( bool bLoopVideo )
+{
+ m_bLoopMovie = bLoopVideo;
+}
+
+
+bool CQuickTimeMaterial::IsLooping()
+{
+ return m_bLoopMovie;
+}
+
+
+void CQuickTimeMaterial::SetPaused( bool bPauseState )
+{
+ if ( !m_bMoviePlaying || m_bMoviePaused == bPauseState )
+ {
+ Assert( m_bMoviePlaying );
+ return;
+ }
+
+ if ( bPauseState ) // Pausing the movie?
+ {
+ // Save off current time and set paused state
+ m_MoviePauseTime = GetMovieTime( m_QTMovie, nullptr );
+ StopMovie( m_QTMovie );
+ }
+ else // unpausing the movie
+ {
+ // Reset the movie to the paused time
+ SetMovieTimeValue( m_QTMovie, m_MoviePauseTime );
+ StartMovie( m_QTMovie );
+ Assert( GetMoviesError() == noErr );
+ }
+
+ m_bMoviePaused = bPauseState;
+}
+
+
+bool CQuickTimeMaterial::IsPaused()
+{
+ return ( m_bMoviePlaying ) ? m_bMoviePaused : false;
+}
+
+
+// Begins playback of the movie
+bool CQuickTimeMaterial::StartVideo()
+{
+ if ( !m_bMovieInitialized )
+ {
+ Assert( false );
+ SetResult( VideoResult::OPERATION_ALREADY_PERFORMED );
+ return false;
+ }
+
+ // Start the movie playing at the first frame
+ SetMovieTimeValue( m_QTMovie, m_MovieFirstFrameTime );
+ Assert( GetMoviesError() == noErr );
+
+ StartMovie( m_QTMovie );
+ Assert( GetMoviesError() == noErr );
+
+ // Transition to playing state
+ m_bMovieInitialized = false;
+ m_bMoviePlaying = true;
+
+ // Deliberately set the next interesting time to the current time to
+ // insure that the ::update() call causes the textures to be downloaded
+ m_NextInterestingTimeToPlay = m_MovieFirstFrameTime;
+ Update();
+
+ return true;
+}
+
+
+// stops movie for good, frees resources, but retains texture & material of last frame rendered
+bool CQuickTimeMaterial::StopVideo()
+{
+ if ( !m_bMoviePlaying )
+ {
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ return false;
+ }
+
+ StopMovie( m_QTMovie );
+
+ m_bMoviePlaying = false;
+ m_bMoviePaused = false;
+ m_bMovieFinishedPlaying = true;
+
+ // free resources
+ CloseQTFile();
+
+ SetResult( VideoResult::SUCCESS );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates our scene
+// Output : true = movie playing ok, false = time to end movie
+// supposed to be: Returns true on a new frame of video being downloaded into the texture
+//-----------------------------------------------------------------------------
+bool CQuickTimeMaterial::Update( void )
+{
+ AssertExitF( m_bMoviePlaying );
+
+ OSType qTypes[1] = { VisualMediaCharacteristic };
+
+ // are we paused? can't update if so...
+ if ( m_bMoviePaused )
+ {
+ return true; // reuse the last frame
+ }
+
+ // Get current time in the movie
+ TimeValue curMovieTime = GetMovieTime( m_QTMovie, nullptr );
+
+ // Did we hit the end of the movie?
+ if ( curMovieTime >= m_QTMovieDuration )
+ {
+ // If we're not looping, then report that we are done updating
+ if ( m_bLoopMovie == false )
+ {
+ StopVideo();
+ return false;
+ }
+
+ // Reset the movie to the start time
+ SetMovieTimeValue( m_QTMovie, m_MovieFirstFrameTime );
+ AssertExitF( GetMoviesError() == noErr );
+
+ // Assure fall through to render a new frame
+ m_NextInterestingTimeToPlay = m_MovieFirstFrameTime;
+ }
+
+ // Are we on the last frame of the movie? (but not past the end of any audio?)
+ if ( m_NextInterestingTimeToPlay == NO_MORE_INTERESTING_TIMES )
+ {
+ return true; // reuse last frame
+ }
+
+ // Enough time passed to get to next frame?
+ if ( curMovieTime < m_NextInterestingTimeToPlay )
+ {
+ // nope.. use the previous frame
+ return true;
+ }
+
+ // move the movie along
+ UpdateMovie( m_QTMovie );
+ AssertExitF( GetMoviesError() == noErr );
+
+ // Let QuickTime render the frame
+ MoviesTask( m_QTMovie, 0L );
+ AssertExitF( GetMoviesError() == noErr );
+
+ // Get the next frame after the current time (the movie may have advanced a bit during UpdateMovie() and MovieTasks()
+ GetMovieNextInterestingTime( m_QTMovie, nextTimeStep | nextTimeEdgeOK, 1, qTypes, GetMovieTime( m_QTMovie, nullptr ), fixed1, &m_NextInterestingTimeToPlay, nullptr );
+
+ // hit the end of the movie?
+ if ( GetMoviesError() == invalidTime || m_NextInterestingTimeToPlay == END_OF_QUICKTIME_MOVIE )
+ {
+ m_NextInterestingTimeToPlay = NO_MORE_INTERESTING_TIMES;
+ }
+
+ // Regenerate our texture, it'll grab from the GWorld Directly
+ m_Texture->Download();
+
+ SetResult( VideoResult::SUCCESS );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the material
+//-----------------------------------------------------------------------------
+IMaterial *CQuickTimeMaterial::GetMaterial()
+{
+ return m_Material;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the texcoord range
+//-----------------------------------------------------------------------------
+void CQuickTimeMaterial::GetVideoTexCoordRange( float *pMaxU, float *pMaxV )
+{
+ AssertExit( pMaxU != nullptr && pMaxV != nullptr );
+
+ if ( m_Texture == nullptr ) // no texture?
+ {
+ *pMaxU = *pMaxV = 1.0f;
+ return;
+ }
+
+ *pMaxU = m_TexCordU;
+ *pMaxV = m_TexCordV;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the frame size of the QuickTime Video in pixels
+//-----------------------------------------------------------------------------
+void CQuickTimeMaterial::GetVideoImageSize( int *pWidth, int *pHeight )
+{
+ Assert( pWidth != nullptr && pHeight != nullptr );
+
+ *pWidth = m_VideoFrameWidth;
+ *pHeight = m_VideoFrameHeight;
+}
+
+
+float CQuickTimeMaterial::GetVideoDuration()
+{
+ return m_QTMovieDurationinSec;
+}
+
+
+int CQuickTimeMaterial::GetFrameCount()
+{
+ return m_QTMovieFrameCount;
+}
+
+
+//-----------------------------------------------------------------------------
+// Sets the frame for an QuickTime Material (use instead of SetTime)
+//-----------------------------------------------------------------------------
+bool CQuickTimeMaterial::SetFrame( int FrameNum )
+{
+ if ( !m_bMoviePlaying )
+ {
+ Assert( false );
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ return false;
+ }
+
+ float theTime = (float) FrameNum * m_QTMovieFrameRate.GetFPS();
+ return SetTime( theTime );
+}
+
+
+int CQuickTimeMaterial::GetCurrentFrame()
+{
+ AssertExitV( m_bMoviePlaying, -1 );
+
+ TimeValue curTime = m_bMoviePaused ? m_MoviePauseTime : GetMovieTime( m_QTMovie, nullptr );
+
+ return curTime / m_QTMovieFrameRate.GetUnitsPerFrame();
+}
+
+
+float CQuickTimeMaterial::GetCurrentVideoTime()
+{
+ AssertExitV( m_bMoviePlaying, -1.0f );
+
+ TimeValue curTime = m_bMoviePaused ? m_MoviePauseTime : GetMovieTime( m_QTMovie, nullptr );
+
+ return curTime / m_QTMovieFrameRate.GetUnitsPerSecond();
+}
+
+
+bool CQuickTimeMaterial::SetTime( float flTime )
+{
+ AssertExitF( m_bMoviePlaying );
+ AssertExitF( flTime >= 0 && flTime < m_QTMovieDurationinSec );
+
+ TimeValue newTime = (TimeValue) ( flTime * m_QTMovieFrameRate.GetUnitsPerSecond() + 0.5f) ;
+
+ clamp( newTime, m_MovieFirstFrameTime, m_QTMovieDuration );
+
+ // Are we paused?
+ if ( m_bMoviePaused )
+ {
+ m_MoviePauseTime = newTime;
+ return true;
+ }
+
+ TimeValue curMovieTime = GetMovieTime( m_QTMovie, nullptr );
+
+ // Don't stop and reset movie if we are within 1 frame of the requested time
+ if ( newTime <= curMovieTime - m_QTMovieFrameRate.GetUnitsPerFrame() || newTime >= curMovieTime + m_QTMovieFrameRate.GetUnitsPerFrame() )
+ {
+ // Reset the movie to the requested time
+ StopMovie( m_QTMovie );
+ SetMovieTimeValue( m_QTMovie, newTime );
+ StartMovie( m_QTMovie );
+
+ Assert( GetMoviesError() == noErr );
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Initializes, shuts down the procedural texture
+//-----------------------------------------------------------------------------
+void CQuickTimeMaterial::CreateProceduralTexture( const char *pTextureName )
+{
+ AssertIncRange( m_VideoFrameWidth, cMinVideoFrameWidth, cMaxVideoFrameWidth );
+ AssertIncRange( m_VideoFrameHeight, cMinVideoFrameHeight, cMaxVideoFrameHeight );
+ AssertStr( pTextureName );
+
+ // Either make the texture the same dimensions as the video,
+ // or choose power-of-two textures which are at least as big as the video
+ bool actualSizeTexture = BITFLAGS_SET( m_PlaybackFlags, VideoPlaybackFlags::TEXTURES_ACTUAL_SIZE );
+
+ int nWidth = ( actualSizeTexture ) ? ALIGN_VALUE( m_VideoFrameWidth, TEXTURE_SIZE_ALIGNMENT ) : ComputeGreaterPowerOfTwo( m_VideoFrameWidth );
+ int nHeight = ( actualSizeTexture ) ? ALIGN_VALUE( m_VideoFrameHeight, TEXTURE_SIZE_ALIGNMENT ) : ComputeGreaterPowerOfTwo( m_VideoFrameHeight );
+
+ // initialize the procedural texture as 32-it RGBA, w/o mipmaps
+ m_Texture.InitProceduralTexture( pTextureName, "VideoCacheTextures", nWidth, nHeight,
+ IMAGE_FORMAT_BGRA8888, TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_NOMIP |
+ TEXTUREFLAGS_PROCEDURAL | TEXTUREFLAGS_SINGLECOPY | TEXTUREFLAGS_NOLOD );
+
+ // Use this to get the updated frame from the remote connection
+ m_Texture->SetTextureRegenerator( &m_TextureRegen /* , false */ );
+
+ // compute the texcoords
+ int nTextureWidth = m_Texture->GetActualWidth();
+ int nTextureHeight = m_Texture->GetActualHeight();
+
+ m_TexCordU = ( nTextureWidth > 0 ) ? (float) m_VideoFrameWidth / (float) nTextureWidth : 0.0f;
+ m_TexCordV = ( nTextureHeight > 0 ) ? (float) m_VideoFrameHeight / (float) nTextureHeight : 0.0f;
+}
+
+
+void CQuickTimeMaterial::DestroyProceduralTexture()
+{
+ if ( m_Texture != nullptr )
+ {
+ // DO NOT Call release on the Texture Regenerator, as it will destroy this object! bad bad bad
+ // instead we tell it to assign a NULL regenerator and flag it to not call release
+ m_Texture->SetTextureRegenerator( nullptr /*, false */ );
+ // Texture, texture go away...
+ m_Texture.Shutdown( true );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Initializes, shuts down the procedural material
+//-----------------------------------------------------------------------------
+void CQuickTimeMaterial::CreateProceduralMaterial( const char *pMaterialName )
+{
+ // create keyvalues if necessary
+ KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" );
+ {
+ pVMTKeyValues->SetString( "$basetexture", m_Texture->GetName() );
+ pVMTKeyValues->SetInt( "$nobasetexture", 1 );
+ pVMTKeyValues->SetInt( "$nofog", 1 );
+ pVMTKeyValues->SetInt( "$spriteorientation", 3 );
+ pVMTKeyValues->SetInt( "$translucent", 1 );
+ pVMTKeyValues->SetInt( "$nolod", 1 );
+ pVMTKeyValues->SetInt( "$nomip", 1 );
+ pVMTKeyValues->SetInt( "$gammacolorread", 0 );
+ }
+
+ // FIXME: gak, this is backwards. Why doesn't the material just see that it has a funky basetexture?
+ m_Material.Init( pMaterialName, pVMTKeyValues );
+ m_Material->Refresh();
+}
+
+
+void CQuickTimeMaterial::DestroyProceduralMaterial()
+{
+ // Store the internal material pointer for later use
+ IMaterial *pMaterial = m_Material;
+ m_Material.Shutdown();
+ materials->UncacheUnusedMaterials();
+
+ // Now be sure to free that material because we don't want to reference it again later, we'll recreate it!
+ if ( pMaterial != nullptr )
+ {
+ pMaterial->DeleteIfUnreferenced();
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Opens a movie file using quicktime
+//-----------------------------------------------------------------------------
+void CQuickTimeMaterial::OpenQTMovie( const char *theQTMovieFileName )
+{
+ AssertExit( IS_NOT_EMPTY( theQTMovieFileName ) );
+
+ // Set graphics port
+#if defined ( WIN32 )
+ SetGWorld ( (CGrafPtr) GetNativeWindowPort( nil ), nil );
+#elif defined ( OSX )
+ SetGWorld( nil, nil );
+#endif
+
+ SetQTFileName( theQTMovieFileName );
+
+ Handle MovieFileDataRef = nullptr;
+ OSType MovieFileDataRefType = 0;
+
+ CFStringRef imageStrRef = CFStringCreateWithCString ( NULL, theQTMovieFileName, 0 );
+ AssertExitFunc( imageStrRef != nullptr, SetResult( VideoResult::SYSTEM_ERROR_OCCURED ) );
+
+ OSErr status = QTNewDataReferenceFromFullPathCFString( imageStrRef, (QTPathStyle) kQTNativeDefaultPathStyle, 0, &MovieFileDataRef, &MovieFileDataRefType );
+ AssertExitFunc( status == noErr, SetResult( VideoResult::FILE_ERROR_OCCURED ) );
+
+ CFRelease( imageStrRef );
+
+ status = NewMovieFromDataRef( &m_QTMovie, newMovieActive, nil, MovieFileDataRef, MovieFileDataRefType );
+ SAFE_DISPOSE_HANDLE( MovieFileDataRef );
+
+ if ( status != noErr )
+ {
+ Assert( false );
+ Reset();
+ SetResult( VideoResult::VIDEO_ERROR_OCCURED );
+ return;
+ }
+
+ // disabling audio?
+ if ( BITFLAGS_SET( m_PlaybackFlags, VideoPlaybackFlags::NO_AUDIO ) )
+ {
+ m_bHasAudio = false;
+ }
+ else
+ {
+ // does movie have audio?
+ Track audioTrack = GetMovieIndTrackType( m_QTMovie, 1, SoundMediaType, movieTrackMediaType );
+ m_bHasAudio = ( audioTrack != nullptr );
+ }
+
+ // Now we need to extract the time info from the QT Movie
+ m_QTMovieTimeScale = GetMovieTimeScale( m_QTMovie );
+ m_QTMovieDuration = GetMovieDuration( m_QTMovie );
+
+ // compute movie duration
+ m_QTMovieDurationinSec = float ( double( m_QTMovieDuration ) / double( m_QTMovieTimeScale ) );
+ if ( !MovieGetStaticFrameRate( m_QTMovie, m_QTMovieFrameRate ) )
+ {
+ WarningAssert( "Couldn't Get Frame Rate" );
+ }
+
+ // and get an estimated frame count
+ m_QTMovieFrameCount = m_QTMovieDuration / m_QTMovieTimeScale;
+
+ if ( m_QTMovieFrameRate.GetUnitsPerSecond() == m_QTMovieTimeScale )
+ {
+ m_QTMovieFrameCount = m_QTMovieDuration / m_QTMovieFrameRate.GetUnitsPerFrame();
+ }
+ else
+ {
+ m_QTMovieFrameCount = (int) ( (float) m_QTMovieDurationinSec * m_QTMovieFrameRate.GetFPS() + 0.5f );
+ }
+
+ // what size do we set the output rect to?
+ GetMovieNaturalBoundsRect(m_QTMovie, &m_QTMovieRect);
+
+ m_VideoFrameWidth = m_QTMovieRect.right;
+ m_VideoFrameHeight = m_QTMovieRect.bottom;
+
+ // Sanity check...
+ AssertExitFunc( m_QTMovieRect.top == 0 && m_QTMovieRect.left == 0 &&
+ m_QTMovieRect.right >= cMinVideoFrameWidth && m_QTMovieRect.right <= cMaxVideoFrameWidth &&
+ m_QTMovieRect.bottom >= cMinVideoFrameHeight && m_QTMovieRect.bottom <= cMaxVideoFrameHeight &&
+ m_QTMovieRect.right % 4 == 0,
+ SetResult( VideoResult::VIDEO_ERROR_OCCURED ) );
+
+ // Setup the QuiuckTime Graphics World for the Movie
+ status = QTNewGWorld( &m_MovieGWorld, k32BGRAPixelFormat, &m_QTMovieRect, nil, nil, 0 );
+ AssertExit( status == noErr );
+
+ // Setup the playback gamma according to the convar
+ SetGWorldDecodeGamma( m_MovieGWorld, VideoPlaybackGamma::USE_GAMMA_CONVAR );
+
+ // Assign the GWorld to this movie
+ SetMovieGWorld( m_QTMovie, m_MovieGWorld, nil );
+
+ // Setup Movie Audio, unless suppressed
+ if ( !CreateMovieAudioContext( m_bHasAudio, m_QTMovie, &m_AudioContext, true, &m_CurrentVolume ) )
+ {
+ SetResult( VideoResult::AUDIO_ERROR_OCCURED );
+ WarningAssert( "Couldn't Set Audio" );
+ }
+
+ // Get the time of the first frame
+ OSType qTypes[1] = { VisualMediaCharacteristic };
+ short qFlags = nextTimeStep | nextTimeEdgeOK; // use nextTimeStep instead of nextTimeMediaSample for MPEG 1-2 compatibility
+
+ GetMovieNextInterestingTime( m_QTMovie, qFlags, 1, qTypes, (TimeValue) 0, fixed1, &m_MovieFirstFrameTime, NULL );
+ AssertExitFunc( GetMoviesError() == noErr, SetResult( VideoResult::VIDEO_ERROR_OCCURED ) );
+
+ // Preroll the movie
+ if ( BITFLAGS_SET( m_PlaybackFlags, VideoPlaybackFlags::PRELOAD_VIDEO ) )
+ {
+ Fixed playRate = GetMoviePreferredRate( m_QTMovie );
+ status = PrerollMovie( m_QTMovie, m_MovieFirstFrameTime, playRate );
+ AssertExitFunc( status == noErr, SetResult( VideoResult::VIDEO_ERROR_OCCURED ) );
+ }
+
+ m_bMovieInitialized = true;
+}
+
+
+void CQuickTimeMaterial::CloseQTFile()
+{
+ if ( m_QTMovie == nullptr )
+ {
+ return;
+ }
+
+ SAFE_RELEASE_AUDIOCONTEXT( m_AudioContext );
+ SAFE_DISPOSE_GWORLD( m_MovieGWorld );
+ SAFE_DISPOSE_MOVIE( m_QTMovie );
+
+ SetQTFileName( nullptr );
+}
+
+
diff --git a/video/quicktime_material.h b/video/quicktime_material.h
new file mode 100644
index 0000000..a80b644
--- /dev/null
+++ b/video/quicktime_material.h
@@ -0,0 +1,221 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+
+#ifndef QUICKTIME_MATERIAL_H
+#define QUICKTIME_MATERIAL_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Forward declarations
+//-----------------------------------------------------------------------------
+class IFileSystem;
+class IMaterialSystem;
+class CQuickTimeMaterial;
+
+//-----------------------------------------------------------------------------
+// Global interfaces - you already did the needed includes, right?
+//-----------------------------------------------------------------------------
+extern IFileSystem *g_pFileSystem;
+extern IMaterialSystem *materials;
+
+//-----------------------------------------------------------------------------
+// Quicktime includes
+//-----------------------------------------------------------------------------
+#if defined ( OSX )
+ #include <quicktime/QTML.h>
+ #include <quicktime/Movies.h>
+ #include <quicktime/MediaHandlers.h>
+#elif defined ( WIN32 )
+ #include <QTML.h>
+ #include <Movies.h>
+ #include <windows.h>
+ #include <MediaHandlers.h>
+#elif
+ #error "Quicktime not supported on this target platform"
+#endif
+
+
+#include "video/ivideoservices.h"
+
+#include "video_macros.h"
+#include "quicktime_common.h"
+
+#include "materialsystem/itexture.h"
+#include "materialsystem/imaterialsystem.h"
+#include "materialsystem/MaterialSystemUtil.h"
+
+
+// -----------------------------------------------------------------------------
+// Texture regenerator - callback to get new movie pixels into the texture
+// -----------------------------------------------------------------------------
+class CQuicktimeMaterialRGBTextureRegenerator : public ITextureRegenerator
+{
+ public:
+ CQuicktimeMaterialRGBTextureRegenerator();
+ ~CQuicktimeMaterialRGBTextureRegenerator();
+
+ void SetSourceGWorld( GWorldPtr theGWorld, int nWidth, int nHeight );
+
+ // Inherited from ITextureRegenerator
+ virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect );
+ virtual void Release();
+
+ private:
+ GWorldPtr m_SrcGWorld;
+ int m_nSourceWidth;
+ int m_nSourceHeight;
+};
+
+
+
+// -----------------------------------------------------------------------------
+// Class used to play a QuickTime video onto a texture
+// -----------------------------------------------------------------------------
+class CQuickTimeMaterial : public IVideoMaterial
+{
+ public:
+ CQuickTimeMaterial();
+ ~CQuickTimeMaterial();
+
+ static const int MAX_QT_FILENAME_LEN = 255;
+ static const int MAX_MATERIAL_NAME_LEN = 255;
+ static const int TEXTURE_SIZE_ALIGNMENT = 8;
+
+ // Initializes, shuts down the material
+ bool Init( const char *pMaterialName, const char *pFileName, VideoPlaybackFlags_t flags );
+ void Shutdown();
+
+ // Video information functions
+ virtual const char *GetVideoFileName(); // Gets the file name of the video this material is playing
+ virtual VideoResult_t GetLastResult(); // Gets detailed info on the last operation
+
+ virtual VideoFrameRate_t &GetVideoFrameRate(); // Returns the frame rate of the associated video in FPS
+
+ // Audio Functions
+ virtual bool HasAudio(); // Query if the video has an audio track
+
+ virtual bool SetVolume( float fVolume ); // Adjust the playback volume
+ virtual float GetVolume(); // Query the current volume
+ virtual void SetMuted( bool bMuteState ); // Mute/UnMutes the audio playback
+ virtual bool IsMuted(); // Query muted status
+
+ virtual VideoResult_t SoundDeviceCommand( VideoSoundDeviceOperation_t operation, void *pDevice = nullptr, void *pData = nullptr ); // Assign Sound Device for this Video Material
+
+ // Video playback state functions
+ virtual bool IsVideoReadyToPlay(); // Queries if the video material was initialized successfully and is ready for playback, but not playing or finished
+ virtual bool IsVideoPlaying(); // Is the video currently playing (and needs update calls, etc)
+ virtual bool IsNewFrameReady(); // Do we have a new frame to get & display?
+ virtual bool IsFinishedPlaying(); // Have we reached the end of the movie
+
+ virtual bool StartVideo(); // Starts the video playing
+ virtual bool StopVideo(); // Terminates the video playing
+
+ virtual void SetLooping( bool bLoopVideo ); // Sets the video to loop (or not)
+ virtual bool IsLooping(); // Queries if the video is looping
+
+ virtual void SetPaused( bool bPauseState ); // Pauses or Unpauses video playback
+ virtual bool IsPaused(); // Queries if the video is paused
+
+ // Position in playback functions
+ virtual float GetVideoDuration(); // Returns the duration of the associated video in seconds
+ virtual int GetFrameCount(); // Returns the total number of (unique) frames in the video
+
+ virtual bool SetFrame( int FrameNum ); // Sets the current frame # in the video to play next
+ virtual int GetCurrentFrame(); // Gets the current frame # for the video playback, 0 Based
+
+ virtual bool SetTime( float flTime ); // Sets the video playback to specified time (in seconds)
+ virtual float GetCurrentVideoTime(); // Gets the current time in the video playback
+
+ // Update function
+ virtual bool Update(); // Updates the video frame to reflect the time passed, true = new frame available
+
+ // Material / Texture Info functions
+ virtual IMaterial *GetMaterial(); // Gets the IMaterial associated with an video material
+
+ virtual void GetVideoTexCoordRange( float *pMaxU, float *pMaxV ) ; // Returns the max texture coordinate of the video portion of the material surface ( 0.0, 0.0 to U, V )
+ virtual void GetVideoImageSize( int *pWidth, int *pHeight ); // Returns the frame size of the Video Image Frame in pixels ( the stored in a subrect of the material itself)
+
+
+
+ private:
+ friend class CQuicktimeMaterialRGBTextureRegenerator;
+
+ void Reset(); // clears internal state
+ void SetQTFileName( const char *theQTMovieFileName );
+ VideoResult_t SetResult( VideoResult_t status );
+
+ // Initializes, shuts down the video stream
+ void OpenQTMovie( const char *theQTMovieFileName );
+ void CloseQTFile();
+
+ // Initializes, shuts down the procedural texture
+ void CreateProceduralTexture( const char *pTextureName );
+ void DestroyProceduralTexture();
+
+ // Initializes, shuts down the procedural material
+ void CreateProceduralMaterial( const char *pMaterialName );
+ void DestroyProceduralMaterial();
+
+ CQuicktimeMaterialRGBTextureRegenerator m_TextureRegen;
+
+ VideoResult_t m_LastResult;
+
+ CMaterialReference m_Material; // Ref to Material used for rendering the video frame
+ CTextureReference m_Texture; // Ref to the renderable texture which contains the most recent video frame (in a sub-rect)
+
+ float m_TexCordU; // Max U texture coordinate of the texture sub-rect which holds the video frame
+ float m_TexCordV; // Max V texture coordinate of the texture sub-rect which holds the video frame
+
+ int m_VideoFrameWidth; // Size of the movie frame in pixels
+ int m_VideoFrameHeight;
+
+ char *m_pFileName; // resolved filename of the movie being played
+ VideoPlaybackFlags_t m_PlaybackFlags; // option flags user supplied
+
+ bool m_bInitCalled;
+ bool m_bMovieInitialized;
+ bool m_bMoviePlaying;
+ bool m_bMovieFinishedPlaying;
+ bool m_bMoviePaused;
+ bool m_bLoopMovie;
+
+ bool m_bHasAudio;
+ bool m_bMuted;
+
+ float m_CurrentVolume;
+
+ // QuickTime Stuff
+ Movie m_QTMovie;
+
+ TimeScale m_QTMovieTimeScale; // Units per second
+ TimeValue m_QTMovieDuration; // movie duration in TimeScale Units Per Second
+ float m_QTMovieDurationinSec; // movie duration in seconds
+ VideoFrameRate_t m_QTMovieFrameRate; // Frame Rate of movie
+ int m_QTMovieFrameCount;
+
+ Rect m_QTMovieRect;
+ GWorldPtr m_MovieGWorld;
+
+ QTAudioContextRef m_AudioContext;
+
+ TimeValue m_MovieFirstFrameTime;
+ TimeValue m_NextInterestingTimeToPlay;
+ TimeValue m_MoviePauseTime;
+
+};
+
+
+
+
+
+
+
+#endif // QUICKTIME_MATERIAL_H
diff --git a/video/quicktime_recorder.cpp b/video/quicktime_recorder.cpp
new file mode 100644
index 0000000..9ed4766
--- /dev/null
+++ b/video/quicktime_recorder.cpp
@@ -0,0 +1,2146 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//----------------------------------------------------------------------------------------
+
+#define WIN32_LEAN_AND_MEAN
+
+#include "quicktime_recorder.h"
+#include "filesystem.h"
+
+#ifdef _WIN32
+ #include "windows.h"
+#endif
+
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+
+#define SAFE_DISPOSE_HANDLE( _handle ) if ( _handle != nullptr ) { DisposeHandle( (Handle) _handle ); _handle = nullptr; }
+#define SAFE_DISPOSE_GWORLD( _gworld ) if ( _gworld != nullptr ) { DisposeGWorld( _gworld ); _gworld = nullptr; }
+#define SAFE_DISPOSE_MOVIE( _movie ) if ( _movie != nullptr ) { DisposeMovie( _movie ); ThreadSleep(10); Assert( GetMoviesError() == noErr ); _movie = nullptr; }
+
+
+// Platform check
+#if defined ( OSX ) || defined ( WIN32 )
+ // platform is supported
+#else
+ #error "Unsupported Platform for QuickTime"
+#endif
+
+
+
+//-----------------------------------------------------------------------------
+// Helper functions for copying and converting bitmaps
+//-----------------------------------------------------------------------------
+enum PixelComponent_t
+{
+ RED = 0,
+ GREEN,
+ BLUE,
+ ALPHA
+};
+
+
+int GetBytesPerPixel( OSType pixelFormat )
+{
+ int bpp = ( pixelFormat == k24BGRPixelFormat || pixelFormat == k24RGBPixelFormat ) ? 3 :
+ ( pixelFormat == k32BGRAPixelFormat || pixelFormat == k32RGBAPixelFormat ) ? 4 : 0;
+
+ Assert( bpp > 0 );
+ return bpp;
+}
+
+
+int GetPixelCompnentByteOffset( OSType format, PixelComponent_t component )
+{
+ if ( component == RED )
+ {
+ return ( format == k24RGBPixelFormat || format == k32RGBAPixelFormat ) ? 0 :
+ ( format == k24BGRPixelFormat || format == k32BGRAPixelFormat ) ? 2 : -1;
+ }
+ if ( component == GREEN )
+ {
+ return ( format == k24RGBPixelFormat || format == k32RGBAPixelFormat ) ? 1 :
+ ( format == k24BGRPixelFormat || format == k32BGRAPixelFormat ) ? 1 : -1;
+ }
+ if ( component == BLUE )
+ {
+ return ( format == k24RGBPixelFormat || format == k32RGBAPixelFormat ) ? 2 :
+ ( format == k24BGRPixelFormat || format == k32BGRAPixelFormat ) ? 0 : -1;
+ }
+ if ( component == ALPHA )
+ {
+ return ( format == k32BGRAPixelFormat || format == k32RGBAPixelFormat ) ? 3 : -1;
+ }
+
+ Assert( false );
+ return -1;
+}
+
+
+bool CopyBitMapPixels( int width, int height, OSType srcFmt, byte *srcBase, int srcStride, OSType dstFmt, byte *dstBase, int dstStride )
+{
+ AssertExitF( width > 0 && height > 0 && srcBase != nullptr && srcStride > 0 && dstBase != nullptr && dstStride > 0 );
+
+ // copy the bitmap pixels into our GWorld
+ if ( srcFmt == dstFmt ) // identical formats, memcopy each line
+ {
+ int srcLineSize = width * GetBytesPerPixel( srcFmt );
+ AssertExitF( srcLineSize <= dstStride && srcLineSize <= srcStride );
+
+ for ( int y = 0; y < height; y++ )
+ {
+ byte *src = srcBase + srcStride * y;
+ byte *dst = dstBase + dstStride * y;
+ memcpy( dst, src, srcLineSize );
+ }
+
+ return true;
+ }
+
+ // ok, we got some byte swizzling to do.. get the info we need
+ int srcBPP = GetBytesPerPixel( srcFmt );
+ int dstBPP = GetBytesPerPixel( dstFmt );
+
+ int rSrcIndex = GetPixelCompnentByteOffset( srcFmt, RED );
+ int gSrcIndex = GetPixelCompnentByteOffset( srcFmt, GREEN );
+ int bSrcIndex = GetPixelCompnentByteOffset( srcFmt, BLUE );
+ int aSrcIndex = GetPixelCompnentByteOffset( srcFmt, ALPHA );
+
+ int rDstIndex = GetPixelCompnentByteOffset( dstFmt, RED );
+ int gDstIndex = GetPixelCompnentByteOffset( dstFmt, GREEN );
+ int bDstIndex = GetPixelCompnentByteOffset( dstFmt, BLUE );
+ int aDstIndex = GetPixelCompnentByteOffset( dstFmt, ALPHA );
+
+ Assert( rSrcIndex >= 0 && gSrcIndex >= 0 && bSrcIndex >= 0 );
+
+ // 3 byte format to 3 byte format or a 4 byte format to a 3 byte format?
+ if ( dstBPP == 3 )
+ {
+ for ( int y = 0; y < height; y++ )
+ {
+ byte *src = srcBase + srcStride * y;
+ byte *dst = dstBase + dstStride * y;
+
+ for ( int x = 0; x < width; x++, dst+=dstBPP, src+=srcBPP )
+ {
+ dst[rDstIndex] = src[rSrcIndex];
+ dst[gDstIndex] = src[gSrcIndex];
+ dst[bDstIndex] = src[bSrcIndex];
+ }
+ }
+ return true;
+ }
+
+ AssertExitF( aDstIndex >= 0 );
+
+ // 3 byte format to 4 byte format?
+ if ( srcBPP == 3 && dstBPP == 4 )
+ {
+ for ( int y = 0; y < height; y++ )
+ {
+ byte *src = srcBase + srcStride * y;
+ byte *dst = dstBase + dstStride * y;
+
+ for ( int x = 0; x < width; x++, dst+=dstBPP, src+=srcBPP )
+ {
+ dst[rDstIndex] = src[rSrcIndex];
+ dst[gDstIndex] = src[gSrcIndex];
+ dst[bDstIndex] = src[bSrcIndex];
+ dst[aDstIndex] = 0xFF;
+ }
+ }
+ return true;
+ }
+
+ // 4 byte format to 4 byte format?
+ if ( srcBPP == 4 && dstBPP == 4 )
+ {
+ for ( int y = 0; y < height; y++ )
+ {
+ byte *src = srcBase + srcStride * y;
+ byte *dst = dstBase + dstStride * y;
+
+ for ( int x = 0; x < width; x++, dst+=dstBPP, src+=srcBPP )
+ {
+ dst[rDstIndex] = src[rSrcIndex];
+ dst[gDstIndex] = src[gSrcIndex];
+ dst[bDstIndex] = src[bSrcIndex];
+ dst[aDstIndex] = src[aSrcIndex];
+ }
+ }
+ return true;
+ }
+
+ // didn't find the format?
+ Assert( false );
+ return false;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Utility functions to save targa images
+//-----------------------------------------------------------------------------
+#pragma pack( push, 1 )
+struct TGA_Header
+{
+ public:
+ byte identsize; // size of ID field that follows 18 byte header (0 usually)
+ byte colourmaptype; // type of colour map 0=none, 1=has palette
+ byte imagetype; // type of image 0=none,1=indexed,2=rgb,3=grey,+8=rle packed
+
+ short colourmapstart; // first colour map entry in palette
+ short colourmaplength; // number of colours in palette
+ byte colourmapbits; // number of bits per palette entry 15,16,24,32
+
+ short xstart; // image x origin
+ short ystart; // image y origin
+ short width; // image width in pixels
+ short height; // image height in pixels
+ byte bits; // image bits per pixel 8,16,24,32
+ byte descriptor; // image descriptor bits (vh flip bits)
+
+ // pixel data follows header
+};
+#pragma pack(pop)
+
+
+
+void SaveToTargaFile( int frameNum, const char* pBaseFileName, int width, int height, void *pPixels, OSType PixelFormat, int strideAdjust )
+{
+ if ( pBaseFileName == nullptr || pPixels== nullptr ) return;
+
+ Assert( sizeof( TGA_Header ) == 18 );
+
+ TGA_Header theHeader;
+ ZeroVar( theHeader );
+
+ int BytesPerPixel = GetBytesPerPixel( PixelFormat );
+ Assert( BytesPerPixel > 0 );
+
+ theHeader.imagetype = 2;
+ theHeader.width = (short) width;
+ theHeader.height = (short) height;
+ theHeader.colourmapbits = BytesPerPixel * 8;
+ theHeader.bits = BytesPerPixel * 8;
+ theHeader.descriptor = ( BytesPerPixel == 4) ? ( 8 | 32 ) : 32; // Targa32, Upper Left Origin, attribute (alpha) bits in bits 0-3
+
+ char TGAFileName[MAX_PATH];
+
+ V_snprintf( TGAFileName, MAX_PATH, "%s%.4d.tga", pBaseFileName, frameNum );
+
+ FileHandle_t TGAFile = g_pFullFileSystem->Open( TGAFileName, "wb" );
+
+ g_pFullFileSystem->Write( &theHeader, sizeof( theHeader ), TGAFile );
+
+ // is the buffer in BGR format?
+ if ( PixelFormat == k24BGRPixelFormat || PixelFormat == k32BGRAPixelFormat )
+ {
+ if ( strideAdjust == 0 )
+ {
+ g_pFullFileSystem->Write( pPixels, width * height * BytesPerPixel, TGAFile );
+ }
+ else
+ {
+ int lineWidth = width * BytesPerPixel;
+ int lineOffset = lineWidth + strideAdjust;
+ for ( int y = 0; y < height; y++ )
+ {
+ byte *pData = (byte*) pPixels + ( y * lineOffset );
+ g_pFullFileSystem->Write( pData, lineWidth, TGAFile );
+ }
+ }
+
+ }
+ else // we need to convert the bits from RGB to BGR
+ {
+ byte *pData = new byte[width * height * BytesPerPixel];
+
+ OSType tgaFormat = ( PixelFormat == k24RGBPixelFormat ) ? k24BGRPixelFormat :
+ ( PixelFormat == k32RGBAPixelFormat ) ? k32BGRAPixelFormat : 0;
+
+ CopyBitMapPixels( width, height, PixelFormat, (byte*) pPixels, width * BytesPerPixel + strideAdjust, tgaFormat, pData, width * BytesPerPixel );
+
+ g_pFullFileSystem->Write( pData, width * height * BytesPerPixel, TGAFile );
+
+ delete [] pData;
+ }
+
+ g_pFullFileSystem->Close( TGAFile );
+
+}
+
+
+
+
+// ===========================================================================
+// Data tables used to estimate file size
+// ===========================================================================
+enum EstVideoEncodeQuality_t
+{
+ cVEQuality_Min = 0,
+ cVEQuality_Low = 25,
+ cVEQuality_Normal = 50,
+ cVEQuality_High = 75,
+ cVEQuality_Max = 100
+};
+
+
+struct EncodingDataRateInfo_t
+{
+ EstVideoEncodeQuality_t m_QualitySetting;
+ int m_XResolution;
+ int m_YResolution;
+ float m_DataRate; // in MBits / second
+};
+
+
+struct VideoRes_t
+{
+ int X, Y;
+};
+
+
+static EstVideoEncodeQuality_t s_QualityPresets[] =
+{
+ cVEQuality_Min,
+ cVEQuality_Low,
+ cVEQuality_Normal,
+ cVEQuality_High,
+ cVEQuality_Max
+};
+
+
+static VideoRes_t s_ResolutionPresets[] =
+{
+ { 16, 16 },
+ { 720, 480 },
+ { 640, 960 },
+ { 960, 640 },
+ { 1280, 720 },
+ { 1920, 1080 },
+ { 2048, 2048 },
+};
+
+
+static EncodingDataRateInfo_t s_H264EncodeRates[] =
+{
+ { cVEQuality_Min, 16, 160, 2.00f },
+ { cVEQuality_Min, 720, 480, 2.26f },
+ { cVEQuality_Min, 640, 960, 2.73f },
+ { cVEQuality_Min, 960, 640, 2.91f },
+ { cVEQuality_Min, 1280, 720, 3.56f },
+ { cVEQuality_Min, 1920, 1080, 5.6f },
+ { cVEQuality_Min, 2048, 2048, 6.6f },
+
+ { cVEQuality_Low, 16, 160, 3.00f },
+ { cVEQuality_Low, 720, 480, 3.65f },
+ { cVEQuality_Low, 640, 960, 4.57f },
+ { cVEQuality_Low, 960, 640, 5.03f },
+ { cVEQuality_Low, 1280, 720, 6.41f },
+ { cVEQuality_Low, 1920, 1080, 10.57f },
+ { cVEQuality_Low, 2048, 2048, 13.0f },
+
+ { cVEQuality_Normal, 16, 160, 5.00f },
+ { cVEQuality_Normal, 720, 480, 6.4f },
+ { cVEQuality_Normal, 640, 960, 8.25f },
+ { cVEQuality_Normal, 960, 640, 9.24f },
+ { cVEQuality_Normal, 1280, 720, 12.1f },
+ { cVEQuality_Normal, 1920, 1080, 20.64f },
+ { cVEQuality_Normal, 2048, 2048, 25.0f },
+
+ { cVEQuality_High, 16, 160, 9.50f },
+ { cVEQuality_High, 720, 480, 11.3f },
+ { cVEQuality_High, 640, 960, 15.06f },
+ { cVEQuality_High, 960, 640, 16.9f },
+ { cVEQuality_High, 1280, 720, 22.72 },
+ { cVEQuality_High, 1920, 1080, 40.06f },
+ { cVEQuality_High, 2048, 2048, 52.5f },
+
+ { cVEQuality_Max, 16, 160, 15.50f },
+ { cVEQuality_Max, 720, 480, 19.33f },
+ { cVEQuality_Max, 640, 960, 29.89f },
+ { cVEQuality_Max, 960, 640, 26.82f },
+ { cVEQuality_Max, 1280, 720, 41.08f },
+ { cVEQuality_Max, 1920, 1080, 75.14f },
+ { cVEQuality_Max, 2048, 2048, 90.0f },
+
+};
+
+
+
+
+// ===========================================================================
+// CQuickTimeVideoRecorder class - implements IVideoRecorder interface for
+// QuickTime, and buffers commands to the actual encoder object
+// ===========================================================================
+CQuickTimeVideoRecorder::CQuickTimeVideoRecorder() :
+ m_pEncoder( nullptr ),
+ m_LastResult( VideoResult::SUCCESS ),
+ m_bHasAudio( false ),
+ m_bMovieFinished( false )
+{
+
+}
+
+
+CQuickTimeVideoRecorder::~CQuickTimeVideoRecorder()
+{
+ if ( m_pEncoder != nullptr )
+ {
+ // Abort any encoding in progress
+ if ( !m_bMovieFinished )
+ {
+ AbortMovie();
+ }
+ SAFE_DELETE( m_pEncoder );
+ }
+}
+
+
+bool CQuickTimeVideoRecorder::CreateNewMovieFile( const char *pFilename, bool hasAudio )
+{
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( IS_NOT_EMPTY( pFilename ) );
+
+ SetResult( VideoResult::OPERATION_ALREADY_PERFORMED );
+ AssertExitF( m_pEncoder == nullptr && !m_bMovieFinished );
+
+ // Create new video recorder
+ m_pEncoder = new CQTVideoFileComposer();
+ if ( !m_pEncoder->CreateNewMovie( pFilename, hasAudio ) )
+ {
+ SetResult( m_pEncoder->GetResult() ); // save the error result for after the encoder goes poof
+ SAFE_DELETE( m_pEncoder );
+ return false;
+ }
+
+ m_bHasAudio = hasAudio;
+
+ SetResult( VideoResult::SUCCESS );
+ return true;
+}
+
+
+bool CQuickTimeVideoRecorder::SetMovieVideoParameters( VideoEncodeCodec_t theCodec, int videoQuality, int movieFrameWidth, int movieFrameHeight, VideoFrameRate_t movieFPS, VideoEncodeGamma_t gamma )
+{
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( IS_IN_RANGECOUNT( theCodec, VideoEncodeCodec::DEFAULT_CODEC, VideoEncodeCodec::CODEC_COUNT ) );
+ AssertExitF( IS_IN_RANGE( videoQuality, VideoEncodeQuality::MIN_QUALITY, VideoEncodeQuality::MAX_QUALITY ) );
+ AssertExitF( IS_IN_RANGE( movieFrameWidth, cMinVideoFrameWidth, cMaxVideoFrameWidth ) && IS_IN_RANGE( movieFrameHeight, cMinVideoFrameHeight, cMaxVideoFrameHeight ) );
+ AssertExitF( IS_IN_RANGE( movieFPS.GetFPS(), cMinFPS, cMaxFPS ) );
+ AssertExitF( IS_IN_RANGECOUNT( gamma, VideoEncodeGamma::NO_GAMMA_ADJUST, VideoEncodeGamma::GAMMA_COUNT ) );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( m_pEncoder != nullptr && !m_bMovieFinished );
+
+ return m_pEncoder->SetMovieVideoParameters( movieFrameWidth, movieFrameHeight, movieFPS, theCodec, videoQuality, gamma );
+}
+
+
+bool CQuickTimeVideoRecorder::SetMovieSourceImageParameters( VideoEncodeSourceFormat_t srcImageFormat, int imgWidth, int imgHeight )
+{
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( IS_IN_RANGECOUNT( srcImageFormat, VideoEncodeSourceFormat::VIDEO_FORMAT_FIRST, VideoEncodeSourceFormat::VIDEO_FORMAT_COUNT ) );
+ AssertExitF( IS_IN_RANGE( imgWidth, cMinVideoFrameWidth, cMaxVideoFrameWidth ) && IS_IN_RANGE( imgHeight, cMinVideoFrameHeight, cMaxVideoFrameHeight ) );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( m_pEncoder != nullptr && !m_bMovieFinished );
+
+ return m_pEncoder->SetMovieSourceImageParameters( imgWidth, imgHeight, srcImageFormat );
+}
+
+
+bool CQuickTimeVideoRecorder::SetMovieSourceAudioParameters( AudioEncodeSourceFormat_t srcAudioFormat, int audioSampleRate, AudioEncodeOptions_t audioOptions, int audioSampleGroupSize )
+{
+ SetResult( VideoResult::ILLEGAL_OPERATION );
+ AssertExitF( m_bHasAudio );
+
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( IS_IN_RANGECOUNT( srcAudioFormat, AudioEncodeSourceFormat::AUDIO_NONE, AudioEncodeSourceFormat::AUDIO_FORMAT_COUNT ) );
+ AssertExitF( audioSampleRate == 0 || IS_IN_RANGE( audioSampleRate, cMinSampleRate, cMaxSampleRate ) );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( m_pEncoder != nullptr && !m_bMovieFinished );
+
+ bool result = m_pEncoder->SetMovieSourceAudioParameters( srcAudioFormat, audioSampleRate, audioOptions, audioSampleGroupSize );
+
+ m_bHasAudio = m_pEncoder->HasAudio(); // Audio can be turned off after specifying, so reload status
+ return result;
+}
+
+
+bool CQuickTimeVideoRecorder::IsReadyToRecord()
+{
+ return ( m_pEncoder == nullptr || m_bMovieFinished ) ? false : m_pEncoder->IsReadyToRecord();
+}
+
+
+VideoResult_t CQuickTimeVideoRecorder::GetLastResult()
+{
+ return ( m_pEncoder == nullptr ) ? m_LastResult : m_pEncoder->GetResult();
+}
+
+
+void CQuickTimeVideoRecorder::SetResult( VideoResult_t resultCode )
+{
+ m_LastResult = resultCode;
+ if ( m_pEncoder != nullptr )
+ {
+ m_pEncoder->SetResult( resultCode );
+ }
+}
+
+
+bool CQuickTimeVideoRecorder::AppendVideoFrame( void *pFrameBuffer, int nStrideAdjustBytes )
+{
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( pFrameBuffer != nullptr );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( IsReadyToRecord() );
+
+ return m_pEncoder->AppendVideoFrameToMedia( pFrameBuffer, nStrideAdjustBytes );
+}
+
+
+bool CQuickTimeVideoRecorder::AppendAudioSamples( void *pSampleBuffer, size_t sampleSize )
+{
+ SetResult( VideoResult::ILLEGAL_OPERATION );
+ AssertExitF( m_bHasAudio );
+
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( pSampleBuffer != nullptr );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( IsReadyToRecord() );
+
+ return m_pEncoder->AppendAudioSamplesToMedia( pSampleBuffer, sampleSize );
+}
+
+
+int CQuickTimeVideoRecorder::GetFrameCount()
+{
+ return ( m_pEncoder == nullptr ) ? 0 : m_pEncoder->GetFrameCount();
+}
+
+
+int CQuickTimeVideoRecorder::GetSampleCount()
+{
+ return ( m_pEncoder == nullptr ) ? 0 : m_pEncoder->GetSampleCount();
+}
+
+
+VideoFrameRate_t CQuickTimeVideoRecorder::GetFPS()
+{
+ return ( m_pEncoder == nullptr ) ? VideoFrameRate_t( 0 ) : m_pEncoder->GetFPS();
+}
+
+
+int CQuickTimeVideoRecorder::GetSampleRate()
+{
+ return ( m_pEncoder == nullptr ) ? 0 : m_pEncoder->GetSampleRate();
+}
+
+
+bool CQuickTimeVideoRecorder::AbortMovie()
+{
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( m_pEncoder != nullptr && !m_bMovieFinished );
+
+ m_bMovieFinished = true;
+ return m_pEncoder->AbortMovie();
+}
+
+
+bool CQuickTimeVideoRecorder::FinishMovie( bool SaveMovieToDisk )
+{
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( m_pEncoder != nullptr && !m_bMovieFinished );
+
+ m_bMovieFinished = true;
+ return m_pEncoder->FinishMovie( SaveMovieToDisk );
+}
+
+
+#ifdef ENABLE_EXTERNAL_ENCODER_LOGGING
+bool CQuickTimeVideoRecorder::LogMessage( const char *pMsg )
+{
+ if ( m_pEncoder != nullptr )
+ {
+ m_pEncoder->LogMessage( pMsg );
+ }
+
+ return true;
+}
+#endif
+
+bool CQuickTimeVideoRecorder::EstimateMovieFileSize( size_t *pEstSize, int movieWidth, int movieHeight, VideoFrameRate_t movieFps, float movieDuration, VideoEncodeCodec_t theCodec, int videoQuality, AudioEncodeSourceFormat_t srcAudioFormat, int audioSampleRate )
+{
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertPtrExitF( pEstSize );
+ *pEstSize = 0;
+
+ AssertExitF( IS_IN_RANGE( movieWidth, cMinVideoFrameWidth, cMaxVideoFrameWidth ) && IS_IN_RANGE( movieHeight, cMinVideoFrameHeight, cMaxVideoFrameHeight ) );
+ AssertExitF( IS_IN_RANGE( movieFps.GetFPS(), cMinFPS, cMaxFPS ) && movieDuration > 0.0f );
+ AssertExitF( IS_IN_RANGECOUNT( theCodec, VideoEncodeCodec::DEFAULT_CODEC, VideoEncodeCodec::CODEC_COUNT ) );
+ AssertExitF( IS_IN_RANGE( videoQuality, VideoEncodeQuality::MIN_QUALITY, VideoEncodeQuality::MAX_QUALITY ) );
+ AssertExitF( IS_IN_RANGECOUNT( srcAudioFormat, AudioEncodeSourceFormat::AUDIO_NONE, AudioEncodeSourceFormat::AUDIO_FORMAT_COUNT ) );
+ AssertExitF( audioSampleRate == 0 || IS_IN_RANGE( audioSampleRate, cMinSampleRate, cMaxSampleRate ) );
+
+ // Determine the Quality LERP
+ int Q1 = VideoEncodeQuality::MIN_QUALITY, Q2 = VideoEncodeQuality::MAX_QUALITY;
+ float Qlerp = 0.0f;
+ bool bQLerp = true;
+
+ for ( int i = 0; i < ARRAYSIZE( s_QualityPresets ); i++ )
+ {
+ if ( s_QualityPresets[i] == videoQuality )
+ {
+ Q1 = videoQuality;
+ Q2 = videoQuality;
+ Qlerp = 0.0f;
+ bQLerp = false;
+ break;
+ }
+ else if ( s_QualityPresets[i] < videoQuality && s_QualityPresets[i] > Q1 )
+ {
+ Q1 = s_QualityPresets[i];
+ }
+ else if ( s_QualityPresets[i] > videoQuality && s_QualityPresets[i] < Q2 )
+ {
+ Q2 = s_QualityPresets[i];
+ }
+ }
+
+ if ( bQLerp )
+ {
+ Qlerp = ( (float) videoQuality - (float) Q1 ) / ( (float) Q2 - (float) Q1 ) ;
+ }
+
+ // determine the resolution lerp
+
+ VideoRes_t RES1 = { cMinVideoFrameWidth, cMinVideoFrameHeight }, RES2 = { cMaxVideoFrameWidth, cMaxVideoFrameHeight };
+ float RLerp = 0.0f;
+ bool bRLerp = true;
+ int nPixels = movieHeight * movieWidth;
+ int R1pixels = RES1.X * RES1.Y;
+ int R2pixels = RES2.X * RES2.Y;
+
+ for ( int i = 0; i < ARRAYSIZE( s_ResolutionPresets ); i++ )
+ {
+ if ( s_ResolutionPresets[i].X == movieWidth && s_ResolutionPresets[i].Y == movieHeight )
+ {
+ RES1 = s_ResolutionPresets[i];
+ RES2 = s_ResolutionPresets[i];
+ RLerp = 0.0f;
+ bRLerp = false;
+ break;
+ }
+
+ int rPixels = s_ResolutionPresets[i].X * s_ResolutionPresets[i].Y;
+
+ if ( rPixels <= nPixels && rPixels > R1pixels )
+ {
+ RES1 = s_ResolutionPresets[i];
+ R1pixels = rPixels;
+ }
+ else if ( rPixels > nPixels && rPixels < R2pixels )
+ {
+ RES2 = s_ResolutionPresets[i];
+ R2pixels = rPixels;
+ }
+ }
+
+ if ( bRLerp )
+ {
+ RLerp = (float) (nPixels - R1pixels) / (float) ( R2pixels - R1pixels );
+ }
+
+
+ // Now we see what we need to do
+ // We determine the estimated Data Rate
+
+ float DR = 0.0f;
+
+ if ( bQLerp == false && bRLerp == false )
+ {
+ DR = GetDataRate( videoQuality, movieWidth, movieHeight );
+ }
+ else if ( bQLerp == true && bRLerp == false )
+ {
+ float D1 = GetDataRate( Q1, movieWidth, movieHeight );
+ float D2 = GetDataRate( Q2, movieWidth, movieHeight );
+
+ DR = D1 + Qlerp * ( D2 - D1 );
+ }
+ else if ( bQLerp == false && bRLerp == true )
+ {
+ float D1 = GetDataRate( videoQuality, RES1.X, RES1.Y );
+ float D2 = GetDataRate( videoQuality, RES2.X, RES2.Y );
+
+ DR = D1 + RLerp * ( D2 - D1 );
+
+ }
+ else // need the full filter
+ {
+ float D1 = GetDataRate( Q1, RES1.X, RES1.Y );
+ float D2 = GetDataRate( Q1, RES2.X, RES2.Y );
+ float D3 = GetDataRate( Q2, RES1.X, RES1.Y );
+ float D4 = GetDataRate( Q2, RES2.X, RES2.Y );
+
+ float I1 = D1 + Qlerp * ( D3 - D1 );
+ float I2 = D2 + Qlerp * ( D4 - D2 );
+
+ DR = I1 + RLerp * ( I2 - I1 );
+ }
+
+ // Now do the big computation
+ // should this be 1024 * 1024?
+
+ double VideoData = DR * 1000000 / 8 * movieDuration ;
+
+ // Quick hack to guess at audio data size
+ double audioData = 0;
+
+ if ( srcAudioFormat == AudioEncodeSourceFormat::AUDIO_16BIT_PCMStereo )
+ {
+ audioData = ( audioSampleRate * 2 ) * ( 0.05 * DR );
+ }
+
+ *pEstSize = (size_t) VideoData + (size_t) audioData;
+
+ SetResult( VideoResult::SUCCESS );
+ return true;
+}
+
+
+float CQuickTimeVideoRecorder::GetDataRate( int quality, int width, int height )
+{
+ for (int i = 0; i < ARRAYSIZE( s_H264EncodeRates ); i++ )
+ {
+ if ( s_H264EncodeRates[i].m_QualitySetting == quality && s_H264EncodeRates[i].m_XResolution == width && s_H264EncodeRates[i].m_YResolution == height )
+ {
+ return s_H264EncodeRates[i].m_DataRate;
+ }
+ }
+
+ Assert( false );
+ return 0.0f;
+}
+
+
+
+
+
+// ------------------------------------------------------------------------
+// CQTVideoFileComposer - Class to encapsulate the creation of a QuickTime
+// Movie from a sequence of uncompressed images and (future) audio samples
+// ------------------------------------------------------------------------
+CQTVideoFileComposer::CQTVideoFileComposer() :
+ m_LastResult( VideoResult::SUCCESS ),
+
+ m_bMovieCreated( false ),
+ m_bHasAudioTrack( false ),
+
+ m_bMovieConfigured( false ),
+ m_bSourceImagesConfigured( false ),
+ m_bSourceAudioConfigured( false ),
+
+ m_bComposingMovie( false ),
+ m_bMovieCompleted( false ),
+
+ m_nFramesAdded( 0 ),
+ m_nAudioFramesAdded( 0 ),
+ m_nSamplesAdded( 0 ),
+ m_nSamplesAddedToMedia( 0 ),
+
+ m_MovieFrameWidth( 0 ),
+ m_MovieFrameHeight( 0 ),
+
+ m_MovieTimeScale( 0 ),
+ m_DurationPerFrame( 0 ),
+
+ m_AudioOptions( AudioEncodeOptions::NO_AUDIO_OPTIONS ),
+ m_SampleGrouping( AG_NONE ),
+ m_nAudioSampleGroupSize( 0 ),
+
+ m_AudioSourceFrequency( 0 ),
+ m_AudioBytesPerSample( 0 ),
+
+ m_bBufferSourceAudio( false ),
+ m_bLimitAudioDurationToVideo( false ),
+
+ m_srcAudioBuffer( nullptr ),
+ m_srcAudioBufferSize( 0 ),
+ m_srcAudioBufferCurrentSize( 0 ),
+
+ m_AudioSampleFrameCounter( 0 ),
+
+ m_FileName( nullptr ),
+
+ m_SrcImageWidth( 0 ),
+ m_SrcImageHeight( 0 ),
+ m_ScrImageMaxCompressedSize( 0 ),
+ m_SrcImageBuffer( nullptr ),
+ m_SrcImageCompressedBuffer( nullptr ),
+ m_SrcPixelFormat( 0 ),
+ m_SrcBytesPerPixel( 0 ),
+
+ m_GWorldPixelFormat( 0 ),
+ m_GWorldBytesPerPixel( 0 ),
+ m_GWorldImageWidth( 0 ),
+ m_GWorldImageHeight( 0 ),
+
+ m_srcSoundDescription( nullptr ),
+
+ m_EncodeQuality( CQTVideoFileComposer::DEFAULT_ENCODE_QUALITY ),
+ m_VideoCodecToUse( CQTVideoFileComposer::DEFAULT_CODEC ),
+ m_EncodeGamma( CQTVideoFileComposer::DEFAULT_GAMMA ),
+
+ m_theSrcGWorld( nullptr ),
+
+ m_MovieFileDataRef( nullptr ),
+ m_MovieFileDataRefType( 0 ),
+ m_MovieFileDataHandler( nullptr ),
+
+ m_theMovie( nullptr ),
+ m_theVideoTrack( nullptr),
+ m_theAudioTrack( nullptr ),
+ m_theVideoMedia( nullptr ),
+ m_theAudioMedia( nullptr )
+
+#ifdef LOG_ENCODER_OPERATIONS
+ ,m_LogFile( FILESYSTEM_INVALID_HANDLE )
+#endif
+{
+ m_MovieRecordFPS.SetFPS( 0, false );
+ m_GWorldRect.top = m_GWorldRect.left = m_GWorldRect.bottom = m_GWorldRect.right = 0;
+
+#ifdef LOG_FRAMES_TO_TGA
+ ZeroVar( m_TGAFileBase );
+#endif
+
+}
+
+
+CQTVideoFileComposer::~CQTVideoFileComposer()
+{
+ if ( m_bComposingMovie )
+ {
+ AbortMovie();
+ }
+
+#ifdef LOG_ENCODER_OPERATIONS
+ if ( m_LogFile != FILESYSTEM_INVALID_HANDLE )
+ {
+ g_pFullFileSystem->Close( m_LogFile );
+ m_LogFile = FILESYSTEM_INVALID_HANDLE;
+ }
+
+#endif
+
+
+ SAFE_DELETE_ARRAY( m_FileName );
+ SAFE_DELETE_ARRAY( m_SrcImageBuffer );
+ SAFE_DELETE_ARRAY( m_srcAudioBuffer );
+
+ SAFE_DISPOSE_HANDLE( m_MovieFileDataRef );
+ SAFE_DISPOSE_HANDLE( m_srcSoundDescription );
+ SAFE_DISPOSE_HANDLE( m_SrcImageCompressedBuffer );
+ SAFE_DISPOSE_GWORLD( m_theSrcGWorld );
+
+}
+
+
+#ifdef LOG_ENCODER_OPERATIONS
+void CQTVideoFileComposer::LogMsg( const char* pMsg, ... )
+{
+ const int MAX_TEXT = 8192;
+ static char messageBuf[MAX_TEXT];
+
+ if ( m_LogFile == FILESYSTEM_INVALID_HANDLE || pMsg == nullptr )
+ {
+ return;
+ }
+
+ va_list marker;
+
+ va_start( marker, pMsg );
+
+#ifdef _WIN32
+ int len = _vsnprintf( messageBuf, MAX_TEXT, pMsg, marker );
+#elif POSIX
+ int len = vsnprintf( messageBuf, MAX_TEXT, pMsg, marker );
+#else
+ #error "define vsnprintf type."
+#endif
+
+ // Len < 0 represents an overflow
+ if( len < 0 )
+ {
+ ((char*) pMsg)[MAX_TEXT-1] = nullchar;
+ }
+ va_end( marker );
+
+ g_pFullFileSystem->Write( messageBuf, V_strlen( messageBuf ), m_LogFile );
+
+}
+#endif
+
+
+bool CQTVideoFileComposer::CreateNewMovie( const char *fileName, bool hasAudio )
+{
+ // Validate input and state
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( IS_NOT_EMPTY( fileName ) );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( !m_bMovieCreated && !m_bMovieCompleted );
+
+#ifdef LOG_ENCODER_OPERATIONS
+ char logFileName[MAX_PATH];
+ V_strncpy( logFileName, fileName, MAX_PATH );
+ V_SetExtension( logFileName, ".log", MAX_PATH );
+ m_LogFile = g_pFullFileSystem->Open( logFileName, "wb" );
+ const char* aMsg = (hasAudio) ? "HAS" : "DOES NOT HAVE";
+ LogMsg( "Creating Video File: '%s' - %s AUDIO TRACK\n", fileName, aMsg );
+#endif
+
+ // now create the movie file
+
+ OSErr status = noErr;
+ OSType movieType = FOUR_CHAR_CODE('TVOD'); // todo - change movie type??
+
+ m_MovieFileDataRef = nullptr;
+ m_MovieFileDataRefType = 0;
+ m_MovieFileDataHandler = nullptr;
+
+ CFStringRef imageStrRef = CFStringCreateWithCString ( NULL, fileName, 0 );
+
+ status = QTNewDataReferenceFromFullPathCFString( imageStrRef, (QTPathStyle) kQTNativeDefaultPathStyle, 0, &m_MovieFileDataRef, &m_MovieFileDataRefType );
+ AssertExitF( status == noErr );
+
+
+ status = CreateMovieStorage( m_MovieFileDataRef, m_MovieFileDataRefType, movieType, smCurrentScript, createMovieFileDeleteCurFile | createMovieFileDontCreateResFile, &m_MovieFileDataHandler, &m_theMovie );
+ AssertExitF( status == noErr );
+
+ CFRelease( imageStrRef );
+ m_FileName = COPY_STRING( fileName );
+
+#ifdef LOG_FRAMES_TO_TGA
+ V_strncpy( m_TGAFileBase, m_FileName, sizeof( m_TGAFileBase ) );
+ V_StripExtension( m_TGAFileBase, m_TGAFileBase, sizeof( m_TGAFileBase ) );
+#endif
+
+ // we did it! party on...
+ SetResult( VideoResult::SUCCESS );
+ m_bMovieCreated = true;
+ m_bHasAudioTrack = hasAudio;
+
+ return m_bMovieCreated;
+}
+
+
+bool CQTVideoFileComposer::SetMovieVideoParameters( int width, int height, VideoFrameRate_t movieFPS, VideoEncodeCodec_t desiredCodec, int encodeQuality, VideoEncodeGamma_t gamma )
+{
+ // Validate input and state
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( IS_IN_RANGE( width, cMinVideoFrameWidth, cMaxVideoFrameWidth ) && IS_IN_RANGE( height, cMinVideoFrameHeight, cMaxVideoFrameHeight ) );
+ AssertExitF( IS_IN_RANGE( movieFPS.GetFPS(), cMinFPS, cMaxFPS ) );
+ AssertExitF( IS_IN_RANGECOUNT( desiredCodec, VideoEncodeCodec::DEFAULT_CODEC, VideoEncodeCodec::CODEC_COUNT ) );
+ AssertExitF( IS_IN_RANGE( encodeQuality, VideoEncodeQuality::MIN_QUALITY, VideoEncodeQuality::MAX_QUALITY ) );
+ AssertExitF( IS_IN_RANGECOUNT( gamma, VideoEncodeGamma::NO_GAMMA_ADJUST, VideoEncodeGamma::GAMMA_COUNT ) );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( m_bMovieCreated && !m_bMovieConfigured );
+
+ // Configure video parameters
+ m_MovieFrameWidth = width;
+ m_MovieFrameHeight = height;
+
+ // map the requested codec in
+ switch( desiredCodec )
+ {
+ case VideoEncodeCodec::MPEG2_CODEC:
+ {
+ m_VideoCodecToUse = kMpegYUV420CodecType;
+ break;
+ }
+ case VideoEncodeCodec::MPEG4_CODEC:
+ {
+ m_VideoCodecToUse = kMPEG4VisualCodecType;
+ break;
+ }
+ case VideoEncodeCodec::H261_CODEC:
+ {
+ m_VideoCodecToUse = kH261CodecType;
+ break;
+ }
+ case VideoEncodeCodec::H263_CODEC:
+ {
+ m_VideoCodecToUse = kH263CodecType;
+ break;
+ }
+ case VideoEncodeCodec::H264_CODEC:
+ {
+ m_VideoCodecToUse = kH264CodecType;
+ break;
+ }
+ case VideoEncodeCodec::MJPEG_A_CODEC:
+ {
+ m_VideoCodecToUse = kMotionJPEGACodecType;
+ break;
+ }
+ case VideoEncodeCodec::MJPEG_B_CODEC:
+ {
+ m_VideoCodecToUse = kMotionJPEGBCodecType;
+ break;
+ }
+ case VideoEncodeCodec::SORENSON3_CODEC:
+ {
+ m_VideoCodecToUse = kSorenson3CodecType;
+ break;
+ }
+ case VideoEncodeCodec::CINEPACK_CODEC:
+ {
+ m_VideoCodecToUse = kCinepakCodecType;
+ break;
+ }
+ default: // should never hit this because we are already range checked
+ {
+ m_VideoCodecToUse = CQTVideoFileComposer::DEFAULT_CODEC;
+ break;
+ }
+ }
+
+ // Determine if codec is available...
+ CodecInfo theInfo;
+
+ OSErr status = GetCodecInfo( &theInfo, m_VideoCodecToUse, 0 );
+ if ( status == noCodecErr )
+ {
+ SetResult( VideoResult::CODEC_NOT_AVAILABLE );
+ return false;
+ }
+ AssertExitF( status == noErr );
+
+#ifdef LOG_ENCODER_OPERATIONS
+ char codecName[64];
+ ZeroVar( codecName );
+ V_memcpy( codecName, &theInfo.typeName[1], (int) theInfo.typeName[0] );
+
+ LogMsg( "Video Image Size is (%d x %d)\n", m_MovieFrameWidth, m_MovieFrameHeight );
+ LogMsg( "Codec selected is %s\n", codecName );
+ LogMsg( "Encoding Quality = %d\n", (int) encodeQuality );
+ LogMsg( "Encode Gamma = %d\n", (int) gamma );
+#endif
+
+ // convert encoding quality into quicktime specific value
+ int Q = (int) encodeQuality; - (int) VideoEncodeQuality::MIN_QUALITY;
+ int MaxQ = (int) VideoEncodeQuality::MAX_QUALITY - (int) VideoEncodeQuality::MIN_QUALITY;
+
+ m_EncodeQuality = codecLosslessQuality * ( (float) Q / (float) MaxQ ) ;
+ clamp( m_EncodeQuality, codecMinQuality, codecMaxQuality );
+
+ // convert the gamma correction value into quicktime specific values
+ switch( gamma )
+ {
+ case VideoEncodeGamma::NO_GAMMA_ADJUST:
+ {
+ m_EncodeGamma = kQTUseSourceGammaLevel;
+ break;
+ }
+ case VideoEncodeGamma::PLATFORM_STANDARD_GAMMA:
+ {
+ m_EncodeGamma = kQTUsePlatformDefaultGammaLevel;
+ break;
+ }
+ case VideoEncodeGamma::GAMMA_1_8:
+ {
+ m_EncodeGamma = 0x0001CCCC; // (Fixed) Gamma 1.8
+ break;
+ }
+ case VideoEncodeGamma::GAMMA_2_2:
+ {
+ m_EncodeGamma = kQTCCIR601VideoGammaLevel;
+ break;
+ }
+ case VideoEncodeGamma::GAMMA_2_5:
+ {
+ m_EncodeGamma = 0x00028000; // (Fixed) Gamma 2.5
+ break;
+ }
+ default:
+ {
+ m_EncodeGamma = CQTVideoFileComposer::DEFAULT_GAMMA;
+ break;
+ }
+ }
+
+ // Process the framerate into usable values
+
+ m_MovieRecordFPS = movieFPS;
+
+ m_DurationPerFrame = m_MovieRecordFPS.GetUnitsPerFrame();
+ m_MovieTimeScale = m_MovieRecordFPS.GetUnitsPerSecond();
+
+ AssertExitF( m_DurationPerFrame > 0 && m_MovieTimeScale > 0 );
+
+/* if ( movieFPS.IsNTSCRate() )
+ {
+ m_MovieTimeScale = movieFPS.GetIntFPS() * 1000;
+ m_DurationPerFrame = 1001;
+ }
+ else if ( movieFPS.GetUnitsPerSecond() % movieFPS.GetUnitsPerFrame() == 0 ) // integer frame rate?
+ {
+ m_MovieTimeScale = movieFPS.GetIntFPS() * 1000;
+ m_DurationPerFrame = 1000;
+ }
+ else // round to nearest .001 second
+ {
+ m_MovieTimeScale = (int) ( movieFPS.GetFPS() * 1000 );
+ m_DurationPerFrame = 1000;
+ }
+*/
+
+#ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "Video Frame Rate = %f FPS\n %d time units per second\n %d time units per frame\n", m_MovieRecordFPS.GetFPS(), m_MovieRecordFPS.GetUnitsPerSecond(), m_MovieRecordFPS.GetUnitsPerFrame() );
+ if ( m_MovieRecordFPS.IsNTSCRate() )
+ LogMsg( " IS CONSIDERED NTSC RATE\n");
+ LogMsg( "MovieTimeScale is being set to %d\nDuration Per Frame is %d\n\n", m_MovieTimeScale, m_DurationPerFrame );
+#endif
+
+ // Create the video track and media
+ SetResult( VideoResult::VIDEO_ERROR_OCCURED );
+
+ m_theVideoTrack = NewMovieTrack( m_theMovie, FixRatio( width, 1 ), FixRatio( height, 1 ), kNoVolume );
+ AssertExitF( GetMoviesError() == noErr );
+
+ m_theVideoMedia = NewTrackMedia( m_theVideoTrack, VideoMediaType, m_MovieTimeScale, NULL, 0 );
+ AssertExitF( GetMoviesError() == noErr );
+
+ // we have successfully configured the output movie
+ SetResult( VideoResult::SUCCESS );
+ m_bMovieConfigured = true;
+
+ return true;
+}
+
+
+bool CQTVideoFileComposer::SetMovieSourceImageParameters( int srcWidth, int srcHeight, VideoEncodeSourceFormat_t srcImageFormat )
+{
+ // Validate input and state
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( IS_IN_RANGE( srcWidth, cMinVideoFrameWidth, cMaxVideoFrameWidth ) && IS_IN_RANGE( srcHeight, cMinVideoFrameHeight, cMaxVideoFrameHeight ) );
+ AssertExitF( IS_IN_RANGECOUNT( srcImageFormat, VideoEncodeSourceFormat::VIDEO_FORMAT_FIRST, VideoEncodeSourceFormat::VIDEO_FORMAT_COUNT ) );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( m_bMovieCreated && !m_bMovieCompleted && m_bMovieConfigured && !m_bSourceImagesConfigured );
+
+ // Setup source image format related stuff
+ m_SrcPixelFormat = ( srcImageFormat == VideoEncodeSourceFormat::BGRA_32BIT ) ? k32BGRAPixelFormat :
+ ( srcImageFormat == VideoEncodeSourceFormat::BGR_24BIT ) ? k24BGRPixelFormat :
+ ( srcImageFormat == VideoEncodeSourceFormat::RGB_24BIT ) ? k24RGBPixelFormat :
+ ( srcImageFormat == VideoEncodeSourceFormat::RGBA_32BIT ) ? k32RGBAPixelFormat : 0;
+
+ m_SrcBytesPerPixel = GetBytesPerPixel( m_SrcPixelFormat );
+
+ // Setup source image size related stuff
+ m_SrcImageWidth = srcWidth;
+ m_SrcImageHeight = srcHeight;
+ m_SrcImageSize = srcWidth * srcHeight * m_SrcBytesPerPixel;
+
+ // Setup the GWorld to hold the frame to video compress
+
+ m_GWorldPixelFormat = k32BGRAPixelFormat; // can use k24BGRPixelFormat on Win32.. but it compresses wrong on OSX?
+ m_GWorldBytesPerPixel = 4;
+ m_GWorldImageWidth = CONTAINING_MULTIPLE_OF( srcWidth, 4 ); // make sure the encoded surface is a multiple of 4 in each dimensions
+ m_GWorldImageHeight = CONTAINING_MULTIPLE_OF( srcHeight, 4 );
+
+ m_GWorldRect.top = m_GWorldRect.left = 0;
+ m_GWorldRect.bottom = m_GWorldImageHeight;
+ m_GWorldRect.right = m_GWorldImageWidth;
+
+ // Setup the QuiuckTime Graphics World for incoming frames of video
+ // Always use a 32-bit GWORD to avoid encoding bugs
+ SetResult( VideoResult::VIDEO_ERROR_OCCURED );
+
+ OSErr status = QTNewGWorld( &m_theSrcGWorld, m_GWorldPixelFormat, &m_GWorldRect, nil, nil, 0 );
+ AssertExitF( status == noErr );
+
+ PixMapHandle thePixMap = GetGWorldPixMap( m_theSrcGWorld );
+ AssertPtrExitF( thePixMap );
+
+ status = QTSetPixMapHandleRequestedGammaLevel( thePixMap, m_EncodeGamma );
+ AssertExitF( status == noErr );
+
+ // Set encoding buffer to max size at max quality
+ // Should we try it with the actual quality setting?
+
+ status = GetMaxCompressionSize( thePixMap, &m_GWorldRect, 0, m_EncodeQuality, m_VideoCodecToUse,
+ (CompressorComponent)anyCodec, (long*) &m_ScrImageMaxCompressedSize );
+
+ AssertExitF( status == noErr && m_ScrImageMaxCompressedSize > 0 );
+
+ // allocated buffers for the uncompressed and compressed images
+ m_SrcImageBuffer = new byte[ m_SrcImageSize ];
+ m_SrcImageCompressedBuffer = NewHandle( m_ScrImageMaxCompressedSize );
+
+ // we have successfully configured the video input images
+ SetResult( VideoResult::SUCCESS );
+ m_bSourceImagesConfigured = true;
+
+ return CheckForReadyness(); // We are ready to go if audio is...
+}
+
+
+bool CQTVideoFileComposer::SetMovieSourceAudioParameters( AudioEncodeSourceFormat_t srcAudioFormat, int audioSampleRate, AudioEncodeOptions_t audioOptions, int audioSampleGroupSize )
+{
+ SetResult( VideoResult::ILLEGAL_OPERATION );
+ AssertExitF( m_bHasAudioTrack );
+
+ // Validate input and state
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( IS_IN_RANGECOUNT( srcAudioFormat, AudioEncodeSourceFormat::AUDIO_NONE, AudioEncodeSourceFormat::AUDIO_FORMAT_COUNT ) );
+ AssertExitF( audioSampleRate == 0 || IS_IN_RANGE( audioSampleRate, cMinSampleRate, cMaxSampleRate ) );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( m_bMovieCreated && !m_bMovieCompleted && m_bMovieConfigured && !m_bSourceAudioConfigured );
+
+ // it is possible to disable audio here by passing in AudioEncodeSourceFormat::AUDIO_NONE even
+ // if the movie was created with the hasHadio flag set to true, or by setting the sample rate to 0
+
+ if ( srcAudioFormat == AudioEncodeSourceFormat::AUDIO_NONE || audioSampleRate == 0 )
+ {
+ m_bHasAudioTrack = false;
+ }
+ else
+ {
+ m_AudioOptions = audioOptions;
+
+ // Setup the audio frequency
+ m_AudioSourceFrequency = audioSampleRate;
+
+ // Create the audio track and media
+ SetResult( VideoResult::AUDIO_ERROR_OCCURED );
+
+ m_theAudioTrack = NewMovieTrack( m_theMovie, 0, 0, kFullVolume );
+ AssertExitF( GetMoviesError() == noErr );
+
+ m_theAudioMedia = NewTrackMedia( m_theAudioTrack, SoundMediaType, (TimeScale) audioSampleRate, NULL, 0 );
+ AssertExitF( GetMoviesError() == noErr );
+
+ // Setup the Audio Sound description
+ AudioStreamBasicDescription inASBD;
+
+ switch( srcAudioFormat )
+ {
+ case AudioEncodeSourceFormat::AUDIO_16BIT_PCMStereo:
+ {
+ inASBD.mSampleRate = Float64( audioSampleRate );
+ inASBD.mFormatID = kAudioFormatLinearPCM;
+ inASBD.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
+ inASBD.mBytesPerPacket = 4;
+ inASBD.mFramesPerPacket = 1;
+ inASBD.mBytesPerPacket = 4;
+ inASBD.mChannelsPerFrame = 2;
+ inASBD.mBitsPerChannel = 16;
+ inASBD.mReserved = 0;
+ break;
+ }
+ default:
+ {
+ Assert( false ); // Impossible.. we hope
+ return false;
+ }
+ }
+
+ m_AudioBytesPerSample = inASBD.mBytesPerPacket;
+
+ OSStatus result = QTSoundDescriptionCreate( &inASBD, NULL, 0, NULL, 0, kQTSoundDescriptionKind_Movie_LowestPossibleVersion, (SoundDescriptionHandle*) &m_srcSoundDescription );
+ AssertExitF( result == noErr );
+
+ // Setup audio sample buffering if needed
+
+ m_bLimitAudioDurationToVideo = BITFLAGS_SET( audioOptions, AudioEncodeOptions::LIMIT_AUDIO_TRACK_TO_VIDEO_DURATION );
+
+ m_SampleGrouping = ( BITFLAGS_SET( audioOptions, AudioEncodeOptions::GROUP_SIZE_IS_VIDEO_FRAME ) ) ? CQTVideoFileComposer::AG_PER_FRAME :
+ ( BITFLAGS_SET( audioOptions, AudioEncodeOptions::USE_AUDIO_ENCODE_GROUP_SIZE ) ) ? CQTVideoFileComposer::AG_FIXED_SIZE : AG_NONE;
+
+ // check for invalid sample grouping duration
+ if ( m_SampleGrouping == AG_FIXED_SIZE && ( audioSampleGroupSize < MIN_AUDIO_SAMPLE_GROUP_SIZE || audioSampleGroupSize > MAX_AUDIO_GROUP_SIZE_IN_SEC * m_AudioSourceFrequency ) )
+ {
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ Assert( false );
+ return false;
+ }
+
+ m_bBufferSourceAudio = ( m_SampleGrouping != AG_NONE ) || m_bLimitAudioDurationToVideo;
+
+ // Set up an audio buffer than can hold the maxium specified duration
+ if ( m_bBufferSourceAudio )
+ {
+ m_srcAudioBufferSize = m_AudioSourceFrequency * m_AudioBytesPerSample * MAX_AUDIO_GROUP_SIZE_IN_SEC;
+ m_srcAudioBuffer = new byte[m_srcAudioBufferSize];
+ m_srcAudioBufferCurrentSize = 0;
+ }
+
+ if ( m_SampleGrouping == AG_FIXED_SIZE )
+ {
+ // Set up to emit audio after fixed number of samples
+ m_nAudioSampleGroupSize = audioSampleGroupSize;
+
+ }
+
+ if ( m_SampleGrouping == AG_PER_FRAME )
+ {
+ m_AudioSampleFrameCounter = 0;
+ }
+
+ m_bSourceAudioConfigured = true;
+ }
+
+#ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Audio Sample Grouping Mode = %d\nSample Group Size = %d \n", (int) m_SampleGrouping, m_nAudioSampleGroupSize );
+ LogMsg( "Audio Track Sample Rate is %d samples per second\n", m_AudioSourceFrequency );
+ LogMsg( "Estimated Samples per frame = %d\n\n", (int) ( m_AudioSourceFrequency / m_MovieRecordFPS.GetFPS() ) );
+#endif
+
+ // finish up
+ SetResult( VideoResult::SUCCESS );
+
+ return CheckForReadyness(); // We are ready to go if video is...
+}
+
+
+// Returns true if we are not ready, or if we began movie creation successfully
+// This ONLY returns false if it tried to begin the movie creation process and failed
+bool CQTVideoFileComposer::CheckForReadyness()
+{
+ return ( m_bMovieCreated && !m_bMovieCompleted && !m_bComposingMovie && m_bMovieConfigured && m_bSourceImagesConfigured &&
+ m_bSourceAudioConfigured == m_bHasAudioTrack ) ? BeginMovieCreation() : true;
+}
+
+
+bool CQTVideoFileComposer::BeginMovieCreation()
+{
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( m_bMovieCreated && !m_bMovieCompleted && !m_bComposingMovie &&
+ m_bMovieConfigured && m_bSourceImagesConfigured && m_bSourceAudioConfigured == m_bHasAudioTrack );
+
+ // Open the tracks up for editing
+ SetResult( VideoResult::VIDEO_ERROR_OCCURED );
+ OSErr status = BeginMediaEdits( m_theVideoMedia );
+ AssertExitF( status == noErr );
+
+ if ( m_bHasAudioTrack )
+ {
+ OSErr status = BeginMediaEdits( m_theAudioMedia );
+ AssertExitF( status == noErr );
+ }
+
+
+#ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "Media Tracks opened for editing\n\n" );
+#endif
+
+
+ // We are now ready to take in data to make a movie with
+ SetResult( VideoResult::SUCCESS );
+ m_bComposingMovie = true;
+
+ return true;
+}
+
+
+bool CQTVideoFileComposer::AppendVideoFrameToMedia( void *ImageBuffer, int strideAdjustBytes )
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "AppendVideoFrameToMedia( %8.8x ) called for %d --- ", ImageBuffer, m_nFramesAdded+1 );
+#endif
+
+ // Validate input and state
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( ImageBuffer != nullptr );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( m_bComposingMovie && !m_bMovieCompleted );
+
+ SetResult( VideoResult::VIDEO_ERROR_OCCURED );
+
+ // Get the pixmap
+ PixMapHandle thePixMap = GetGWorldPixMap( m_theSrcGWorld );
+ AssertPtrExitF( thePixMap );
+
+ // copy the raw image into our bitmap
+ AssertExitF( LockPixels( thePixMap ) );
+
+ byte *srcBase = (byte*) ImageBuffer;
+ int srcStride = m_SrcImageWidth * m_SrcBytesPerPixel + strideAdjustBytes;
+
+ byte *dstBase = nullptr;
+ int dstStride = 0;
+
+#if defined ( WIN32 )
+ // Get the HBITMAP of our GWorld
+ HBITMAP theHBITMAP = (HBITMAP) GetPortHBITMAP( (GrafPtr) m_theSrcGWorld );
+
+ // retrieve the bitmap info header information
+ BITMAP bmp;
+ AssertExitF( GetObject( theHBITMAP, sizeof(BITMAP), (LPSTR) &bmp) );
+
+ // validate the BMP we just got
+ AssertExitF( bmp.bmWidth == m_SrcImageWidth && bmp.bmHeight == m_SrcImageHeight && bmp.bmBitsPixel == 8 * m_GWorldBytesPerPixel );
+
+ // setup the pixel copy info
+ dstBase = (byte*)bmp.bmBits;
+ dstStride = bmp.bmWidthBytes;
+
+#elif defined ( OSX )
+ // setup the pixel copy info
+ dstBase = (byte*) GetPixBaseAddr( thePixMap );
+ dstStride = GetPixRowBytes( thePixMap );
+
+#endif
+
+ AssertExitF( dstBase != nullptr && dstStride > 0 );
+
+ // save a TGA if we are running diagnostics
+#if defined ( LOG_FRAMES_TO_TGA )
+
+ if ( ( m_nFramesAdded % LOG_FRAMES_TO_TGA_INTERVAL ) == 0 )
+ {
+ SaveToTargaFile( m_nFramesAdded, m_TGAFileBase, m_SrcImageWidth, m_SrcImageHeight, srcBase, m_SrcPixelFormat, strideAdjustBytes );
+ }
+
+#endif
+
+ // copy the supplied pixel buffer into our GWORLD data
+ if ( !CopyBitMapPixels( m_SrcImageWidth, m_SrcImageHeight,
+ m_SrcPixelFormat, srcBase, srcStride,
+ m_GWorldPixelFormat, dstBase, dstStride ) )
+ {
+ Assert( false );
+ return false;
+ }
+
+ // You are now free to move about the cabin...
+ UnlockPixels( thePixMap );
+
+ // allocate a handle which CompressImage will resize...
+ ImageDescriptionHandle theImageDescHandle = (ImageDescriptionHandle) NewHandle( sizeof(ImageDescriptionHandle) );
+ AssertExitF( theImageDescHandle != nullptr );
+
+ // compress the single image
+ OSErr status = CompressImage( thePixMap, &m_GWorldRect, m_EncodeQuality,
+ m_VideoCodecToUse, theImageDescHandle, *m_SrcImageCompressedBuffer );
+ if ( status != noErr )
+ {
+ Assert( false ); // tell the user
+ SAFE_DISPOSE_HANDLE( theImageDescHandle );
+ return false;
+ }
+
+ TimeValue addedTime = 0;
+
+ // Lets add gamma info the image description
+ if ( m_EncodeGamma != kQTUseSourceGammaLevel )
+ {
+ Fixed newGamma = m_EncodeGamma;
+ status = ICMImageDescriptionSetProperty( theImageDescHandle, kQTPropertyClass_ImageDescription, kICMImageDescriptionPropertyID_GammaLevel, sizeof( newGamma ), &newGamma );
+ AssertExitF( status == noErr );
+ }
+
+ // add the compressed image to the movie stream
+ status = AddMediaSample( m_theVideoMedia, m_SrcImageCompressedBuffer, 0, (**theImageDescHandle).dataSize, m_DurationPerFrame,
+ (SampleDescriptionHandle) theImageDescHandle, 1, 0, &addedTime );
+
+ if ( status != noErr )
+ {
+ Assert( false ); // tell the user
+ SAFE_DISPOSE_HANDLE( theImageDescHandle );
+ return false;
+ }
+
+
+#ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "Video Frame %d added to Video Media: Duration = %d, Inserted at Time %d\n", m_nFramesAdded+1, m_DurationPerFrame, addedTime );
+#endif
+
+ // free up dynamic resources
+ SAFE_DISPOSE_HANDLE( theImageDescHandle );
+
+ // Report success
+ SetResult( VideoResult::SUCCESS );
+ m_nFramesAdded++;
+
+ return true;
+}
+
+
+
+int CQTVideoFileComposer::GetAudioSampleCountThruFrame( int frameNo )
+{
+ if ( frameNo < 1 )
+ {
+ return 0;
+ }
+
+ double secondsSoFar = (double) ( frameNo * m_DurationPerFrame ) / (double) m_MovieTimeScale;
+ int nAudioSamples = (int) floor( secondsSoFar * (double) m_AudioSourceFrequency );
+
+ return nAudioSamples;
+}
+
+
+
+bool CQTVideoFileComposer::AppendAudioSamplesToMedia( void *soundBuffer, size_t bufferSize )
+{
+ SetResult( VideoResult::ILLEGAL_OPERATION );
+ AssertExitF( m_bHasAudioTrack );
+
+ // Validate input and state
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( soundBuffer != nullptr && bufferSize % m_AudioBytesPerSample == 0 );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( m_bComposingMovie && !m_bMovieCompleted );
+
+ int nSamples = bufferSize / m_AudioBytesPerSample;
+ Assert( bufferSize % m_AudioBytesPerSample == 0 );
+
+ TimeValue64 insertTime64 = 0;
+ OSErr status = noErr;
+
+ bool retest;
+ int samplesToEmit, MaxCanAdd, nSamplesAvailable;
+
+ m_nSamplesAdded+= nSamples; // track samples given to encoder
+
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "%d Audio Samples Submitted (%d total) -- ", nSamples, m_nSamplesAdded );
+ #endif
+
+ // We can pass in 0 bytes to trigger a flush...
+ if ( nSamples == 0 )
+ {
+ if ( !m_bBufferSourceAudio || m_srcAudioBufferCurrentSize == 0 )
+ {
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "NO SAMPLES TO PROCESS. EXIT\n" );
+ #endif
+ goto finish_up;
+ }
+ }
+
+ // Are we not buffering audio?
+ if ( !m_bBufferSourceAudio && nSamples > 0 )
+ {
+ SetResult( VideoResult::AUDIO_ERROR_OCCURED );
+ status = AddMediaSample2( m_theAudioMedia, (const UInt8 *) soundBuffer, (ByteCount) bufferSize,
+ (TimeValue64) 1, (TimeValue64) 0, (SampleDescriptionHandle) m_srcSoundDescription,
+ (ItemCount) nSamples, 0, &insertTime64 );
+
+ AssertExitF( status == noErr );
+
+ m_nSamplesAddedToMedia+= nSamples;
+
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "%d samples (%d total) inserted into Video at time %ld\n", nSamples, m_nSamplesAddedToMedia, insertTime64 );
+ #endif
+
+ goto finish_up;
+ }
+
+ // Buffering audio ....
+ if ( nSamples > 0 )
+ {
+ memaddr_t pSrc = (memaddr_t) soundBuffer;
+ size_t bytesToCopy = nSamples * m_AudioBytesPerSample;
+
+ // Is buffer big enough to hold it all?
+ if ( m_srcAudioBufferCurrentSize + bytesToCopy > m_srcAudioBufferSize )
+ {
+ // get a bigger buffer
+ size_t newBufferSize = m_srcAudioBufferSize * 2 + bytesToCopy;
+ byte *newBuffer = new byte[newBufferSize];
+
+ // copy buffered sound and swap out buffers
+ V_memcpy( newBuffer, m_srcAudioBuffer, m_srcAudioBufferCurrentSize );
+
+ delete[] m_srcAudioBuffer;
+ m_srcAudioBuffer = newBuffer;
+ m_srcAudioBufferSize = newBufferSize;
+ }
+
+ // Append samples to buffer
+ V_memcpy( m_srcAudioBuffer + m_srcAudioBufferCurrentSize, pSrc, bytesToCopy );
+ m_srcAudioBufferCurrentSize += bytesToCopy;
+ }
+
+#ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "%d Samples buffered. Buffer=%d Samples -- ", nSamples, ( m_srcAudioBufferCurrentSize / m_AudioBytesPerSample ) );
+#endif
+
+retest_here:
+ nSamplesAvailable = m_srcAudioBufferCurrentSize / m_AudioBytesPerSample;
+ samplesToEmit = 0;
+ retest = false;
+ MaxCanAdd = ( m_bLimitAudioDurationToVideo ) ? ( GetAudioSampleCountThruFrame( m_nFramesAdded ) - m_nSamplesAddedToMedia ) : INT32_MAX;
+
+ if ( MaxCanAdd <= 0 )
+ {
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Can't Add Audio Now.\n" );
+ #endif
+ goto finish_up;
+ }
+
+ // Now.. we determine if we are ready to insert the audio samples into the media..and if so, how much...
+
+ if ( m_SampleGrouping == AG_NONE )
+ {
+ // are we keeping audio from getting ahead of video?
+ Assert( m_bLimitAudioDurationToVideo );
+
+ samplesToEmit = MIN( MaxCanAdd, nSamplesAvailable );
+ }
+ else if ( m_SampleGrouping == AG_FIXED_SIZE )
+ {
+ // do we have enough to emit a sample?
+ if ( ( nSamplesAvailable < m_nAudioSampleGroupSize ) || ( m_bLimitAudioDurationToVideo && ( MaxCanAdd < m_nAudioSampleGroupSize ) ) )
+ {
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ if ( nSamplesAvailable < m_nAudioSampleGroupSize )
+ LogMsg( "Need %d Samples to emit sample group\n", m_nAudioSampleGroupSize );
+ else
+ LogMsg( "Audio is caught up to Video (can add %d) \n", MaxCanAdd );
+ #endif
+ goto finish_up;
+ }
+
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "emitting 1 group of audio (%d samples)\n", m_nAudioSampleGroupSize );
+ #endif
+
+ samplesToEmit = m_nAudioSampleGroupSize;
+ retest = true;
+ }
+ else if ( m_SampleGrouping == AG_PER_FRAME )
+ {
+ // is the audio already caught up with the current video frame?
+ if ( m_bLimitAudioDurationToVideo && m_AudioSampleFrameCounter >= m_nFramesAdded )
+ {
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Audio is caught up to Video\n" );
+ #endif
+ goto finish_up;
+ }
+
+ int curSampleCount = GetAudioSampleCountThruFrame( m_AudioSampleFrameCounter );
+ int nextSampleCount = GetAudioSampleCountThruFrame( m_AudioSampleFrameCounter+1 );
+ int thisGroupSize = nextSampleCount - curSampleCount;
+
+ Assert( m_nSamplesAddedToMedia == curSampleCount );
+
+ if ( nSamplesAvailable < thisGroupSize )
+ {
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Not enough samples to fill video frame (need %d)\n", thisGroupSize );
+ #endif
+ goto finish_up;
+ }
+
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "emitting 1 video frame of audio (%d samples)\n", thisGroupSize );
+ #endif
+
+ samplesToEmit = thisGroupSize;
+ m_AudioSampleFrameCounter++;
+ retest = true;
+ }
+ else
+ {
+ Assert( false );
+ }
+
+ if ( samplesToEmit > 0 )
+ {
+ SetResult( VideoResult::AUDIO_ERROR_OCCURED );
+
+ status = AddMediaSample2( m_theAudioMedia, (const UInt8 *) m_srcAudioBuffer, (ByteCount) samplesToEmit * m_AudioBytesPerSample,
+ (TimeValue64) 1, (TimeValue64) 0, (SampleDescriptionHandle) m_srcSoundDescription,
+ (ItemCount) samplesToEmit, 0, &insertTime64 );
+
+ AssertExitF( status == noErr );
+
+ m_nSamplesAddedToMedia+= samplesToEmit;
+
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "%d samples inserted into Video (%d total) at time %ld -- ", samplesToEmit, m_nSamplesAddedToMedia, insertTime64 );
+ #endif
+
+ // remove added samples from sound buffer
+ // (this really should be a circular buffer.. but that has its own problems)
+
+ m_srcAudioBufferCurrentSize -= samplesToEmit * m_AudioBytesPerSample;
+ nSamplesAvailable -= samplesToEmit;
+
+ if ( nSamplesAvailable > 0 )
+ {
+ V_memcpy( m_srcAudioBuffer, m_srcAudioBuffer + (samplesToEmit * m_AudioBytesPerSample), nSamplesAvailable * m_AudioBytesPerSample );
+ }
+
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Buffer now =%d samples", nSamplesAvailable );
+ #endif
+
+ if ( retest )
+ {
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( " -- rechecking -- " );
+ #endif
+ goto retest_here;
+ }
+ }
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "\n" );
+ #endif
+
+
+finish_up:
+ // Report success
+ SetResult( VideoResult::SUCCESS );
+
+ return true;
+}
+
+
+
+bool CQTVideoFileComposer::SyncAndFlushAudio()
+{
+ if ( !m_bHasAudioTrack )
+ {
+ return false;
+ }
+
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Resolving Audio Track...\n" );
+ #endif
+
+restart_sync:
+ bool bPadWithSilence = BITFLAGS_SET( m_AudioOptions, AudioEncodeOptions::PAD_AUDIO_WITH_SILENCE );
+ int VideoDurationInSamples = GetAudioSampleCountThruFrame( m_nFramesAdded );
+ int CurShortfall = VideoDurationInSamples - m_nSamplesAddedToMedia;
+
+ int SilenceToEmit = 0;
+ bool forceFlush = false;
+ bool forcePartialGroupFlush = false;
+ int nSamplesInBuffer = ( m_bBufferSourceAudio ) ? m_srcAudioBufferCurrentSize / m_AudioBytesPerSample : 0;
+
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Video duration is %d frames, which is %d Audio Samples\n", m_nFramesAdded, VideoDurationInSamples );
+ LogMsg( "%d Samples emitted to Audio track so far. %d samples remain in audio buffer\n", m_nSamplesAddedToMedia, nSamplesInBuffer );
+ LogMsg( "Delta to sync end of audio to end of video is %d Samples\n", CurShortfall );
+ LogMsg( "Pad With Silence Mode = %d, Align End of Audio With Video Mode = %d\n", (int) bPadWithSilence, (int) m_bLimitAudioDurationToVideo );
+ #endif
+
+ // not grouping samples mode
+ if ( m_SampleGrouping == AG_NONE )
+ {
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "No sample grouping Mode\n" );
+ #endif
+
+ Assert( m_bLimitAudioDurationToVideo == m_bBufferSourceAudio ); // if we're not limiting, we're not buffering
+
+ if ( m_bLimitAudioDurationToVideo && CurShortfall > 0 && bPadWithSilence )
+ {
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Padding with %d samples to match video duration", CurShortfall );
+ #endif
+
+ SilenceToEmit = CurShortfall; // pad with silence
+ }
+
+ if ( nSamplesInBuffer > 0 || SilenceToEmit > 0 ) // force if we have something to add
+ {
+ forceFlush = true;
+ }
+ }
+
+ // Fixed sized grouping (and buffering)
+ if ( m_SampleGrouping == AG_FIXED_SIZE )
+ {
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Fixed sample grouping mode. Group size of %d\n", m_nAudioSampleGroupSize );
+ #endif
+
+ // No matter what, if we have a partially filled buffer, we add silence to it to make a complete group
+ // do we have a partially full buffer? if so pad with silence to make a full group
+ if ( nSamplesInBuffer > 0 && nSamplesInBuffer % m_nAudioSampleGroupSize != 0 )
+ {
+ SilenceToEmit = m_nAudioSampleGroupSize - ( nSamplesInBuffer % m_nAudioSampleGroupSize );
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Adding %d silence samples to complete group\n", SilenceToEmit );
+ #endif
+
+ }
+
+ int bufferedSamples = nSamplesInBuffer + SilenceToEmit;
+ int newShortFall = VideoDurationInSamples - m_nSamplesAddedToMedia - bufferedSamples;
+
+ if ( bPadWithSilence && newShortFall > 0 )
+ {
+ SilenceToEmit += newShortFall; // pad with silence until audio matches video duration
+ forceFlush = true;
+ forcePartialGroupFlush = true;
+
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Adding %d silence samples to pad to match audio to video duration\n", newShortFall );
+ #endif
+ }
+ }
+
+ if ( m_SampleGrouping == AG_PER_FRAME )
+ {
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Video Frame duraiton Audio grouping mode\n" );
+ #endif
+
+ // Have we already enough audio to match the video
+ if ( m_bLimitAudioDurationToVideo && m_AudioSampleFrameCounter >= m_nFramesAdded )
+ {
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Audio is caught up to Video\n" );
+ #endif
+ goto audio_complete;
+ }
+
+ // if we have anything in the buffer... pad it out with zeros
+ if ( nSamplesInBuffer > 0 )
+ {
+ // get the group size for the video frame the audio is currently on
+ int thisGroupSize = GetAudioSampleCountThruFrame( m_AudioSampleFrameCounter+1 ) - GetAudioSampleCountThruFrame( m_AudioSampleFrameCounter );
+ Assert( m_nSamplesAddedToMedia == GetAudioSampleCountThruFrame( m_AudioSampleFrameCounter ) );
+
+ // if we already have 1 (or more) groups in the buffer.. emit them, and restart
+ if ( nSamplesInBuffer >= thisGroupSize )
+ {
+ char n = nullchar;
+ AppendAudioSamplesToMedia( &n, 0 );
+ goto restart_sync;
+ }
+
+ SilenceToEmit = thisGroupSize - nSamplesInBuffer;
+ forceFlush = true;
+
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Adding %d silence samples to pad current group to match video frame duration\n", SilenceToEmit );
+ #endif
+
+ }
+
+ // with the output being aligned to a video frame, do we need to add more to pad to end of the video
+
+ int bufferedSamples = nSamplesInBuffer + SilenceToEmit;
+ int newShortFall = VideoDurationInSamples - m_nSamplesAddedToMedia - bufferedSamples;
+ if ( bPadWithSilence && newShortFall > 0 )
+ {
+ SilenceToEmit += newShortFall; // pad with silence until audio matches video duration
+ forceFlush = true;
+ forcePartialGroupFlush = true;
+
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Adding %d silence samples to pad audio to match video duration", newShortFall );
+ #endif
+ }
+
+ }
+
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "\n" );
+ #endif
+
+ // now we append any needed silence to the audio stream...
+ if ( SilenceToEmit > 0 )
+ {
+ int bufferSize = SilenceToEmit * m_AudioBytesPerSample;
+ byte *pSilenceBuf = new byte[ bufferSize ];
+ V_memset( pSilenceBuf, nullchar, bufferSize );
+
+ #ifdef LOG_ENCODER_AUDIO_OPERATIONS
+ LogMsg( "Appending %d Silence samples\n", SilenceToEmit );
+ #endif
+
+ AppendAudioSamplesToMedia( pSilenceBuf, bufferSize );
+ }
+ else
+ {
+ if ( forceFlush )
+ {
+ char n = nullchar;
+ AppendAudioSamplesToMedia( &n, 0 );
+ }
+ }
+
+ if ( forcePartialGroupFlush && m_srcAudioBufferCurrentSize >0 )
+ {
+ int nSamplesThisAdd = m_srcAudioBufferCurrentSize / m_AudioBytesPerSample;
+ TimeValue64 insertTime64 = 0;
+
+ OSErr status = AddMediaSample2( m_theAudioMedia, (const UInt8 *) m_srcAudioBuffer, (ByteCount) m_srcAudioBufferCurrentSize,
+ (TimeValue64) 1, (TimeValue64) 0, (SampleDescriptionHandle) m_srcSoundDescription,
+ (ItemCount) nSamplesThisAdd, 0, &insertTime64 );
+
+ AssertExitF( status == noErr );
+
+ m_srcAudioBufferCurrentSize = 0;
+ m_nSamplesAddedToMedia+= nSamplesThisAdd;
+
+ #ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "FORCED FLUSH - Audio Samples added to media. %d added, %d total samples, inserted at time %ld\n", nSamplesThisAdd, m_nSamplesAddedToMedia, insertTime64 );
+ #endif
+ }
+
+
+audio_complete:
+
+ return true;
+}
+
+
+bool CQTVideoFileComposer::EndMovieCreation( bool saveMovieData )
+{
+ #ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "\nEndMovieCreation Called Composing=%d Completed=%d\n\n", (int) m_bComposingMovie, (int) m_bMovieCompleted );
+ #endif
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( m_bComposingMovie && !m_bMovieCompleted );
+
+ #ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "\nEndMovieCreation Called\n\n" );
+ #endif
+
+ // Stop adding to and (optionally) save the video media into the file
+ SetResult( VideoResult::VIDEO_ERROR_OCCURED );
+ if ( m_nFramesAdded > 0 )
+ {
+ TimeValue VideoDuration = GetMediaDuration( m_theVideoMedia );
+ AssertExitF( VideoDuration == m_nFramesAdded * m_DurationPerFrame );
+
+ OSErr status = EndMediaEdits( m_theVideoMedia );
+ AssertExitF( status == noErr );
+
+ if ( saveMovieData )
+ {
+ status = InsertMediaIntoTrack( m_theVideoTrack, 0, 0, VideoDuration, fixed1 );
+ Assert( status == noErr );
+
+ #ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "\nVideo Media inserted into Track\n" );
+ #endif
+
+ }
+ }
+
+ // Stop adding to and (optionally) save the audio media into the file
+ SetResult( VideoResult::AUDIO_ERROR_OCCURED );
+ if ( m_bHasAudioTrack && m_nSamplesAdded > 0 )
+ {
+ // flush any remaining samples in the buffer to the media
+
+ #ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "Calling SyncAndFlushAudio()\n" );
+ #endif
+
+ SyncAndFlushAudio();
+
+ TimeValue AudioDuration = GetMediaDuration( m_theAudioMedia );
+ #ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "Audio Duration = %d nSamples Added = %d\n", AudioDuration, m_nSamplesAdded );
+ #endif
+ // AssertExitF( AudioDuration == m_nSamplesAdded );
+
+ OSErr status = EndMediaEdits( m_theAudioMedia );
+ AssertExitF( status == noErr );
+
+ if ( saveMovieData )
+ {
+ status = InsertMediaIntoTrack( m_theAudioTrack, 0, 0, AudioDuration, fixed1 );
+ AssertExitF( status == noErr );
+
+ #ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "\nAudio Media inserted into Track\n" );
+ #endif
+ }
+ }
+
+ if ( saveMovieData )
+ {
+
+#ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "Saving Movie Data...\n" );
+#endif
+
+ SetResult( VideoResult::FILE_ERROR_OCCURED );
+ OSErr status = AddMovieToStorage( m_theMovie, m_MovieFileDataHandler );
+ AssertExitF( status == noErr );
+ if ( status != noErr )
+ {
+ DataHDeleteFile( m_MovieFileDataHandler );
+ }
+
+ #ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "\nMovie Resource added to file. Returned Status = %d\n", (int) status );
+ #endif
+ }
+
+ // free our resources
+ if ( m_MovieFileDataHandler != nullptr )
+ {
+ OSErr status = CloseMovieStorage( m_MovieFileDataHandler );
+ m_MovieFileDataHandler = nullptr;
+ AssertExitF( status == noErr );
+ }
+
+ SAFE_DISPOSE_HANDLE( m_MovieFileDataRef );
+ SAFE_DISPOSE_HANDLE( m_SrcImageCompressedBuffer );
+ SAFE_DISPOSE_GWORLD( m_theSrcGWorld );
+ SAFE_DISPOSE_HANDLE( m_srcSoundDescription );
+
+ SetResult( VideoResult::SUCCESS );
+ m_bComposingMovie = false;
+ return true;
+}
+
+
+// The movie can be aborted at any time before completion
+bool CQTVideoFileComposer::AbortMovie()
+{
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( !m_bMovieCompleted );
+
+ // Shut down the movie if we are recording
+ if ( m_bComposingMovie )
+ {
+ if ( !EndMovieCreation( false ) )
+ {
+ return false;
+ }
+ }
+
+ if ( m_bMovieCreated )
+ {
+ if ( m_MovieFileDataHandler != nullptr )
+ {
+ SetResult( VideoResult::FILE_ERROR_OCCURED );
+ OSErr status = CloseMovieStorage( m_MovieFileDataHandler );
+ AssertExitF( status == noErr );
+ m_MovieFileDataHandler = nullptr;
+ }
+
+ SAFE_DISPOSE_HANDLE( m_MovieFileDataRef );
+ SAFE_DISPOSE_MOVIE( m_theMovie );
+ }
+
+ if ( m_FileName )
+ {
+ g_pFullFileSystem->RemoveFile( m_FileName );
+ }
+
+ SetResult( VideoResult::SUCCESS );
+ m_bMovieCompleted = true;
+
+ return true;
+}
+
+
+
+bool CQTVideoFileComposer::FinishMovie( bool SaveMovieToDisk )
+{
+ #ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "\nFinish Movie Called\n" );
+ #endif
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( m_bComposingMovie && !m_bMovieCompleted );
+
+ // Shutdown movie creation
+ if ( !EndMovieCreation( SaveMovieToDisk ) )
+ {
+ #ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "\nEndMovieCreation Aborted\n" );
+ #endif
+ return false;
+ }
+
+ // todo: check on Disposing of theMovie and theMedia
+ if ( m_MovieFileDataHandler != nullptr )
+ {
+ SetResult( VideoResult::FILE_ERROR_OCCURED );
+ OSErr status = CloseMovieStorage( m_MovieFileDataHandler );
+ AssertExitF( status == noErr );
+ m_MovieFileDataHandler = nullptr;
+
+#ifdef LOG_ENCODER_OPERATIONS
+ LogMsg( "Movie File Closed\n" );
+#endif
+
+ }
+
+ SAFE_DISPOSE_HANDLE( m_MovieFileDataRef );
+ SAFE_DISPOSE_MOVIE( m_theMovie );
+
+ // if no frames have been added.. delete files
+ if ( SaveMovieToDisk == false || ( m_nFramesAdded <= 0 && m_nSamplesAdded <= 0) )
+ {
+ g_pFullFileSystem->RemoveFile( m_FileName );
+ }
+
+ SetResult( VideoResult::SUCCESS );
+ m_bMovieCompleted = true;
+
+#ifdef LOG_ENCODER_OPERATIONS
+ g_pFullFileSystem->Close( m_LogFile );
+ m_LogFile = FILESYSTEM_INVALID_HANDLE;
+#endif
+
+
+ return true;
+}
+
+
+
+void CQTVideoFileComposer::SetResult( VideoResult_t status )
+{
+ m_LastResult = status;
+}
+
+VideoResult_t CQTVideoFileComposer::GetResult()
+{
+ return m_LastResult;
+}
+
+bool CQTVideoFileComposer::IsReadyToRecord()
+{
+ return ( m_bComposingMovie && !m_bMovieCompleted );
+}
+
+
+#ifdef ENABLE_EXTERNAL_ENCODER_LOGGING
+bool CQTVideoFileComposer::LogMessage( const char *msg )
+{
+
+#ifdef LOG_ENCODER_OPERATIONS
+ if ( IS_NOT_EMPTY(msg) && m_LogFile != FILESYSTEM_INVALID_HANDLE )
+ {
+ g_pFullFileSystem->Write( msg, V_strlen( msg ), m_LogFile );
+ }
+#endif
+ return true;
+}
+#endif
diff --git a/video/quicktime_recorder.h b/video/quicktime_recorder.h
new file mode 100644
index 0000000..85aef0a
--- /dev/null
+++ b/video/quicktime_recorder.h
@@ -0,0 +1,263 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//----------------------------------------------------------------------------------------
+
+#ifndef QUICKTIME_RECORDER_H
+#define QUICKTIME_RECORDER_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+//--------------------------------------------------------------------------------
+
+#if defined( OSX )
+ #include <quicktime/QTML.h>
+ #include <quicktime/Movies.h>
+ #include <quicktime/QuickTimeComponents.h>
+#elif defined( WIN32 )
+ #include "QTML.h"
+ #include "Movies.h"
+ #include "QuickTimeComponents.h"
+#else
+ #error "Quicktime encoding is not supported on this platform"
+#endif
+
+#include "video/ivideoservices.h"
+
+#include "video_macros.h"
+#include "quicktime_common.h"
+
+
+// comment out to prevent logging of creation data
+//#define LOG_ENCODER_OPERATIONS
+//#define LOG_ENCODER_AUDIO_OPERATIONS
+
+// comment out to log images of frames
+//#define LOG_FRAMES_TO_TGA
+//#define LOG_FRAMES_TO_TGA_INTERVAL 16
+
+#if defined( LOG_ENCODER_OPERATIONS ) || defined( LOG_ENCODER_AUDIO_OPERATIONS ) || defined ( LOG_FRAMES_TO_TGA ) || defined ( ENABLE_EXTERNAL_ENCODER_LOGGING )
+ #include <filesystem.h>
+#endif
+
+
+
+// ------------------------------------------------------------------------
+// CQTVideoFileComposer
+// Class to manages to creation of a video file from a series of
+// supplied bitmap images.
+//
+// At this time, the time interval between frames must be the same for all
+// frames of the movie
+// ------------------------------------------------------------------------
+class CQTVideoFileComposer
+{
+ public:
+ static const CodecType DEFAULT_CODEC = kH264CodecType;
+ static const CodecQ DEFAULT_ENCODE_QUALITY = codecNormalQuality;
+ static const Fixed DEFAULT_GAMMA = kQTUseSourceGammaLevel;
+
+ static const int MIN_AUDIO_SAMPLE_GROUP_SIZE = 64;
+ static const int MAX_AUDIO_GROUP_SIZE_IN_SEC = 4;
+
+ CQTVideoFileComposer();
+ ~CQTVideoFileComposer();
+
+ bool CreateNewMovie( const char *fileName, bool hasAudio );
+
+ bool SetMovieVideoParameters( int width, int height, VideoFrameRate_t movieFPS, VideoEncodeCodec_t desiredCodec, int encodeQuality, VideoEncodeGamma_t gamma );
+ bool SetMovieSourceImageParameters( int srcWidth, int srcHeight, VideoEncodeSourceFormat_t srcImageFormat );
+ bool SetMovieSourceAudioParameters( AudioEncodeSourceFormat_t srcAudioFormat = AudioEncodeSourceFormat::AUDIO_NONE, int audioSampleRate = 0, AudioEncodeOptions_t audioOptions = AudioEncodeOptions::NO_AUDIO_OPTIONS, int audioSampleGroupSize = 0 );
+
+ bool AppendVideoFrameToMedia( void *ImageBuffer, int strideAdjustBytes );
+ bool AppendAudioSamplesToMedia( void *soundBuffer, size_t bufferSize );
+
+ bool AbortMovie();
+ bool FinishMovie( bool SaveMovieToDisk = true );
+
+ size_t GetFrameBufferSize() { return m_SrcImageSize; }
+
+ bool CheckFilename( const char *filename );
+
+ void SetResult( VideoResult_t status );
+ VideoResult_t GetResult();
+ bool IsReadyToRecord();
+
+ int GetFrameCount() { return m_nFramesAdded; }
+ int GetSampleCount() { return m_nSamplesAdded; }
+
+ VideoFrameRate_t GetFPS() { return m_MovieRecordFPS; }
+ int GetSampleRate() { return m_AudioSourceFrequency; }
+
+ bool HasAudio() { return m_bHasAudioTrack; }
+
+#ifdef ENABLE_EXTERNAL_ENCODER_LOGGING
+ bool LogMessage( const char *msg );
+#endif
+
+ private:
+ enum AudioGrouping_t
+ {
+ AG_NONE = 0,
+ AG_FIXED_SIZE,
+ AG_PER_FRAME
+ };
+
+ // disable copy constructor and copy assignment
+ CQTVideoFileComposer( CQTVideoFileComposer &rhs );
+ CQTVideoFileComposer& operator= ( CQTVideoFileComposer &rhs );
+
+ bool CheckForReadyness();
+ bool BeginMovieCreation();
+ bool EndMovieCreation( bool saveMovieData );
+
+ bool SyncAndFlushAudio();
+ int GetAudioSampleCountThruFrame( int frameNo );
+
+ VideoResult_t m_LastResult;
+
+ // Current State of Movie Creation;
+ bool m_bMovieCreated;
+ bool m_bHasAudioTrack;
+
+ bool m_bMovieConfigured;
+ bool m_bSourceImagesConfigured;
+ bool m_bSourceAudioConfigured;
+
+ bool m_bComposingMovie;
+ bool m_bMovieCompleted;
+
+ int m_nFramesAdded;
+
+ int m_nAudioFramesAdded;
+ int m_nSamplesAdded;
+ int m_nSamplesAddedToMedia;
+
+ // parameters of the movie to create;
+ int m_MovieFrameWidth;
+ int m_MovieFrameHeight;
+
+ VideoFrameRate_t m_MovieRecordFPS;
+ TimeScale m_MovieTimeScale;
+ TimeValue m_DurationPerFrame;
+
+ // Audio recording options
+ AudioEncodeOptions_t m_AudioOptions; // Option flags specifed by user
+ AudioGrouping_t m_SampleGrouping; // Mode to group samples
+ int m_nAudioSampleGroupSize; // number of samples to collect per sample group
+
+ int m_AudioSourceFrequency; // Source frequency of the supplied audio
+ int m_AudioBytesPerSample;
+
+ bool m_bBufferSourceAudio;
+ bool m_bLimitAudioDurationToVideo;
+
+ byte *m_srcAudioBuffer; // buffer to hold audio samples
+ size_t m_srcAudioBufferSize;
+ size_t m_srcAudioBufferCurrentSize;
+
+ int m_AudioSampleFrameCounter;
+
+ char *m_FileName;
+
+ int m_SrcImageWidth;
+ int m_SrcImageHeight;
+ size_t m_SrcImageSize;
+ int m_ScrImageMaxCompressedSize;
+ byte *m_SrcImageBuffer;
+ Handle m_SrcImageCompressedBuffer;
+ OSType m_SrcPixelFormat;
+ int m_SrcBytesPerPixel;
+
+ OSType m_GWorldPixelFormat;
+ int m_GWorldBytesPerPixel;
+ int m_GWorldImageWidth;
+ int m_GWorldImageHeight;
+
+ Handle m_srcSoundDescription;
+
+
+ // parameters used by QuickTime
+ CodecQ m_EncodeQuality;
+ CodecType m_VideoCodecToUse;
+ Fixed m_EncodeGamma;
+
+ Rect m_GWorldRect;
+ GWorldPtr m_theSrcGWorld;
+
+// short m_ResRefNum; // QuickTime Movie Resource Ref number
+
+ Handle m_MovieFileDataRef;
+ OSType m_MovieFileDataRefType;
+ DataHandler m_MovieFileDataHandler;
+
+
+ Movie m_theMovie;
+ Track m_theVideoTrack;
+ Track m_theAudioTrack;
+ Media m_theVideoMedia;
+ Media m_theAudioMedia;
+
+#ifdef LOG_ENCODER_OPERATIONS
+ FileHandle_t m_LogFile;
+ void LogMsg( PRINTF_FORMAT_STRING const char* pMsg, ... );
+#endif
+
+#ifdef LOG_FRAMES_TO_TGA
+ char m_TGAFileBase[MAX_PATH];
+#endif
+
+
+};
+
+
+
+class CQuickTimeVideoRecorder : public IVideoRecorder
+{
+ public:
+ CQuickTimeVideoRecorder();
+ ~CQuickTimeVideoRecorder();
+
+ virtual bool EstimateMovieFileSize( size_t *pEstSize, int movieWidth, int movieHeight, VideoFrameRate_t movieFps, float movieDuration, VideoEncodeCodec_t theCodec, int videoQuality, AudioEncodeSourceFormat_t srcAudioFormat = AudioEncodeSourceFormat::AUDIO_NONE, int audioSampleRate = 0 );
+
+ virtual bool CreateNewMovieFile( const char *pFilename, bool hasAudioTrack = false );
+
+ virtual bool SetMovieVideoParameters( VideoEncodeCodec_t theCodec, int videoQuality, int movieFrameWidth, int movieFrameHeight, VideoFrameRate_t movieFPS, VideoEncodeGamma_t gamma = VideoEncodeGamma::NO_GAMMA_ADJUST );
+ virtual bool SetMovieSourceImageParameters( VideoEncodeSourceFormat_t srcImageFormat, int imgWidth, int imgHeight );
+ virtual bool SetMovieSourceAudioParameters( AudioEncodeSourceFormat_t srcAudioFormat = AudioEncodeSourceFormat::AUDIO_NONE, int audioSampleRate = 0, AudioEncodeOptions_t audioOptions = AudioEncodeOptions::NO_AUDIO_OPTIONS, int audioSampleGroupSize = 0 );
+
+ virtual bool IsReadyToRecord();
+ virtual VideoResult_t GetLastResult();
+
+ virtual bool AppendVideoFrame( void *pFrameBuffer, int nStrideAdjustBytes = 0 );
+ virtual bool AppendAudioSamples( void *pSampleBuffer, size_t sampleSize );
+
+ virtual int GetFrameCount();
+ virtual int GetSampleCount();
+ virtual int GetSampleRate();
+ virtual VideoFrameRate_t GetFPS();
+
+ virtual bool AbortMovie();
+ virtual bool FinishMovie( bool SaveMovieToDisk = true );
+
+#ifdef ENABLE_EXTERNAL_ENCODER_LOGGING
+ virtual bool LogMessage( const char *msg );
+#endif
+
+ private:
+
+ void SetResult( VideoResult_t resultCode );
+
+ float GetDataRate( int quality, int width, int height );
+
+ CQTVideoFileComposer *m_pEncoder;
+ VideoResult_t m_LastResult;
+ bool m_bHasAudio;
+ bool m_bMovieFinished;
+
+};
+
+
+
+#endif // QUICKTIME_RECORDER_H
diff --git a/video/quicktime_video.cpp b/video/quicktime_video.cpp
new file mode 100644
index 0000000..8917138
--- /dev/null
+++ b/video/quicktime_video.cpp
@@ -0,0 +1,1096 @@
+//========= 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 <WinDef.h>
+ #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;
+}
diff --git a/video/quicktime_video.h b/video/quicktime_video.h
new file mode 100644
index 0000000..6d6f87a
--- /dev/null
+++ b/video/quicktime_video.h
@@ -0,0 +1,128 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+
+#ifndef QUICKTIME_VIDEO_H
+#define QUICKTIME_VIDEO_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Forward declarations
+//-----------------------------------------------------------------------------
+class IFileSystem;
+class IMaterialSystem;
+class CQuickTimeMaterial;
+
+//-----------------------------------------------------------------------------
+// Global interfaces - you already did the needed includes, right?
+//-----------------------------------------------------------------------------
+extern IFileSystem *g_pFileSystem;
+extern IMaterialSystem *materials;
+
+//-----------------------------------------------------------------------------
+// Quicktime includes - conditional compilation on #define QUICKTIME in VPC
+// The intent is to have a default functionality fallback if not defined
+// which provides a dynamically generated texture (moving line on background)
+//-----------------------------------------------------------------------------
+#if defined ( OSX )
+ #include <quicktime/QTML.h>
+ #include <quicktime/Movies.h>
+#elif defined ( WIN32 )
+ #include <QTML.h>
+ #include <Movies.h>
+ #include <windows.h>
+#elif
+ #error "Quicktime not supported on this target platform"
+#endif
+
+
+#include "video/ivideoservices.h"
+#include "videosubsystem.h"
+
+#include "utlvector.h"
+
+
+
+
+
+// -----------------------------------------------------------------------------
+// CQuickTimeVideoSubSystem - Implementation of IVideoSubSystem
+// -----------------------------------------------------------------------------
+class CQuickTimeVideoSubSystem : public CTier2AppSystem< IVideoSubSystem >
+{
+ typedef CTier2AppSystem< IVideoSubSystem > BaseClass;
+
+ public:
+ CQuickTimeVideoSubSystem();
+ ~CQuickTimeVideoSubSystem();
+
+ // Inherited from IAppSystem
+ virtual bool Connect( CreateInterfaceFn factory );
+ virtual void Disconnect();
+ virtual void *QueryInterface( const char *pInterfaceName );
+ virtual InitReturnVal_t Init();
+ virtual void Shutdown();
+
+ // Inherited from IVideoSubSystem
+
+ // SubSystem Identification functions
+ virtual VideoSystem_t GetSystemID();
+ virtual VideoSystemStatus_t GetSystemStatus();
+ virtual VideoSystemFeature_t GetSupportedFeatures();
+ virtual const char *GetVideoSystemName();
+
+ // Setup & Shutdown Services
+ virtual bool InitializeVideoSystem( IVideoCommonServices *pCommonServices );
+ virtual bool ShutdownVideoSystem();
+
+ virtual VideoResult_t VideoSoundDeviceCMD( VideoSoundDeviceOperation_t operation, void *pDevice = nullptr, void *pData = nullptr );
+
+ // get list of file extensions and features we support
+ virtual int GetSupportedFileExtensionCount();
+ virtual const char *GetSupportedFileExtension( int num );
+ virtual VideoSystemFeature_t GetSupportedFileExtensionFeatures( int num );
+
+ // Video Playback and Recording Services
+ virtual VideoResult_t PlayVideoFileFullScreen( const char *filename, void *mainWindow, int windowWidth, int windowHeight, int desktopWidth, int desktopHeight, bool windowed, float forcedMinTime, VideoPlaybackFlags_t playbackFlags );
+
+ // Create/destroy a video material
+ virtual IVideoMaterial *CreateVideoMaterial( const char *pMaterialName, const char *pVideoFileName, VideoPlaybackFlags_t flags );
+ virtual VideoResult_t DestroyVideoMaterial( IVideoMaterial *pVideoMaterial );
+
+ // Create/destroy a video encoder
+ virtual IVideoRecorder *CreateVideoRecorder();
+ virtual VideoResult_t DestroyVideoRecorder( IVideoRecorder *pRecorder );
+
+ virtual VideoResult_t CheckCodecAvailability( VideoEncodeCodec_t codec );
+
+ virtual VideoResult_t GetLastResult();
+
+ private:
+
+ bool SetupQuickTime();
+ bool ShutdownQuickTime();
+
+ VideoResult_t SetResult( VideoResult_t status );
+
+ bool m_bQuickTimeInitialized;
+ VideoResult_t m_LastResult;
+
+ VideoSystemStatus_t m_CurrentStatus;
+ VideoSystemFeature_t m_AvailableFeatures;
+
+ IVideoCommonServices *m_pCommonServices;
+
+ CUtlVector< IVideoMaterial* > m_MaterialList;
+ CUtlVector< IVideoRecorder* > m_RecorderList;
+
+ static const VideoSystemFeature_t DEFAULT_FEATURE_SET;
+};
+
+#endif // QUICKTIME_VIDEO_H
diff --git a/video/video_macros.h b/video/video_macros.h
new file mode 100644
index 0000000..0b75933
--- /dev/null
+++ b/video/video_macros.h
@@ -0,0 +1,114 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// videomacros.h - Commone macros used by valve video services
+//
+// Purpose - to save typing, make things cleaer, prep for c++0x, prep for 64-bit,
+// make lots of money, and drive Brian crazy
+//
+// ===========================================================================
+
+#ifndef VIDEOMACROS_H
+#define VIDEOMACROS_H
+
+#ifdef _WIN32
+ #pragma once
+#endif
+
+#include <tier0/dbg.h>
+
+// ------------------------------------------------------------------------
+// MACROS
+// ------------------------------------------------------------------------
+
+#define nullchar ( (char) 0x00 )
+#ifndef nullptr
+#define nullptr ( 0 )
+#endif
+
+#define ZeroVar( var ) V_memset( &var, nullchar, sizeof( var ) )
+#define ZeroVarPtr( pVar ) V_memset( pVar, nullchar, sizeof( *pVar) )
+
+#define SAFE_DELETE( var ) if ( var != nullptr ) { delete var; var = nullptr; }
+#define SAFE_DELETE_ARRAY( var ) if ( var != nullptr ) { delete[] var; var = nullptr; }
+
+#define SAFE_RELEASE( var ) if ( var != nullptr ) { var->Release(); var = nullptr; }
+#define SAFE_FREE( var ) if ( var != nullptr ) { free( var ); var = nullptr; }
+
+// Common Assert Patterns
+
+#define AssertPtr( _exp ) Assert( ( _exp ) != nullptr )
+#define AssertNull( _exp ) Assert( ( _exp ) == nullptr )
+#define AssertStr( _str ) Assert( ( _str ) != nullptr && *( _str ) != nullchar )
+
+#define AssertInRange( _exp, low, high ) Assert( ( _exp ) > ( low ) && ( _exp ) < ( high ) )
+#define AssertIncRange( _exp, low, high ) Assert( ( _exp ) >= ( low ) && ( _exp ) <= ( high ) )
+
+// AssertExit macros .. in release builds, or when Assert is disabled, they exit (w/ opt return value)
+// if the assert condition is false
+//
+
+#ifdef DBGFLAG_ASSERT
+
+ #define AssertExit( _exp ) Assert( _exp )
+ #define AssertExitV( _exp , _rval ) Assert( _exp )
+ #define AssertExitF( _exp ) Assert( _exp )
+ #define AssertExitN( _exp ) Assert( _exp )
+ #define AssertExitFunc( _exp, _func ) Assert( _exp )
+
+ #define AssertPtrExit( _exp ) Assert( ( _exp ) != nullptr )
+ #define AssertPtrExitV( _exp , _rval ) Assert( ( _exp ) != nullptr )
+ #define AssertPtrExitF( _exp ) Assert( ( _exp ) != nullptr )
+ #define AssertPtrExitN( _exp ) Assert( ( _exp ) != nullptr )
+
+ #define AssertInRangeExit( _exp , low , high ) Assert( ( _exp ) > ( low ) && ( _exp ) < ( high ) )
+ #define AssertInRangeExitV( _exp , low , high, _rval ) Assert( ( _exp ) > ( low ) && ( _exp ) < ( high ) )
+ #define AssertInRangeExitF( _exp , low , high ) Assert( ( _exp ) > ( low ) && ( _exp ) < ( high ) )
+ #define AssertInRangeExitN( _exp , low , high ) Assert( ( _exp ) > ( low ) && ( _exp ) < ( high ) )
+
+
+#else // Asserts not enabled
+
+ #define AssertExit( _exp ) if ( !( _exp ) ) return
+ #define AssertExitV( _exp , _rval ) if ( !( _exp ) ) return _rval
+ #define AssertExitF( _exp ) if ( !( _exp ) ) return false
+ #define AssertExitN( _exp ) if ( !( _exp ) ) return nullptr
+ #define AssertExitFunc( _exp, _func ) if ( !( _exp ) ) { _func; return; }
+
+ #define AssertPtrExit( _exp ) if ( ( _exp ) == nullptr ) return
+ #define AssertPtrExitV( _exp , _rval ) if ( ( _exp ) == nullptr ) return _rval
+ #define AssertPtrExitF( _exp ) if ( ( _exp ) == nullptr ) return false
+ #define AssertPtrExitN( _exp ) if ( ( _exp ) == nullptr ) return nullptr
+
+ #define AssertInRangeExit( _exp, low, high ) if ( ( _exp ) <= ( low ) || ( _exp ) >= ( high ) ) return
+ #define AssertInRangeExitV( _exp, low, high, _rval ) if ( ( _exp ) <= ( low ) || ( _exp ) >= ( high ) ) return _rval
+ #define AssertInRangeExitF( _exp, low, high ) if ( ( _exp ) <= ( low ) || ( _exp ) >= ( high ) ) return false
+ #define AssertInRangeExitN( _exp, low, high ) if ( ( _exp ) <= ( low ) || ( _exp ) >= ( high ) ) return nullptr
+
+#endif
+
+#define WarningAssert( _msg ) AssertMsg( false, _msg )
+
+
+#define STRINGS_MATCH 0
+#define IS_NOT_EMPTY( str ) ( (str) != nullptr && *(str) != nullchar )
+#define IS_EMPTY_STR( str ) ( (str) == nullptr || *(str) == nullchar )
+
+#define IS_IN_RANGE( var, low, high ) ( (var) >= (low) && (var) <= (high) )
+#define IS_IN_RANGECOUNT( var, low, high ) ( (var) >= (low) && (var) < (high) )
+
+#define IS_OUT_OF_RANGE( var, low, high ) ( (var) < (low) || (var) > (high) )
+#define IS_OUT_OF_RANGECOUNT( var, low, high ) ( (var) < (low) || (var) >= (high) )
+
+#define BITFLAGS_SET( var, bits ) ( ( (var) & (bits) ) == (bits) )
+#define ANY_BITFLAGS_SET( var, bits ) ( ( (var) & (bits) ) != 0 )
+
+#define MAKE_UINT64( highVal, lowVal ) ( ( (uint64) highVal << 32 ) | (uint64) lowVal )
+
+#define CONTAINING_MULTIPLE_OF( var, multVal ) ( (var) + ( multVal - 1) ) - ( ( (var) - 1) % multVal )
+
+// use this whenever we do address arithmetic in bytes
+typedef unsigned char* memaddr_t;
+typedef int32 memoffset_t;
+
+
+#endif // VIDEOMACROS_H \ No newline at end of file
diff --git a/video/video_quicktime.vpc b/video/video_quicktime.vpc
new file mode 100644
index 0000000..c9f6cd9
--- /dev/null
+++ b/video/video_quicktime.vpc
@@ -0,0 +1,84 @@
+//-----------------------------------------------------------------------------
+// video_quicktime.vpc
+//
+// Project Script
+// Created by: Matt Pritchard
+//
+// Description: Quicktime video sub-system (for video services system)
+//
+//-----------------------------------------------------------------------------
+
+$Macro SRCDIR ".."
+$Macro OUTBINDIR "$SRCDIR\..\game\bin"
+
+$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc"
+
+$Configuration
+{
+ $Compiler
+ {
+ // Win32 - need to point to Quicktime 7 for Win SDK directory so that dependant includes will work
+ $AdditionalIncludeDirectories "$BASE,..\common\quicktime_win32\" [$WIN32]
+ $AdditionalIncludeDirectories "$BASE;$SRCDIR\dx9sdk\include" [$WIN32]
+ }
+ $Linker
+ {
+ $IgnoreImportLibrary "Yes" [$WIN32]
+ $AdditionalDependencies "$BASE vfw32.lib" [$WIN32]
+ $SystemLibraries "iconv" [$OSXALL]
+ $SystemFrameworks "Quicktime;Carbon" [$OSXALL]
+ $AdditionalLibraryDirectories "$BASE;$SRCDIR\dx9sdk\lib" [$WIN32]
+ }
+}
+
+
+$Configuration "Debug"
+{
+ $General
+ {
+ $OutputDirectory "Debug_Video_Quicktime" [$WINDOWS]
+ $IntermediateDirectory "Debug_Video_Quicktime" [$WINDOWS]
+ }
+}
+
+$Configuration "Release"
+{
+ $General
+ {
+ $OutputDirectory "Release_Video_Quicktime" [$WINDOWS]
+ $IntermediateDirectory "Release_Video_Quicktime" [$WINDOWS]
+ }
+}
+
+
+$Project "video_quicktime"
+{
+ $Folder "Source Files" [$OSXALL||$WIN32]
+ {
+ $file "quicktime_video.cpp"
+ $file "quicktime_material.cpp"
+ $file "quicktime_recorder.cpp"
+ }
+
+ $Folder "Header Files" [$OSXALL||$WIN32]
+ {
+ $file "videosubsystem.h"
+ $file "video_macros.h"
+ $file "quicktime_common.h"
+ $file "quicktime_video.h"
+ $file "quicktime_material.h"
+ $file "quicktime_recorder.h"
+ $file "$SRCDIR\public\pixelwriter.h"
+
+ }
+
+ $Folder "Link Libraries"
+ {
+ $Lib tier2
+ $File "$SRCDIR\lib\common\quicktime\QTMLClient.lib" [$WIN32]
+ $File "$SRCDIR\DX9SDK\lib\dsound.lib" [$WIN32]
+ $File "$SRCDIR\DX9SDK\lib\dxguid.lib" [$WIN32]
+
+ }
+}
+
diff --git a/video/video_services.vpc b/video/video_services.vpc
new file mode 100644
index 0000000..fcc0ead
--- /dev/null
+++ b/video/video_services.vpc
@@ -0,0 +1,72 @@
+//-----------------------------------------------------------------------------
+// videoservices.vpc
+//
+// Project Script
+// Created by: Matt Pritchard
+//
+// Description: Centralized & abstracted cross-platform video services
+// Provides a central interface where the game can
+// handle video requests and query video capabilities
+//
+//-----------------------------------------------------------------------------
+
+$Macro SRCDIR ".."
+$Macro OUTBINDIR "$SRCDIR\..\game\bin"
+
+$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc"
+
+
+$Configuration
+{
+ $Linker
+ {
+ $SystemLibraries "iconv" [$OSXALL]
+ $SystemFrameworks "Carbon" [$OSXALL]
+ }
+}
+
+$Configuration "Debug"
+{
+ $General
+ {
+ $OutputDirectory "debug_video_services" [$WINDOWS]
+ $IntermediateDirectory "debug_video_services" [$WINDOWS]
+ }
+}
+
+$Configuration "Release"
+{
+ $General
+ {
+ $OutputDirectory "Release_video_services" [$WINDOWS]
+ $IntermediateDirectory "Release_video_services" [$WINDOWS]
+ }
+}
+
+
+$Project "video_services"
+{
+ $Folder "Source Files"
+ {
+ $file "videoservices.cpp"
+ }
+
+ $Folder "Header Files"
+ {
+ $file "video_macros.h"
+ $file "videoservices.h"
+ $file "videosubsystem.h"
+ }
+
+ $Folder "Interface"
+ {
+ $File "$SRCDIR\public\video\ivideoservices.h"
+ }
+
+ $Folder "Link Libraries"
+ {
+ $Lib tier2
+ $Lib tier3
+ }
+}
+
diff --git a/video/video_webm.vpc b/video/video_webm.vpc
new file mode 100644
index 0000000..379dd97
--- /dev/null
+++ b/video/video_webm.vpc
@@ -0,0 +1,86 @@
+//-----------------------------------------------------------------------------
+// video_webm.vpc
+//
+// Project Script
+//
+// Description: WebM video sub-system (for video services system)
+//
+//-----------------------------------------------------------------------------
+
+$Macro SRCDIR ".."
+$Macro OUTBINDIR "$SRCDIR\..\game\bin"
+
+$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc"
+
+$Configuration
+{
+ $Compiler
+ {
+ $AdditionalIncludeDirectories "$BASE;$SRCDIR\dx9sdk\include" [$WIN32]
+ $AdditionalIncludeDirectories "$BASE;$SRCDIR\thirdparty\libvpx-v1.1.0"
+ $AdditionalIncludeDirectories "$BASE;$SRCDIR\thirdparty\libvorbis-1.3.2\include"
+ $AdditionalIncludeDirectories "$BASE;$SRCDIR\thirdparty\libwebm"
+ $AdditionalIncludeDirectories "$BASE;$SRCDIR\thirdparty\libogg-1.2.2\include"
+ }
+ $Linker
+ {
+ $IgnoreImportLibrary "Yes" [$WIN32]
+ $AdditionalDependencies "$BASE vfw32.lib" [$WIN32]
+ $SystemLibraries "iconv" [$OSXALL]
+ $SystemFrameworks "Carbon" [$OSXALL]
+ $AdditionalLibraryDirectories "$BASE;$SRCDIR\dx9sdk\lib" [$WIN32]
+ }
+}
+
+
+$Configuration "Debug"
+{
+ $General
+ {
+ $OutputDirectory "Debug_Video_WebM"
+ $IntermediateDirectory "Debug_Video_WebM"
+ }
+}
+
+$Configuration "Release"
+{
+ $General
+ {
+ $OutputDirectory "Release_Video_WebM"
+ $IntermediateDirectory "Release_Video_WebM"
+ }
+}
+
+
+$Project "video_webm"
+{
+ $Folder "Source Files" [$OSXALL||$WIN32||$LINUXALL]
+ {
+ $file "webm_video.cpp"
+// $file "webm_material.cpp"
+ $file "webm_recorder.cpp"
+ }
+
+ $Folder "Header Files" [$OSXALL||$WIN32||$LINUXALL]
+ {
+ $file "videosubsystem.h"
+ $file "video_macros.h"
+ $file "webm_video.h"
+ $file "webm_recorder.h"
+ $file "webm_common.h"
+ $file "$SRCDIR\public\pixelwriter.h"
+
+ }
+
+ $Folder "Link Libraries"
+ {
+ $Lib tier1
+ $Lib tier2
+ $Lib $LIBCOMMON/libvorbis
+ $Lib $LIBCOMMON/libvpx
+ $Lib $LIBCOMMON/libwebm
+ $Lib $LIBCOMMON/libvorbisenc
+ $Lib $LIBCOMMON/libogg
+ }
+}
+
diff --git a/video/videoservices.cpp b/video/videoservices.cpp
new file mode 100644
index 0000000..24417c7
--- /dev/null
+++ b/video/videoservices.cpp
@@ -0,0 +1,1464 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#include "filesystem.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 "vgui/ILocalize.h"
+#include "vtf/vtf.h"
+#include "pixelwriter.h"
+#include "tier3/tier3.h"
+#include "platform.h"
+
+#include "videoservices.h"
+#include "video_macros.h"
+
+#include "tier0/memdbgon.h"
+
+#if defined( WIN32 )
+ #include <windows.h>
+#elif defined( OSX )
+ #include <Carbon/Carbon.h>
+#endif
+
+#if defined( USE_SDL )
+ #include "SDL.h"
+ #include "appframework/ilaunchermgr.h"
+#endif
+
+//-----------------------------------------------------------------------------
+// Platform specific video system controls & definitions
+//-----------------------------------------------------------------------------
+
+enum EPlatform_t
+{
+ PLATFORM_NONE = 0,
+ PLATFORM_WIN32 = 0x01,
+ PLATFORM_OSX = 0x02,
+ PLATFORM_XBOX_360 = 0x04,
+ PLATFORM_PS3 = 0x08,
+ PLATFORM_LINUX = 0x10
+};
+
+DEFINE_ENUM_BITWISE_OPERATORS( EPlatform_t );
+
+#if defined( IS_WINDOWS_PC )
+ const EPlatform_t thisPlatform = PLATFORM_WIN32;
+#elif defined( OSX )
+ const EPlatform_t thisPlatform = PLATFORM_OSX;
+#elif defined( _X360 )
+ const EPlatform_t thisPlatform = PLATFORM_XBOX_360;
+#elif defined( _PS3 )
+ const EPlatform_t thisPlatform = PLATFORM_PS3;
+#elif defined ( _LINUX )
+ const EPlatform_t thisPlatform = PLATFORM_LINUX;
+#else
+ #error "UNABLE TO DETERMINE PLATFORM"
+#endif
+
+
+#if defined( OSX ) || defined( LINUX )
+ILauncherMgr *g_pLauncherMgr = NULL;
+#endif
+
+
+struct VideoSystemInfo_t
+{
+ VideoSystem_t m_SystemID;
+ EPlatform_t m_Platforms;
+ const char *m_pModuleName;
+ const char *m_pInterfaceName;
+};
+
+static VideoSystemInfo_t s_VideoAppSystems[] =
+{
+ { VideoSystem::QUICKTIME, PLATFORM_WIN32 | PLATFORM_OSX, "video_quicktime", VIDEO_SUBSYSTEM_INTERFACE_VERSION },
+ { VideoSystem::BINK, PLATFORM_WIN32 | PLATFORM_OSX | PLATFORM_XBOX_360 | PLATFORM_LINUX, "video_bink", VIDEO_SUBSYSTEM_INTERFACE_VERSION },
+ //{ VideoSystem::AVI, PLATFORM_WIN32, "avi", VIDEO_SUBSYSTEM_INTERFACE_VERSION },
+ //{ VideoSystem::WMV, PLATFORM_WIN32, "wmv", VIDEO_SUBSYSTEM_INTERFACE_VERSION },
+ { VideoSystem::WEBM, PLATFORM_LINUX, "video_webm", VIDEO_SUBSYSTEM_INTERFACE_VERSION },
+
+ { VideoSystem::NONE, PLATFORM_NONE, nullptr, nullptr } // Required to terminate the list
+};
+
+
+
+//-----------------------------------------------------------------------------
+// Setup Singleton for accessing Valve Video Services
+//-----------------------------------------------------------------------------
+static CValveVideoServices g_VALVeVIDEO;
+EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CValveVideoServices, IVideoServices, VIDEO_SERVICES_INTERFACE_VERSION, g_VALVeVIDEO );
+
+
+static CVideoCommonServices g_VALVEVIDEOCommon;
+
+
+//-----------------------------------------------------------------------------
+// Valve Video Services implementation
+//-----------------------------------------------------------------------------
+CValveVideoServices::CValveVideoServices() :
+ m_nInstalledSystems( 0 ),
+ m_bInitialized( false ),
+ m_nMaterialCount( 0 )
+{
+ for ( int i = 0; i < VideoSystem::VIDEO_SYSTEM_COUNT; i++ )
+ {
+ m_VideoSystemModule[i] = nullptr;
+ m_VideoSystems[i] = nullptr;
+ m_VideoSystemType[i] = VideoSystem::NONE;
+ m_VideoSystemFeatures[i] = VideoSystemFeature::NO_FEATURES;
+ }
+
+}
+
+
+CValveVideoServices::~CValveVideoServices()
+{
+ DisconnectVideoLibraries( );
+}
+
+
+bool CValveVideoServices::Connect( CreateInterfaceFn factory )
+{
+ if ( !BaseClass::Connect( factory ) )
+ {
+ return false;
+ }
+
+ if ( g_pFullFileSystem == nullptr || materials == nullptr )
+ {
+ Msg( "Valve Video Services unable to connect due to missing dependent system\n" );
+ return false;
+ }
+
+#if defined( USE_SDL )
+ g_pLauncherMgr = (ILauncherMgr *)factory( SDLMGR_INTERFACE_VERSION, NULL );
+#endif
+
+ if ( !ConnectVideoLibraries( factory ) )
+ {
+ return false;
+ }
+
+ return ( true );
+}
+
+
+void CValveVideoServices::Disconnect()
+{
+ DisconnectVideoLibraries();
+}
+
+
+void* CValveVideoServices::QueryInterface( const char *pInterfaceName )
+{
+ if ( Q_strncmp( pInterfaceName, VIDEO_SERVICES_INTERFACE_VERSION, Q_strlen( VIDEO_SERVICES_INTERFACE_VERSION ) + 1) == 0 )
+ {
+ return (IVideoServices*) this;
+ }
+
+ return nullptr;
+}
+
+
+bool CValveVideoServices::ConnectVideoLibraries( CreateInterfaceFn factory )
+{
+ // Don't connect twice..
+ AssertExitF( m_bInitialized == false );
+
+ int n = 0;
+
+ while ( IS_NOT_EMPTY( s_VideoAppSystems[n].m_pModuleName ) && s_VideoAppSystems[n].m_SystemID != VideoSystem::NONE )
+ {
+ if (BITFLAGS_SET( s_VideoAppSystems[n].m_Platforms, thisPlatform ) )
+ {
+ bool success = false;
+ CSysModule *pModule = Sys_LoadModule(s_VideoAppSystems[n].m_pModuleName );
+ if( pModule != nullptr )
+ {
+ CreateInterfaceFn fn = Sys_GetFactory( pModule );
+ if ( fn != nullptr )
+ {
+
+ IVideoSubSystem *pVideoSystem = (IVideoSubSystem*) fn( s_VideoAppSystems[n].m_pInterfaceName, NULL );
+ if ( pVideoSystem != nullptr && pVideoSystem->Connect( factory ) )
+ {
+ if ( pVideoSystem->InitializeVideoSystem( &g_VALVEVIDEOCommon ) )
+ {
+ int slotNum = (int) pVideoSystem->GetSystemID();
+
+ if ( IS_IN_RANGECOUNT( slotNum, VideoSystem::VIDEO_SYSTEM_FIRST, VideoSystem::VIDEO_SYSTEM_COUNT ) )
+ {
+ Assert( m_VideoSystemModule[slotNum] == nullptr );
+ m_VideoSystemModule[slotNum] = pModule;
+ m_VideoSystems[slotNum] = pVideoSystem;
+
+ m_nInstalledSystems++;
+ success = true;
+ }
+ }
+ }
+ }
+
+ if ( success == false )
+ {
+
+ Msg( "Error occurred while attempting to load and initialize Video Subsystem\n Video Subsystem module '%s'\n Video Subsystem Interface '%s'", s_VideoAppSystems[n].m_pModuleName, s_VideoAppSystems[n].m_pInterfaceName );
+ Sys_UnloadModule( pModule );
+ }
+ }
+ }
+
+ n++;
+ }
+
+ // now we query each video system for its capabilities, and supported file extensions
+ for ( int i = VideoSystem::VIDEO_SYSTEM_FIRST; i < VideoSystem::VIDEO_SYSTEM_COUNT; i++ )
+ {
+ IVideoSubSystem *pSubSystem = m_VideoSystems[i];
+ if ( pSubSystem != nullptr )
+ {
+ m_VideoSystemType[i] = pSubSystem->GetSystemID();
+ m_VideoSystemFeatures[i] = pSubSystem->GetSupportedFeatures();
+
+ // get every file extension it handles, and the info about it
+ int eCount = pSubSystem->GetSupportedFileExtensionCount();
+ Assert( eCount > 0 );
+
+ for ( int n = 0; n < eCount; n++ )
+ {
+ VideoFileExtensionInfo_t extInfoRec;
+
+ extInfoRec.m_FileExtension = pSubSystem->GetSupportedFileExtension( n );
+ extInfoRec.m_VideoSubSystem = pSubSystem->GetSystemID();
+ extInfoRec.m_VideoFeatures = pSubSystem->GetSupportedFileExtensionFeatures( n );
+
+ AssertPtr( extInfoRec.m_FileExtension );
+
+ m_ExtInfo.AddToTail( extInfoRec );
+ }
+ }
+ }
+
+ m_bInitialized = true;
+
+ return true;
+}
+
+
+bool CValveVideoServices::DisconnectVideoLibraries()
+{
+ if ( !m_bInitialized )
+ {
+ return false;
+ }
+
+ // free up any objects/resources still out there
+ DestroyAllVideoInterfaces();
+
+ for ( int i = 0; i < VideoSystem::VIDEO_SYSTEM_COUNT; i++ )
+ {
+ if ( m_VideoSystems[i] != nullptr )
+ {
+ m_VideoSystems[i]->ShutdownVideoSystem();
+ m_VideoSystems[i]->Disconnect();
+ m_VideoSystems[i] = nullptr;
+ }
+
+ if ( m_VideoSystemModule[i] != nullptr )
+ {
+ Sys_UnloadModule( m_VideoSystemModule[i] );
+ m_VideoSystemModule[i] = nullptr;
+ }
+
+ m_VideoSystemType[i] = VideoSystem::NONE;
+ m_VideoSystemFeatures[i] = VideoSystemFeature::NO_FEATURES;
+ }
+
+ m_bInitialized = false;
+
+ return true;
+}
+
+
+int CValveVideoServices::DestroyAllVideoInterfaces()
+{
+ int n = m_RecorderList.Count() + m_MaterialList.Count();
+
+ for ( int i = m_RecorderList.Count() -1; i >= 0; i-- )
+ {
+ DestroyVideoRecorder( (IVideoRecorder*) m_RecorderList[i].m_pObject );
+ }
+
+ for ( int i = m_MaterialList.Count() -1; i >= 0; i-- )
+ {
+ DestroyVideoMaterial( (IVideoMaterial*) m_MaterialList[i].m_pObject );
+ }
+
+ return n;
+}
+
+
+InitReturnVal_t CValveVideoServices::Init()
+{
+ InitReturnVal_t nRetVal = BaseClass::Init();
+ if ( nRetVal != INIT_OK )
+ {
+ return nRetVal;
+ }
+
+ // Initialize all loaded subsystems
+ for ( int n = VideoSystem::VIDEO_SYSTEM_FIRST; n < VideoSystem::VIDEO_SYSTEM_COUNT; n++ )
+ {
+ if ( m_VideoSystems[n] != nullptr )
+ {
+ nRetVal = m_VideoSystems[n]->Init();
+ if ( nRetVal != INIT_OK )
+ {
+ return nRetVal;
+ }
+ }
+ }
+
+ return INIT_OK;
+}
+
+
+void CValveVideoServices::Shutdown()
+{
+ DestroyAllVideoInterfaces();
+
+ // Shutdown all loaded subsystems
+ for ( int n = VideoSystem::VIDEO_SYSTEM_FIRST; n < VideoSystem::VIDEO_SYSTEM_COUNT; n++ )
+ {
+ if ( m_VideoSystems[n] != nullptr )
+ {
+ m_VideoSystems[n]->Shutdown();
+ }
+ }
+
+ BaseClass::Shutdown();
+}
+
+
+// ===========================================================================
+// Inherited from IVideoServices
+// ===========================================================================
+
+// Query the available video systems
+int CValveVideoServices::GetAvailableVideoSystemCount()
+{
+ return m_nInstalledSystems;
+}
+
+
+// returns the enumerated video system, *IF* it is installed and working
+VideoSystem_t CValveVideoServices::GetAvailableVideoSystem( int n )
+{
+ if ( n< 0 || n >= m_nInstalledSystems )
+ {
+ return VideoSystem::NONE;
+ }
+
+ for ( int i = VideoSystem::VIDEO_SYSTEM_FIRST, c = 0; i < VideoSystem::VIDEO_SYSTEM_COUNT; i++ )
+ {
+ if ( m_VideoSystems[i] != nullptr )
+ {
+ if ( c == n )
+ {
+ return m_VideoSystemType[i];
+ }
+ c++;
+ }
+ }
+
+ return VideoSystem::NONE;
+}
+
+
+// ===========================================================================
+// returns the index for the video system...
+// ... provided that system is installed and available to do something
+// ===========================================================================
+int CValveVideoServices::GetIndexForSystem( VideoSystem_t n )
+{
+ if ( n >= VideoSystem::VIDEO_SYSTEM_FIRST && n < VideoSystem::VIDEO_SYSTEM_COUNT && m_nInstalledSystems > 0 )
+ {
+ int i = (int) n;
+ if ( m_VideoSystems[i] != nullptr && m_VideoSystemFeatures[i] != VideoSystemFeature::NO_FEATURES )
+ {
+ return i;
+ }
+ }
+
+ return SYSTEM_NOT_FOUND;
+}
+
+
+VideoSystem_t CValveVideoServices::GetSystemForIndex( int n )
+{
+ if ( n >= VideoSystem::VIDEO_SYSTEM_FIRST && n < VideoSystem::VIDEO_SYSTEM_COUNT && m_nInstalledSystems > 0 )
+ {
+ if ( m_VideoSystems[n] != nullptr && m_VideoSystemFeatures[n] != VideoSystemFeature::NO_FEATURES )
+ {
+ return (VideoSystem_t) n;
+ }
+ }
+
+ return VideoSystem::NONE;
+}
+
+
+// ===========================================================================
+// video system query functions
+// ===========================================================================
+bool CValveVideoServices::IsVideoSystemAvailable( VideoSystem_t videoSystem )
+{
+ int n = GetIndexForSystem( videoSystem );
+ return ( n != SYSTEM_NOT_FOUND ) ? true : false;
+}
+
+
+VideoSystemStatus_t CValveVideoServices::GetVideoSystemStatus( VideoSystem_t videoSystem )
+{
+ int n = GetIndexForSystem( videoSystem );
+ return ( n!= SYSTEM_NOT_FOUND ) ? m_VideoSystems[n]->GetSystemStatus() : VideoSystemStatus::NOT_INSTALLED;
+}
+
+
+VideoSystemFeature_t CValveVideoServices::GetVideoSystemFeatures( VideoSystem_t videoSystem )
+{
+ int n = GetIndexForSystem( videoSystem );
+ return ( n!= SYSTEM_NOT_FOUND ) ? m_VideoSystemFeatures[n] : VideoSystemFeature::NO_FEATURES;
+
+}
+
+
+const char *CValveVideoServices::GetVideoSystemName( VideoSystem_t videoSystem )
+{
+ int n = GetIndexForSystem( videoSystem );
+ return ( n!= SYSTEM_NOT_FOUND ) ? m_VideoSystems[n]->GetVideoSystemName() : nullptr;
+}
+
+
+VideoSystem_t CValveVideoServices::FindNextSystemWithFeature( VideoSystemFeature_t features, VideoSystem_t startAfter )
+{
+ if ( ( features & VideoSystemFeature::ALL_VALID_FEATURES ) == 0 )
+ {
+ return VideoSystem::NONE;
+ }
+
+ int start = VideoSystem::VIDEO_SYSTEM_FIRST;
+ if ( startAfter != VideoSystem::NONE && IS_IN_RANGECOUNT( startAfter, VideoSystem::VIDEO_SYSTEM_FIRST, VideoSystem::VIDEO_SYSTEM_COUNT ) )
+ {
+ start = (int) startAfter;
+ }
+
+ for ( int i = start; i < VideoSystem::VIDEO_SYSTEM_COUNT; i++ )
+ {
+ if ( m_VideoSystems[i] != nullptr && BITFLAGS_SET( m_VideoSystemFeatures[i], features ) )
+ {
+ return (VideoSystem_t) i;
+ }
+ }
+
+ return VideoSystem::NONE;
+}
+
+
+// ===========================================================================
+// video services status functions
+// ===========================================================================
+VideoResult_t CValveVideoServices::GetLastResult()
+{
+ return m_LastResult;
+}
+
+
+VideoResult_t CValveVideoServices::SetResult( VideoResult_t resultCode )
+{
+ m_LastResult = resultCode;
+ return resultCode;
+}
+
+
+// ===========================================================================
+// deal with video file extensions and video system mappings
+// ===========================================================================
+int CValveVideoServices::GetSupportedFileExtensionCount( VideoSystem_t videoSystem )
+{
+ int n = GetIndexForSystem( videoSystem );
+
+ return ( n == SYSTEM_NOT_FOUND ) ? 0 : m_VideoSystems[n]->GetSupportedFileExtensionCount();
+}
+
+
+const char *CValveVideoServices::GetSupportedFileExtension( VideoSystem_t videoSystem, int extNum )
+{
+ int n = GetIndexForSystem( videoSystem );
+
+ int c = ( n == SYSTEM_NOT_FOUND ) ? 0 : m_VideoSystems[n]->GetSupportedFileExtensionCount();;
+
+ return ( extNum < 0 || extNum >= c ) ? nullptr : m_VideoSystems[n]->GetSupportedFileExtension( extNum );
+
+}
+
+
+VideoSystemFeature_t CValveVideoServices::GetSupportedFileExtensionFeatures( VideoSystem_t videoSystem, int extNum )
+{
+ int n = GetIndexForSystem( videoSystem );
+
+ int c = ( n == SYSTEM_NOT_FOUND ) ? 0 : m_VideoSystems[n]->GetSupportedFileExtensionCount();
+
+ return ( extNum < 0 || extNum >= c ) ? VideoSystemFeature::NO_FEATURES : m_VideoSystems[n]->GetSupportedFileExtensionFeatures( extNum );
+}
+
+
+VideoSystem_t CValveVideoServices::LocateVideoSystemForPlayingFile( const char *pFileName, VideoSystemFeature_t playMode )
+{
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitV( IS_NOT_EMPTY( pFileName ), VideoSystem::NONE );
+
+ VideoSystem_t theSystem = LocateSystemAndFeaturesForFileName( pFileName, nullptr, playMode );
+
+ SetResult( VideoResult::SUCCESS );
+ return theSystem;
+}
+
+
+// ===========================================================================
+// Given a video file name, possibly with a set extension, locate the file
+// or a suitable substitute that is playable on the current system
+// ===========================================================================
+VideoResult_t CValveVideoServices::LocatePlayableVideoFile( const char *pSearchFileName, const char *pPathID, VideoSystem_t *pPlaybackSystem, char *pPlaybackFileName, int fileNameMaxLen, VideoSystemFeature_t playMode )
+{
+ AssertExitV( IS_NOT_EMPTY( pSearchFileName ) || pPlaybackSystem == nullptr || pPlaybackSystem == nullptr || fileNameMaxLen <= 0, SetResult( VideoResult::BAD_INPUT_PARAMETERS ) );
+
+ VideoResult_t Status = ResolveToPlayableVideoFile( pSearchFileName, pPathID, VideoSystem::DETERMINE_FROM_FILE_EXTENSION, playMode,
+ true, pPlaybackFileName, fileNameMaxLen, pPlaybackSystem );
+
+ return SetResult( Status );
+}
+
+
+
+// ===========================================================================
+// Create/destroy a video material
+// ===========================================================================
+IVideoMaterial* CValveVideoServices::CreateVideoMaterial( const char *pMaterialName, const char *pVideoFileName, const char *pPathID, VideoPlaybackFlags_t playbackFlags, VideoSystem_t videoSystem, bool PlayAlternateIfNotAvailable )
+{
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitV( IS_NOT_EMPTY( pVideoFileName ), nullptr );
+ AssertExitV( videoSystem == VideoSystem::DETERMINE_FROM_FILE_EXTENSION || IS_IN_RANGECOUNT( videoSystem, VideoSystem::VIDEO_SYSTEM_FIRST, VideoSystem::VIDEO_SYSTEM_COUNT ), nullptr );
+
+ // We need to resolve the filename and video system
+
+ char ResolvedFilePath[MAX_PATH];
+ VideoSystem_t actualVideoSystem = videoSystem;
+
+ VideoResult_t Status = ResolveToPlayableVideoFile( pVideoFileName, pPathID, videoSystem, VideoSystemFeature::PLAY_VIDEO_FILE_IN_MATERIAL, PlayAlternateIfNotAvailable,
+ ResolvedFilePath, sizeof(ResolvedFilePath), &actualVideoSystem );
+
+ SetResult( Status );
+ if ( Status != VideoResult::SUCCESS )
+ {
+ return nullptr;
+ }
+
+ int sysIndex = GetIndexForSystem( actualVideoSystem );
+
+ if ( sysIndex == SYSTEM_NOT_FOUND )
+ {
+ SetResult( VideoResult::SYSTEM_ERROR_OCCURED );
+ return nullptr;
+ }
+
+ // Create the video material
+ IVideoMaterial *pMaterial = m_VideoSystems[sysIndex]->CreateVideoMaterial( pMaterialName, ResolvedFilePath, playbackFlags );
+
+ // Update our list, and return
+ if ( pMaterial != nullptr )
+ {
+ CActiveVideoObjectRecord_t info;
+ info.m_pObject = pMaterial;
+ info.m_VideoSystem = sysIndex;
+ m_MaterialList.AddToTail( info );
+ }
+
+ SetResult( m_VideoSystems[sysIndex]->GetLastResult() );
+ return pMaterial;
+}
+
+
+VideoResult_t CValveVideoServices::DestroyVideoMaterial( IVideoMaterial* pVideoMaterial )
+{
+ AssertPtrExitV( pVideoMaterial, SetResult( VideoResult::BAD_INPUT_PARAMETERS ) );
+
+ for ( int i = 0; i < m_MaterialList.Count(); i++ )
+ {
+ if ( m_MaterialList[i].m_pObject == pVideoMaterial )
+ {
+ VideoResult_t Status = m_VideoSystems[ m_MaterialList[i].m_VideoSystem ]->DestroyVideoMaterial( pVideoMaterial );
+ m_MaterialList.Remove( i );
+
+ return SetResult( Status );
+ }
+ }
+
+ return SetResult( VideoResult::RECORDER_NOT_FOUND );
+
+
+ return VideoResult::SUCCESS;
+}
+
+
+int CValveVideoServices::GetUniqueMaterialID()
+{
+ m_nMaterialCount++;
+ return m_nMaterialCount;
+}
+
+// ===========================================================================
+// Query availabilily of codec for encoding video
+// ===========================================================================
+VideoResult_t CValveVideoServices::IsRecordCodecAvailable( VideoSystem_t videoSystem, VideoEncodeCodec_t codec )
+{
+ AssertExitV( codec >= VideoEncodeCodec::DEFAULT_CODEC && codec < VideoEncodeCodec::CODEC_COUNT, SetResult( VideoResult::BAD_INPUT_PARAMETERS ) );
+
+ int n = GetIndexForSystem( videoSystem );
+
+ if ( n == SYSTEM_NOT_FOUND )
+ {
+ return SetResult( VideoResult::SYSTEM_NOT_AVAILABLE );
+ }
+
+ return m_VideoSystems[n]->CheckCodecAvailability( codec );
+}
+
+
+// ===========================================================================
+// Create/destroy a video encoder
+// ===========================================================================
+IVideoRecorder* CValveVideoServices::CreateVideoRecorder( VideoSystem_t videoSystem )
+{
+ int n = GetIndexForSystem( videoSystem );
+
+ if ( n == SYSTEM_NOT_FOUND )
+ {
+ SetResult( VideoResult::SYSTEM_NOT_AVAILABLE );
+ return nullptr;
+ }
+
+ if ( !BITFLAGS_SET( m_VideoSystemFeatures[n], VideoSystemFeature::ENCODE_VIDEO_TO_FILE ) )
+ {
+ SetResult( VideoResult::FEATURE_NOT_AVAILABLE );
+ return nullptr;
+ }
+
+ IVideoRecorder *pRecorder = m_VideoSystems[n]->CreateVideoRecorder();
+
+ if ( pRecorder != nullptr )
+ {
+ CActiveVideoObjectRecord_t info;
+ info.m_pObject = pRecorder;
+ info.m_VideoSystem = n;
+ m_RecorderList.AddToTail( info );
+ }
+
+ SetResult( m_VideoSystems[n]->GetLastResult() );
+ return pRecorder;
+}
+
+
+VideoResult_t CValveVideoServices::DestroyVideoRecorder( IVideoRecorder *pVideoRecorder )
+{
+ AssertPtrExitV( pVideoRecorder, SetResult( VideoResult::BAD_INPUT_PARAMETERS ) );
+
+ for ( int i = 0; i < m_RecorderList.Count(); i++ )
+ {
+ if ( m_RecorderList[i].m_pObject == pVideoRecorder )
+ {
+ VideoResult_t Status = m_VideoSystems[ m_RecorderList[i].m_VideoSystem ]->DestroyVideoRecorder( pVideoRecorder );
+ m_RecorderList.Remove( i );
+
+ return SetResult( Status );
+ }
+ }
+
+ return SetResult( VideoResult::RECORDER_NOT_FOUND );
+
+}
+
+
+// ===========================================================================
+// Plays a given video file until it completes or the user aborts
+// ===========================================================================
+VideoResult_t CValveVideoServices::PlayVideoFileFullScreen( const char *pFileName, const char *pPathID, void *mainWindow, int windowWidth, int windowHeight, int desktopWidth, int desktopHeight, bool windowed, float forcedMinTime, VideoPlaybackFlags_t playbackFlags, VideoSystem_t videoSystem, bool PlayAlternateIfNotAvailable )
+{
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitV( IS_NOT_EMPTY( pFileName ), VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitV( videoSystem == VideoSystem::DETERMINE_FROM_FILE_EXTENSION || IS_IN_RANGECOUNT( videoSystem, VideoSystem::VIDEO_SYSTEM_FIRST, VideoSystem::VIDEO_SYSTEM_COUNT ), VideoResult::BAD_INPUT_PARAMETERS );
+
+ char ResolvedFilePath[MAX_PATH];
+ VideoSystem_t actualVideoSystem = videoSystem;
+
+ VideoResult_t Status = ResolveToPlayableVideoFile( pFileName, pPathID, videoSystem, VideoSystemFeature::PLAY_VIDEO_FILE_FULL_SCREEN, PlayAlternateIfNotAvailable,
+ ResolvedFilePath, sizeof(ResolvedFilePath), &actualVideoSystem );
+
+ if ( Status != VideoResult::SUCCESS )
+ {
+ return Status;
+ }
+
+ int sysIndex = GetIndexForSystem( actualVideoSystem );
+
+ if ( sysIndex != SYSTEM_NOT_FOUND )
+ {
+ return SetResult( m_VideoSystems[sysIndex]->PlayVideoFileFullScreen( ResolvedFilePath, mainWindow, windowWidth, windowHeight, desktopWidth, desktopHeight, windowed, forcedMinTime, playbackFlags ) );
+ }
+ else
+ {
+ return SetResult( VideoResult::SYSTEM_ERROR_OCCURED );
+ }
+
+}
+
+
+// ===========================================================================
+// Functions to connect sound systems to video systems
+// ===========================================================================
+VideoResult_t CValveVideoServices::SoundDeviceCommand( VideoSoundDeviceOperation_t operation, void *pDevice, void *pData, VideoSystem_t videoSystem )
+{
+ AssertExitV( IS_IN_RANGECOUNT( operation, 0, VideoSoundDeviceOperation::OPERATION_COUNT ), SetResult( VideoResult::BAD_INPUT_PARAMETERS ) );
+
+ AssertExitV( videoSystem == VideoSystem::ALL_VIDEO_SYSTEMS || IS_IN_RANGECOUNT( videoSystem, VideoSystem::VIDEO_SYSTEM_FIRST, VideoSystem::VIDEO_SYSTEM_COUNT ), SetResult( VideoResult::BAD_INPUT_PARAMETERS ) );
+
+ int startIdx = (int) VideoSystem::VIDEO_SYSTEM_FIRST;
+ int lastIdx = (int) VideoSystem::VIDEO_SYSTEM_COUNT - 1;
+
+ if ( videoSystem != VideoSystem::ALL_VIDEO_SYSTEMS )
+ {
+ startIdx = lastIdx = GetIndexForSystem( videoSystem );
+ if ( startIdx == SYSTEM_NOT_FOUND )
+ {
+ return SetResult( VideoResult::SYSTEM_NOT_AVAILABLE );
+ }
+ }
+
+ VideoResult_t result = VideoResult::SYSTEM_NOT_AVAILABLE;
+
+ for ( int i = startIdx; i <= lastIdx; i++ )
+ {
+ int n = GetIndexForSystem( (VideoSystem_t) i );
+ if ( n != SYSTEM_NOT_FOUND )
+ {
+ result = m_VideoSystems[n]->VideoSoundDeviceCMD( operation, pDevice, pData );
+ }
+ }
+
+ return SetResult( result );
+}
+
+
+// ===========================================================================
+// Sets the sound devices that the video will decode to
+// ===========================================================================
+const wchar_t *CValveVideoServices::GetCodecName( VideoEncodeCodec_t nCodec )
+{
+ static const char *s_pCodecLookup[VideoEncodeCodec::CODEC_COUNT] =
+ {
+ "#Codec_MPEG2",
+ "#Codec_MPEG4",
+ "#Codec_H261",
+ "#Codec_H263",
+ "#Codec_H264",
+ "#Codec_MJPEG_A",
+ "#Codec_MJPEG_B",
+ "#Codec_SORENSON3",
+ "#Codec_CINEPACK",
+ "#Codec_WEBM",
+ };
+
+ if ( nCodec < 0 || nCodec >= VideoEncodeCodec::CODEC_COUNT )
+ {
+ AssertMsg( 0, "Invalid codec in CValveVideoServices::GetCodecName()" );
+ return NULL;
+ }
+
+ return g_pVGuiLocalize->Find( s_pCodecLookup[ nCodec ] );
+}
+
+// ===========================================================================
+// Functions to determine which file and video system to use
+// ===========================================================================
+VideoResult_t CValveVideoServices::ResolveToPlayableVideoFile( const char *pFileName, const char *pPathID, VideoSystem_t videoSystem, VideoSystemFeature_t requiredFeature,
+ bool PlayAlternateIfNotAvailable, char *pResolvedFileName, int resolvedFileNameMaxLen, VideoSystem_t *pResolvedVideoSystem )
+{
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitV( IS_NOT_EMPTY( pFileName ), VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitV( videoSystem == VideoSystem::DETERMINE_FROM_FILE_EXTENSION || IS_IN_RANGECOUNT( videoSystem, VideoSystem::VIDEO_SYSTEM_FIRST, VideoSystem::VIDEO_SYSTEM_COUNT ), VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitV( requiredFeature != VideoSystemFeature::NO_FEATURES, VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitV( pResolvedFileName != nullptr && resolvedFileNameMaxLen > 0 && pResolvedVideoSystem != nullptr, VideoResult::BAD_INPUT_PARAMETERS );
+
+ // clear results should we return failure
+ pResolvedFileName[0] = nullchar;
+ *pResolvedVideoSystem = VideoSystem::NONE;
+
+ int sysIdx = SYSTEM_NOT_FOUND;
+ VideoSystemFeature_t sysFeatures = VideoSystemFeature::NO_FEATURES;
+
+ // check the file extension to see if it specifies searching for any compatible video files
+ // if so, override a couple input values
+ if ( !IsMatchAnyExtension( pFileName ) )
+ {
+ goto search_for_video;
+ }
+
+ // is the requested video system available?
+
+ // We start with either the specified video system.. OR.. we choose the system based on the file extension
+ // Get the system and if it's valid, it's available features
+ if ( videoSystem != VideoSystem::DETERMINE_FROM_FILE_EXTENSION )
+ {
+ sysIdx = GetIndexForSystem( videoSystem ); // Caller specified the video system
+ sysFeatures = ( sysIdx != SYSTEM_NOT_FOUND ) ? m_VideoSystemFeatures[sysIdx] : VideoSystemFeature::NO_FEATURES;
+ }
+ else
+ {
+ // We need to determine the system to use based on filename
+ sysIdx = GetIndexForSystem( LocateSystemAndFeaturesForFileName( pFileName, &sysFeatures, requiredFeature ) );
+ }
+
+ // if we don't have a system to play this video.. and aren't allowed to look for an alternative...
+ if ( sysIdx == SYSTEM_NOT_FOUND && PlayAlternateIfNotAvailable == false )
+ {
+ return SetResult( VideoResult::VIDEO_SYSTEM_NOT_FOUND ); // return failure
+ }
+
+ char ActualFilePath[MAX_PATH];
+
+ // Examine the requested of inferred video system to see if it can do what we want,
+ // and if so, see if the corresponding file is actually found (we support search paths)
+
+ // Decision Path for when we have a preferred/specified video system specified to use
+ if ( sysIdx != SYSTEM_NOT_FOUND )
+ {
+ bool fileFound = false;
+
+ // if the request system can do the task, see if we can find the file as supplied by the caller
+ if ( BITFLAGS_SET( sysFeatures, requiredFeature ) )
+ {
+ if ( V_IsAbsolutePath( pFileName ) )
+ {
+ V_strncpy( ActualFilePath, pFileName, sizeof( ActualFilePath ) );
+ fileFound = g_pFullFileSystem->FileExists( pFileName, nullptr );
+ }
+ else
+ {
+ fileFound = ( g_pFullFileSystem->RelativePathToFullPath( pFileName, pPathID, ActualFilePath, sizeof( ActualFilePath ) ) != nullptr );
+ }
+ }
+ else // The specified video system does not support this (required) feature
+ {
+ // if we can't search for an alternative file, tell them we don't support this
+ if ( !PlayAlternateIfNotAvailable )
+ {
+ return SetResult( VideoResult::FEATURE_NOT_AVAILABLE );
+ }
+ }
+
+ // We found the specified file, and the video system has the feature support
+ if ( fileFound )
+ {
+ // copy the resolved filename and system and report success
+ V_strncpy( pResolvedFileName, ActualFilePath, resolvedFileNameMaxLen );
+ *pResolvedVideoSystem = GetSystemForIndex( sysIdx );
+ return SetResult( VideoResult::SUCCESS );
+ }
+
+ // ok, we have the feature support but didn't find the file to use...
+ if ( !PlayAlternateIfNotAvailable )
+ {
+ // if we can't search for an alternate file, so report file not found
+ return SetResult( VideoResult::VIDEO_FILE_NOT_FOUND );
+ }
+ }
+
+ // Ok, we didn't find the file and a system that could handle it
+ // but hey, we are allowed to look for an alternate video file and system
+
+search_for_video:
+
+ // start with the passed in filespec, and change the extension to wildcard
+ char SearchFileSpec[MAX_PATH];
+ V_strncpy( SearchFileSpec, pFileName, sizeof(SearchFileSpec) );
+ V_SetExtension( SearchFileSpec, ".*", sizeof(SearchFileSpec) );
+
+ FileFindHandle_t searchHandle = 0;
+
+ const char *pMatchingFile = g_pFullFileSystem->FindFirstEx( SearchFileSpec, pPathID, &searchHandle );
+
+ while ( pMatchingFile != nullptr )
+ {
+ const char *pExt = GetFileExtension( pMatchingFile );
+
+ if ( pExt != nullptr )
+ {
+ // compare file extensions
+ for ( int i = 0; i < m_ExtInfo.Count(); i++ )
+ {
+ // do we match a known extension?
+ if ( stricmp( pExt, m_ExtInfo[i].m_FileExtension ) == STRINGS_MATCH )
+ {
+ // do we support the requested feature?
+ if ( BITFLAGS_SET( m_ExtInfo[i].m_VideoFeatures, requiredFeature ) )
+ {
+ // Make sure it's a valid system
+ sysIdx = GetIndexForSystem( m_ExtInfo[i].m_VideoSubSystem );
+ if ( sysIdx != SYSTEM_NOT_FOUND )
+ {
+
+ // Start with any optional path we got...
+ V_ExtractFilePath( pFileName, ActualFilePath, sizeof( ActualFilePath ) );
+ // Append the search match file
+ V_strncat( ActualFilePath, pMatchingFile, sizeof( ActualFilePath ) );
+
+ if ( V_IsAbsolutePath( ActualFilePath ) )
+ {
+ V_strncpy( pResolvedFileName, ActualFilePath, resolvedFileNameMaxLen );
+ }
+ else
+ {
+ g_pFullFileSystem->RelativePathToFullPath( ActualFilePath, pPathID, pResolvedFileName, resolvedFileNameMaxLen );
+ }
+
+ // Return the system
+ *pResolvedVideoSystem = GetSystemForIndex( sysIdx );
+
+ g_pFullFileSystem->FindClose( searchHandle );
+
+ return SetResult( VideoResult::SUCCESS );
+ }
+ }
+ }
+ }
+ }
+
+ // not usable.. keep searching
+ pMatchingFile = g_pFullFileSystem->FindNext( searchHandle );
+ }
+
+ // we didn't find anything we could use
+ g_pFullFileSystem->FindClose( searchHandle );
+
+ return SetResult( VideoResult::VIDEO_FILE_NOT_FOUND );
+}
+
+
+VideoSystem_t CValveVideoServices::LocateSystemAndFeaturesForFileName( const char *pFileName, VideoSystemFeature_t *pFeatures, VideoSystemFeature_t requiredFeatures )
+{
+ if ( pFeatures != nullptr)
+ {
+ *pFeatures = VideoSystemFeature::NO_FEATURES;
+ }
+
+ AssertExitV( IS_NOT_EMPTY( pFileName ), VideoSystem::NONE );
+
+ if ( m_ExtInfo.Count() < 1 )
+ {
+ return VideoSystem::NONE;
+ }
+
+ // extract the file extension
+
+ char fileExt[MAX_PATH];
+
+ const char *pExt = GetFileExtension( pFileName );
+ if ( pExt == nullptr )
+ {
+ return VideoSystem::NONE;
+ }
+
+ // lowercase it so we can compare
+ V_strncpy( fileExt, pExt, sizeof(fileExt) );
+ V_strlower( fileExt );
+
+ for ( int i = 0; i < m_ExtInfo.Count(); i++ )
+ {
+ if ( V_stricmp( fileExt, m_ExtInfo[i].m_FileExtension ) == STRINGS_MATCH )
+ {
+ // must it have certain feature support?
+ if ( requiredFeatures != VideoSystemFeature::NO_FEATURES )
+ {
+ if ( !BITFLAGS_SET( m_ExtInfo[i].m_VideoFeatures, requiredFeatures ) )
+ {
+ continue;
+ }
+ }
+
+ if ( pFeatures != nullptr)
+ {
+ *pFeatures = m_ExtInfo[i].m_VideoFeatures;
+ }
+ return m_ExtInfo[i].m_VideoSubSystem;
+ }
+ }
+
+ return VideoSystem::NONE;
+}
+
+
+bool CValveVideoServices::IsMatchAnyExtension( const char *pFileName )
+{
+ if ( IS_EMPTY_STR( pFileName ) )
+ {
+ return false;
+ }
+
+ const char* pExt = GetFileExtension( pFileName );
+ if ( pExt == nullptr )
+ {
+ return false;
+ }
+
+ return ( V_stricmp( pExt, FILE_EXTENSION_ANY_MATCHING_VIDEO ) == STRINGS_MATCH );
+}
+
+
+const char *CValveVideoServices::GetFileExtension( const char *pFileName )
+{
+ if ( pFileName == nullptr )
+ {
+ return nullptr;
+ }
+
+ const char *pExt = V_GetFileExtension( pFileName );
+
+ if ( pExt == nullptr )
+ {
+ return nullptr;
+ }
+
+ if ( pExt != pFileName && *( pExt - 1 ) == '.' )
+ {
+ pExt--;
+ }
+
+ return pExt;
+}
+
+
+
+// ===========================================================================
+// CVideoCommonServices - services used by any/multiple videoSubsystems
+// Functions are put here to avoid duplication and ensure they stay
+// consistant across all installed subsystems
+// ===========================================================================
+
+
+#ifdef WIN32
+ typedef SHORT (WINAPI *GetAsyncKeyStateFn_t)( int vKey );
+
+ static HINSTANCE s_UserDLLhInst = nullptr;
+ GetAsyncKeyStateFn_t s_pfnGetAsyncKeyState = nullptr;
+#endif
+
+CVideoCommonServices::CVideoCommonServices()
+{
+ ResetInputHandlerState();
+}
+
+
+CVideoCommonServices::~CVideoCommonServices()
+{
+ if ( m_bInputHandlerInitialized )
+ {
+ TerminateFullScreenPlaybackInputHandler();
+ }
+
+}
+
+
+void CVideoCommonServices::ResetInputHandlerState()
+{
+ m_bInputHandlerInitialized = false;
+
+ m_bScanAll = false;
+ m_bScanEsc = false;
+ m_bScanReturn = false;
+ m_bScanSpace = false;
+ m_bPauseEnabled = false;
+ m_bAbortEnabled = false;
+ m_bEscLast = false;
+ m_bReturnLast = false;
+ m_bSpaceLast = false;
+ m_bForceMinPlayTime = false;
+
+ m_bWindowed = false;
+
+ m_playbackFlags = VideoPlaybackFlags::NO_PLAYBACK_OPTIONS;
+ m_forcedMinTime = 0.0f;
+
+ m_StartTime = 0;
+
+#ifdef WIN32
+ s_UserDLLhInst = nullptr;
+ s_pfnGetAsyncKeyState = nullptr;
+#endif
+
+}
+
+// ===========================================================================
+// Calculate the proper dimensions to play a video in full screen mode
+// uses the playback flags to supply rules for streaching, scaling and
+// centering the video
+// ===========================================================================
+bool CVideoCommonServices::CalculateVideoDimensions( int videoWidth, int videoHeight, int displayWidth, int displayHeight, VideoPlaybackFlags_t playbackFlags,
+ int *pOutputWidth, int *pOutputHeight, int *pXOffset, int *pYOffset )
+{
+ AssertExitF( pOutputWidth != nullptr && pOutputHeight != nullptr && pXOffset != nullptr && pYOffset != nullptr );
+ AssertExitF( videoWidth >= 16 && videoHeight >= 16 && displayWidth > 64 && displayHeight > 64 );
+
+ // extract relevant options
+ bool bFillWindow = BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::FILL_WINDOW );
+ bool bLockAspect = BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::LOCK_ASPECT_RATIO );
+ bool bIntegralScale = BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::INTEGRAL_SCALE );
+ bool bCenterVideo = BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::CENTER_VIDEO_IN_WINDOW );
+
+ int curWidth = videoWidth;
+ int curHeight = videoHeight;
+
+ // Try and just play it actual size?
+ if ( !bFillWindow )
+ {
+ // is the window the same size or larger?
+ if ( curWidth <= displayWidth && curHeight <= displayHeight )
+ {
+ goto finish;
+ }
+ else // we need to shrink the video output
+ {
+ // if we aren't locking the aspect ratio, just shrink each axis until it fits
+ if ( !bLockAspect )
+ {
+ while ( curWidth > displayWidth)
+ {
+ curWidth = ( bIntegralScale ) ? curWidth >> 1 : displayWidth;
+ }
+ while ( curHeight > displayHeight )
+ {
+ curHeight = ( bIntegralScale ) ? curHeight >> 1 : displayHeight;
+ }
+ goto finish;
+ }
+ else // we are locking the aspect ratio, and need to shrink the video
+ {
+ // integral scale only....
+ if ( bIntegralScale )
+ {
+ while ( curWidth > displayWidth || curHeight > displayHeight)
+ {
+ curWidth >>= 1;
+ curHeight >>= 1;
+ }
+ goto finish;
+ }
+ else // can scale variably..
+ {
+ float Xfactor = ( displayWidth / curWidth );
+ float Yfactor = ( displayHeight / curHeight );
+ float scale = MIN( Xfactor, Yfactor );
+
+ curWidth = (int) ( curWidth * scale + 0.35f );
+ curHeight = (int) ( curHeight * scale + 0.35f );
+ clamp( curWidth, 0, displayWidth );
+ clamp( curHeight, 0, displayHeight );
+ goto finish;
+ }
+
+ }
+ }
+ }
+
+ // ok.. we are wanting to fill the window....
+ if ( bFillWindow )
+ {
+ // are we locking the aspect ratio?
+ if ( bLockAspect )
+ {
+ // are we only allowed to scale integrally?
+ if ( bIntegralScale )
+ {
+ while ( (curWidth << 1) <= displayWidth && (curHeight << 1) <= displayHeight )
+ {
+ curWidth <<= 1;
+ curHeight <<= 1;
+ }
+ goto finish;
+ }
+ else
+ {
+ float Xfactor = ( (float)displayWidth / curWidth );
+ float Yfactor = ( (float)displayHeight / curHeight );
+ float scale = MIN( Xfactor, Yfactor );
+
+ curWidth = (int) ( curWidth * scale + 0.35f );
+ curHeight = (int) ( curHeight * scale + 0.35f );
+ clamp( curWidth, 0, displayWidth );
+ clamp( curHeight, 0, displayHeight );
+ goto finish;
+ }
+ }
+ else // we are not locking the aspect ratio...
+ {
+ if ( bIntegralScale )
+ {
+ while ( (curWidth << 1) <= displayWidth )
+ {
+ curWidth <<= 1;
+ }
+ while ( (curHeight << 1) <= displayHeight )
+ {
+ curHeight <<= 1;
+ }
+ goto finish;
+ }
+ else
+ {
+ curWidth = displayWidth;
+ curHeight = displayHeight;
+ goto finish;
+ }
+ }
+ }
+
+
+finish:
+ AssertExitF( displayWidth >= curWidth && displayHeight >= curHeight );
+
+ if ( bCenterVideo )
+ {
+ *pXOffset = ( displayWidth - curWidth ) >> 1;
+ *pYOffset = ( displayHeight - curHeight ) >> 1;
+ }
+ else
+ {
+ *pXOffset = 0;
+ *pYOffset = 0;
+ }
+
+ *pOutputWidth = curWidth;
+ *pOutputHeight = curHeight;
+
+ return true;
+
+}
+
+
+float CVideoCommonServices::GetSystemVolume()
+{
+ ConVarRef volumeConVar( "volume" );
+ float sysVolume = volumeConVar.IsValid() ? volumeConVar.GetFloat() : 1.0f;
+ clamp( sysVolume, 0.0f, 1.0f);
+
+ return sysVolume;
+}
+
+
+
+// ===========================================================================
+// Sets up the state machine to receive messages and poll the keyboard
+// while a full-screen video is playing
+// ===========================================================================
+VideoResult_t CVideoCommonServices::InitFullScreenPlaybackInputHandler( VideoPlaybackFlags_t playbackFlags, float forcedMinTime, bool windowed )
+{
+ // already initialized?
+ if ( m_bInputHandlerInitialized )
+ {
+ WarningAssert( "called twice" );
+ return VideoResult::OPERATION_ALREADY_PERFORMED;
+ }
+
+#ifdef WIN32
+ // We need to be able to poll the state of the input device, but we're not completely setup yet, so this spoofs the ability
+ HINSTANCE m_UserDLLhInst = LoadLibrary( "user32.dll" );
+ if ( m_UserDLLhInst == NULL )
+ {
+ return VideoResult::SYSTEM_ERROR_OCCURED;
+ }
+
+ s_pfnGetAsyncKeyState = (GetAsyncKeyStateFn_t) GetProcAddress( m_UserDLLhInst, "GetAsyncKeyState" );
+ if ( s_pfnGetAsyncKeyState == NULL )
+ {
+ FreeLibrary( m_UserDLLhInst );
+ return VideoResult::SYSTEM_ERROR_OCCURED;
+ }
+
+#endif
+
+ // save off playback options
+ m_playbackFlags = playbackFlags;
+ m_forcedMinTime = forcedMinTime;
+ m_bWindowed = windowed;
+
+ // process the pause and abort options
+ m_bScanAll = ANY_BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::PAUSE_ON_ANY_KEY | VideoPlaybackFlags::ABORT_ON_ANY_KEY );
+
+ m_bScanEsc = m_bScanAll || ANY_BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::PAUSE_ON_ESC | VideoPlaybackFlags::ABORT_ON_ESC );
+ m_bScanReturn = m_bScanAll || ANY_BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::PAUSE_ON_RETURN | VideoPlaybackFlags::ABORT_ON_RETURN );
+ m_bScanSpace = m_bScanAll || ANY_BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::PAUSE_ON_SPACE | VideoPlaybackFlags::ABORT_ON_SPACE );
+
+ m_bPauseEnabled = ANY_BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::PAUSE_ON_ESC | VideoPlaybackFlags::PAUSE_ON_RETURN | VideoPlaybackFlags::PAUSE_ON_SPACE | VideoPlaybackFlags::PAUSE_ON_ANY_KEY );
+ m_bAbortEnabled = ANY_BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::ABORT_ON_ESC | VideoPlaybackFlags::ABORT_ON_RETURN | VideoPlaybackFlags::ABORT_ON_SPACE | VideoPlaybackFlags::ABORT_ON_ANY_KEY );
+
+ // Setup the scan options
+ m_bEscLast = false;
+ m_bReturnLast = false;
+ m_bSpaceLast = false;
+
+ // Other Movie playback state init
+ m_bForceMinPlayTime = BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::FORCE_MIN_PLAY_TIME ) && ( forcedMinTime > 0.0f );
+
+ // Note the start time
+ m_StartTime = Plat_FloatTime();
+
+ // and we're on
+ m_bInputHandlerInitialized = true;
+
+ return VideoResult::SUCCESS;
+}
+
+
+// ===========================================================================
+// Pumps the message loops and checks for a supported event
+// returns true if there is an event to check
+// ===========================================================================
+bool CVideoCommonServices::ProcessFullScreenInput( bool &bAbortEvent, bool &bPauseEvent, bool &bQuitEvent )
+{
+
+ bAbortEvent = false;
+ bPauseEvent = false;
+ bQuitEvent = false;
+
+ if ( !m_bInputHandlerInitialized )
+ {
+ WarningAssert( "Not Initialized to call" );
+ return false;
+ }
+
+
+ // Pump OS Messages
+#if defined( WIN32 )
+ MSG msg;
+ while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
+ {
+ // did we get a quit message?
+ if ( msg.message == WM_QUIT )
+ {
+ ::PostQuitMessage( msg.wParam );
+ return true;
+ }
+
+ // todo - look for alt-tab events, etc?
+
+ TranslateMessage( &msg );
+ DispatchMessage( &msg );
+ }
+ // Escape, return, or space stops or pauses the playback
+ bool bEscPressed = ( m_bScanEsc ) ? ( s_pfnGetAsyncKeyState( VK_ESCAPE ) & 0x8000 ) != 0 : false;
+ bool bReturnPressed = ( m_bScanReturn ) ? ( s_pfnGetAsyncKeyState( VK_RETURN ) & 0x8000 ) != 0 : false;
+ bool bSpacePressed = ( m_bScanSpace ) ? ( s_pfnGetAsyncKeyState( VK_SPACE ) & 0x8000 ) != 0 : false;
+#elif defined(OSX)
+ g_pLauncherMgr->PumpWindowsMessageLoop();
+ // Escape, return, or space stops or pauses the playback
+ bool bEscPressed = ( m_bScanEsc ) ? CGEventSourceKeyState( kCGEventSourceStateCombinedSessionState, kVK_Escape ) : false;
+ bool bReturnPressed = ( m_bScanReturn ) ? CGEventSourceKeyState( kCGEventSourceStateCombinedSessionState, kVK_Return ) : false;
+ bool bSpacePressed = ( m_bScanSpace ) ? CGEventSourceKeyState( kCGEventSourceStateCombinedSessionState, kVK_Space ) : false;
+#elif defined(LINUX)
+ g_pLauncherMgr->PumpWindowsMessageLoop();
+
+ // Escape, return, or space stops or pauses the playback
+ bool bEscPressed = false;
+ bool bReturnPressed = false;
+ bool bSpacePressed = false;
+
+ g_pLauncherMgr->PeekAndRemoveKeyboardEvents( &bEscPressed, &bReturnPressed, &bSpacePressed );
+#endif
+
+ // Manual debounce of the keys, only interested in unpressed->pressed transitions
+ bool bEscEvent = ( bEscPressed != m_bEscLast ) && bEscPressed;
+ bool bReturnEvent = ( bReturnPressed != m_bReturnLast ) && bReturnPressed;
+ bool bSpaceEvent = ( bSpacePressed != m_bSpaceLast ) && bSpacePressed;
+ bool bAnyKeyEvent = bEscEvent || bReturnEvent || bSpaceEvent;
+
+ m_bEscLast = bEscPressed;
+ m_bReturnLast = bReturnPressed;
+ m_bSpaceLast = bSpacePressed;
+
+ // Are we forcing a minimum playback time?
+ // if so, no Abort or Pause events until the necessary time has elasped
+ if ( m_bForceMinPlayTime )
+ {
+ double elapsedTime = Plat_FloatTime() - m_StartTime;
+ if ( (float) elapsedTime > m_forcedMinTime )
+ {
+ m_bForceMinPlayTime = false; // turn off forced minimum
+ }
+ }
+
+ // any key events to check? ( provided minimum enforced playback has occurred )
+ if ( m_bForceMinPlayTime == false && bAnyKeyEvent )
+ {
+ // check for aborting the movie
+ if ( m_bAbortEnabled )
+ {
+ bAbortEvent = ( bAnyKeyEvent && BITFLAGS_SET( m_playbackFlags, VideoPlaybackFlags::ABORT_ON_ANY_KEY ) ) ||
+ ( bEscEvent && BITFLAGS_SET( m_playbackFlags, VideoPlaybackFlags::ABORT_ON_ESC ) ) ||
+ ( bReturnEvent && BITFLAGS_SET( m_playbackFlags, VideoPlaybackFlags::ABORT_ON_RETURN ) ) ||
+ ( bSpaceEvent && BITFLAGS_SET( m_playbackFlags, VideoPlaybackFlags::ABORT_ON_SPACE ) );
+
+ }
+
+ // check for pausing the movie?
+ if ( m_bPauseEnabled )
+ {
+ bPauseEvent = ( bAnyKeyEvent && BITFLAGS_SET( m_playbackFlags, VideoPlaybackFlags::PAUSE_ON_ANY_KEY ) ) ||
+ ( bEscEvent && BITFLAGS_SET( m_playbackFlags, VideoPlaybackFlags::PAUSE_ON_ESC ) ) ||
+ ( bReturnEvent && BITFLAGS_SET( m_playbackFlags, VideoPlaybackFlags::PAUSE_ON_RETURN ) ) ||
+ ( bSpaceEvent && BITFLAGS_SET( m_playbackFlags, VideoPlaybackFlags::PAUSE_ON_SPACE ) );
+ }
+ }
+
+ // notify if any events triggered
+ return ( bAbortEvent || bPauseEvent );
+}
+
+
+
+
+VideoResult_t CVideoCommonServices::TerminateFullScreenPlaybackInputHandler()
+{
+
+ if ( !m_bInputHandlerInitialized )
+ {
+ WarningAssert( "Not Initialized to call" );
+ return VideoResult::OPERATION_OUT_OF_SEQUENCE;
+ }
+
+#if defined ( WIN32 )
+ FreeLibrary( s_UserDLLhInst ); // and free the dll we needed
+#endif
+
+ ResetInputHandlerState();
+
+ return VideoResult::SUCCESS;
+
+}
diff --git a/video/videoservices.h b/video/videoservices.h
new file mode 100644
index 0000000..cc30b75
--- /dev/null
+++ b/video/videoservices.h
@@ -0,0 +1,198 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// The copyright to the contents herein is the property of Valve, L.L.C.
+// The contents may be used and/or copied only with the written permission of
+// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
+// the agreement/contract under which the contents have been supplied.
+//
+//=============================================================================
+
+#ifndef VIDEOSERVICES_H
+#define VIDEOSERVICES_H
+
+#if defined ( WIN32 )
+ #pragma once
+#endif
+
+
+#include "video/ivideoservices.h"
+
+#include "videosubsystem.h"
+
+
+struct CVideFileoExtInfo_t
+{
+ const char *m_pExtension; // extension including "."
+ VideoSystem_t m_VideoSystemSupporting;
+ VideoSystemFeature_t m_VideoFeaturesSupporting;
+};
+
+
+struct CActiveVideoObjectRecord_t
+{
+ void *m_pObject;
+ int m_VideoSystem;
+};
+
+
+//-----------------------------------------------------------------------------
+// Main VIDEO_SERVICES interface
+//-----------------------------------------------------------------------------
+
+class CValveVideoServices : public CTier3AppSystem< IVideoServices >
+{
+ typedef CTier3AppSystem< IVideoServices > BaseClass;
+
+ public:
+ CValveVideoServices();
+ ~CValveVideoServices();
+
+ // Inherited from IAppSystem
+ virtual bool Connect( CreateInterfaceFn factory );
+ virtual void Disconnect();
+ virtual void *QueryInterface( const char *pInterfaceName );
+ virtual InitReturnVal_t Init();
+ virtual void Shutdown();
+
+ // Inherited from IVideoServices
+
+ // Query the available video systems
+ virtual int GetAvailableVideoSystemCount();
+ virtual VideoSystem_t GetAvailableVideoSystem( int n );
+
+ virtual bool IsVideoSystemAvailable( VideoSystem_t videoSystem );
+ virtual VideoSystemStatus_t GetVideoSystemStatus( VideoSystem_t videoSystem );
+ virtual VideoSystemFeature_t GetVideoSystemFeatures( VideoSystem_t videoSystem );
+ virtual const char *GetVideoSystemName( VideoSystem_t videoSystem );
+
+ virtual VideoSystem_t FindNextSystemWithFeature( VideoSystemFeature_t features, VideoSystem_t startAfter = VideoSystem::NONE );
+
+ virtual VideoResult_t GetLastResult();
+
+ // deal with video file extensions and video system mappings
+ virtual int GetSupportedFileExtensionCount( VideoSystem_t videoSystem );
+ virtual const char *GetSupportedFileExtension( VideoSystem_t videoSystem, int extNum = 0 );
+ virtual VideoSystemFeature_t GetSupportedFileExtensionFeatures( VideoSystem_t videoSystem, int extNum = 0 );
+
+
+ virtual VideoSystem_t LocateVideoSystemForPlayingFile( const char *pFileName, VideoSystemFeature_t playMode = VideoSystemFeature::PLAY_VIDEO_FILE_IN_MATERIAL );
+ virtual VideoResult_t LocatePlayableVideoFile( const char *pSearchFileName, const char *pPathID, VideoSystem_t *pPlaybackSystem, char *pPlaybackFileName, int fileNameMaxLen, VideoSystemFeature_t playMode = VideoSystemFeature::FULL_PLAYBACK );
+
+ // Create/destroy a video material
+ virtual IVideoMaterial *CreateVideoMaterial( const char *pMaterialName, const char *pVideoFileName, const char *pPathID = nullptr,
+ VideoPlaybackFlags_t playbackFlags = VideoPlaybackFlags::DEFAULT_MATERIAL_OPTIONS,
+ VideoSystem_t videoSystem = VideoSystem::DETERMINE_FROM_FILE_EXTENSION, bool PlayAlternateIfNotAvailable = true );
+
+ virtual VideoResult_t DestroyVideoMaterial( IVideoMaterial* pVideoMaterial );
+ virtual int GetUniqueMaterialID();
+
+ // Create/destroy a video encoder
+ virtual VideoResult_t IsRecordCodecAvailable( VideoSystem_t videoSystem, VideoEncodeCodec_t codec );
+
+ virtual IVideoRecorder *CreateVideoRecorder( VideoSystem_t videoSystem );
+ virtual VideoResult_t DestroyVideoRecorder( IVideoRecorder *pVideoRecorder );
+
+ // Plays a given video file until it completes or the user presses ESC, SPACE, or ENTER
+ virtual VideoResult_t PlayVideoFileFullScreen( const char *pFileName, const char *pPathID, void *mainWindow, int windowWidth, int windowHeight, int desktopWidth, int desktopHeight, bool windowed, float forcedMinTime,
+ VideoPlaybackFlags_t playbackFlags = VideoPlaybackFlags::DEFAULT_FULLSCREEN_OPTIONS,
+ VideoSystem_t videoSystem = VideoSystem::DETERMINE_FROM_FILE_EXTENSION, bool PlayAlternateIfNotAvailable = true );
+
+ // Sets the sound devices that the video will decode to
+ virtual VideoResult_t SoundDeviceCommand( VideoSoundDeviceOperation_t operation, void *pDevice = nullptr, void *pData = nullptr, VideoSystem_t videoSystem = VideoSystem::ALL_VIDEO_SYSTEMS );
+
+ // Get the name of a codec as a string
+ const wchar_t *GetCodecName( VideoEncodeCodec_t nCodec );
+
+ private:
+
+ VideoResult_t ResolveToPlayableVideoFile( const char *pFileName, const char *pPathID, VideoSystem_t videoSystem, VideoSystemFeature_t requiredFeature, bool PlayAlternateIfNotAvailable,
+ char *pResolvedFileName, int resolvedFileNameMaxLen, VideoSystem_t *pResolvedVideoSystem );
+
+
+ VideoSystem_t LocateSystemAndFeaturesForFileName( const char *pFileName, VideoSystemFeature_t *pFeatures = nullptr, VideoSystemFeature_t requiredFeatures = VideoSystemFeature::NO_FEATURES );
+
+ bool IsMatchAnyExtension( const char *pFileName );
+
+ bool ConnectVideoLibraries( CreateInterfaceFn factory );
+ bool DisconnectVideoLibraries();
+
+ int DestroyAllVideoInterfaces();
+
+ int GetIndexForSystem( VideoSystem_t n );
+ VideoSystem_t GetSystemForIndex( int n );
+
+ VideoResult_t SetResult( VideoResult_t resultCode );
+
+ const char *GetFileExtension( const char *pFileName );
+
+
+ static const int SYSTEM_NOT_FOUND = -1;
+
+ VideoResult_t m_LastResult;
+
+ int m_nInstalledSystems;
+ bool m_bInitialized;
+
+ CSysModule *m_VideoSystemModule[VideoSystem::VIDEO_SYSTEM_COUNT];
+ IVideoSubSystem *m_VideoSystems[VideoSystem::VIDEO_SYSTEM_COUNT];
+ VideoSystem_t m_VideoSystemType[VideoSystem::VIDEO_SYSTEM_COUNT];
+ VideoSystemFeature_t m_VideoSystemFeatures[VideoSystem::VIDEO_SYSTEM_COUNT];
+
+ CUtlVector< VideoFileExtensionInfo_t > m_ExtInfo; // info about supported file extensions
+
+ CUtlVector< CActiveVideoObjectRecord_t > m_RecorderList;
+ CUtlVector< CActiveVideoObjectRecord_t > m_MaterialList;
+
+ int m_nMaterialCount;
+
+};
+
+
+class CVideoCommonServices : public IVideoCommonServices
+{
+ public:
+
+ CVideoCommonServices();
+ ~CVideoCommonServices();
+
+
+ virtual bool CalculateVideoDimensions( int videoWidth, int videoHeight, int displayWidth, int displayHeight, VideoPlaybackFlags_t playbackFlags,
+ int *pOutputWidth, int *pOutputHeight, int *pXOffset, int *pYOffset );
+
+ virtual float GetSystemVolume();
+
+ virtual VideoResult_t InitFullScreenPlaybackInputHandler( VideoPlaybackFlags_t playbackFlags, float forcedMinTime, bool windowed );
+
+ virtual bool ProcessFullScreenInput( bool &bAbortEvent, bool &bPauseEvent, bool &bQuitEvent );
+
+ virtual VideoResult_t TerminateFullScreenPlaybackInputHandler();
+
+
+ private:
+
+ void ResetInputHandlerState();
+
+ bool m_bInputHandlerInitialized;
+
+ bool m_bScanAll;
+ bool m_bScanEsc;
+ bool m_bScanReturn;
+ bool m_bScanSpace;
+ bool m_bPauseEnabled;
+ bool m_bAbortEnabled;
+ bool m_bEscLast;
+ bool m_bReturnLast;
+ bool m_bSpaceLast;
+ bool m_bForceMinPlayTime;
+
+ bool m_bWindowed;
+ VideoPlaybackFlags_t m_playbackFlags;
+ float m_forcedMinTime;
+
+ double m_StartTime;
+
+
+};
+
+
+#endif // VIDEOSERVICES_H
diff --git a/video/videosubsystem.h b/video/videosubsystem.h
new file mode 100644
index 0000000..3fe94dc
--- /dev/null
+++ b/video/videosubsystem.h
@@ -0,0 +1,97 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// The copyright to the contents herein is the property of Valve, L.L.C.
+// The contents may be used and/or copied only with the written permission of
+// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
+// the agreement/contract under which the contents have been supplied.
+//
+//=============================================================================
+
+#ifndef VIDEOSUBSYSTEM_H
+#define VIDEOSUBSYSTEM_H
+
+#if defined ( WIN32 )
+ #pragma once
+#endif
+
+#include "tier2/tier2.h"
+#include "appframework/IAppSystem.h"
+
+
+
+//-----------------------------------------------------------------------------
+// Common structure used to store supported file types
+//-----------------------------------------------------------------------------
+struct VideoFileExtensionInfo_t
+{
+ const char *m_FileExtension;
+ VideoSystem_t m_VideoSubSystem;
+ VideoSystemFeature_t m_VideoFeatures;
+};
+
+
+
+
+class IVideoCommonServices
+{
+ public:
+ virtual bool CalculateVideoDimensions( int videoWidth, int videoHeight, int displayWidth, int displayHeight, VideoPlaybackFlags_t playbackFlags,
+ int *pOutputWidth, int *pOutputHeight, int *pXOffset, int *pYOffset ) = 0;
+
+ virtual float GetSystemVolume() = 0;
+
+ virtual VideoResult_t InitFullScreenPlaybackInputHandler( VideoPlaybackFlags_t playbackFlags, float forcedMinTime, bool windowed ) = 0;
+
+ virtual bool ProcessFullScreenInput( bool &bAbortEvent, bool &bPauseEvent, bool &bQuitEvent ) = 0;
+
+ virtual VideoResult_t TerminateFullScreenPlaybackInputHandler() = 0;
+
+};
+
+
+//-----------------------------------------------------------------------------
+// Main VIDEO_SERVICES interface
+//-----------------------------------------------------------------------------
+#define VIDEO_SUBSYSTEM_INTERFACE_VERSION "IVideoSubSystem002"
+
+class IVideoSubSystem : public IAppSystem
+{
+ public:
+ // SubSystem Identification functions
+ virtual VideoSystem_t GetSystemID() = 0;
+ virtual VideoSystemStatus_t GetSystemStatus() = 0;
+ virtual VideoSystemFeature_t GetSupportedFeatures() = 0;
+ virtual const char *GetVideoSystemName() = 0;
+
+ // Setup & Shutdown Services
+ virtual bool InitializeVideoSystem( IVideoCommonServices *pCommonServices ) = 0;
+ virtual bool ShutdownVideoSystem() = 0;
+
+ virtual VideoResult_t VideoSoundDeviceCMD( VideoSoundDeviceOperation_t operation, void *pDevice, void *pData = nullptr ) = 0;
+
+ // get list of file extensions and features we support
+ virtual int GetSupportedFileExtensionCount() = 0;
+ virtual const char *GetSupportedFileExtension( int num ) = 0;
+ virtual VideoSystemFeature_t GetSupportedFileExtensionFeatures( int num ) = 0;
+
+ // Video Playback and Recording Services
+
+ virtual VideoResult_t PlayVideoFileFullScreen( const char *filename, void *mainWindow, int windowWidth, int windowHeight, int desktopWidth, int desktopHeight, bool windowed, float forcedMinTime, VideoPlaybackFlags_t playbackFlags ) = 0;
+
+ // Create/destroy a video material
+ virtual IVideoMaterial *CreateVideoMaterial( const char *pMaterialName, const char *pVideoFileName, VideoPlaybackFlags_t flags ) = 0;
+ virtual VideoResult_t DestroyVideoMaterial( IVideoMaterial* pVideoMaterial ) = 0;
+
+ // Create/destroy a video encoder
+ virtual IVideoRecorder *CreateVideoRecorder() = 0;
+ virtual VideoResult_t DestroyVideoRecorder( IVideoRecorder *pRecorder ) = 0;
+
+ virtual VideoResult_t CheckCodecAvailability( VideoEncodeCodec_t codec ) = 0;
+
+ virtual VideoResult_t GetLastResult() = 0;
+
+};
+
+
+
+#endif
diff --git a/video/webm_common.h b/video/webm_common.h
new file mode 100644
index 0000000..cd52070
--- /dev/null
+++ b/video/webm_common.h
@@ -0,0 +1,48 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// File: webm_common.h
+//
+// WebM limits and constants shared among all QuickTime functions
+//
+//=============================================================================
+
+
+#ifndef WEBM_COMMON_H
+#define WEBM_COMMON_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+// constant that define the bounds of various inputs
+static const int cMinVideoFrameWidth = 16;
+static const int cMinVideoFrameHeight = 16;
+static const int cMaxVideoFrameWidth = 2 * 2048;
+static const int cMaxVideoFrameHeight = 2 * 2048;
+
+static const int cMinFPS = 1;
+static const int cMaxFPS = 600;
+
+static const float cMinDuration = 0.016666666f; // 1/60th second
+static const float cMaxDuration = 3600.0f; // 1 Hour
+
+static const int cMinSampleRate = 11025; // 1/4 CD sample rate
+static const int cMaxSampleRate = 88200; // 2x CD rate
+
+
+//-----------------------------------------------------------------------------
+// Computes a power of two at least as big as the passed-in number
+//-----------------------------------------------------------------------------
+static inline int ComputeGreaterPowerOfTwo( int n )
+{
+ int i = 1;
+ while ( i < n )
+ {
+ i <<= 1;
+ }
+ return i;
+}
+
+
+#endif // WEBM_COMMON_H
diff --git a/video/webm_material.cpp b/video/webm_material.cpp
new file mode 100644
index 0000000..75d39bf
--- /dev/null
+++ b/video/webm_material.cpp
@@ -0,0 +1,962 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+
+#include "filesystem.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 "tier3/tier3.h"
+#include "platform.h"
+
+
+#include "quicktime_material.h"
+
+#if defined ( WIN32 )
+ #include <WinDef.h>
+ #include <../dx9sdk/include/dsound.h>
+#endif
+
+#include "tier0/memdbgon.h"
+
+
+// ===========================================================================
+// CQuicktimeMaterialRGBTextureRegenerator - Inherited from ITextureRegenerator
+// Copies and converts the buffer bits to texture bits
+// Currently only supports 32-bit BGRA
+// ===========================================================================
+CQuicktimeMaterialRGBTextureRegenerator::CQuicktimeMaterialRGBTextureRegenerator() :
+ m_SrcGWorld( nullptr ),
+ m_nSourceWidth( 0 ),
+ m_nSourceHeight( 0 )
+{
+}
+
+
+CQuicktimeMaterialRGBTextureRegenerator::~CQuicktimeMaterialRGBTextureRegenerator()
+{
+ // nothing to do
+}
+
+
+void CQuicktimeMaterialRGBTextureRegenerator::SetSourceGWorld( GWorldPtr theGWorld, int nWidth, int nHeight )
+{
+ m_SrcGWorld = theGWorld;
+ m_nSourceWidth = nWidth;
+ m_nSourceHeight = nHeight;
+}
+
+
+void CQuicktimeMaterialRGBTextureRegenerator::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect )
+{
+ AssertExit( pVTFTexture != nullptr );
+
+ // Error condition, should only have 1 frame, 1 face, 1 mip level
+ if ( ( pVTFTexture->FrameCount() > 1 ) || ( pVTFTexture->FaceCount() > 1 ) || ( pVTFTexture->MipCount() > 1 ) || ( pVTFTexture->Depth() > 1 ) )
+ {
+ WarningAssert( "Texture Properties Incorrect ");
+ memset( pVTFTexture->ImageData(), 0xAA, pVTFTexture->ComputeTotalSize() );
+ return;
+ }
+
+ // Make sure we have a valid video image source
+ if ( m_SrcGWorld == nullptr )
+ {
+ WarningAssert( "Video texture source not set" );
+ memset( pVTFTexture->ImageData(), 0xCC, pVTFTexture->ComputeTotalSize() );
+ return;
+ }
+
+ // Verify the destination texture is set up correctly
+ Assert( pVTFTexture->Format() == IMAGE_FORMAT_BGRA8888 );
+ Assert( pVTFTexture->RowSizeInBytes( 0 ) >= pVTFTexture->Width() * 4 );
+ Assert( pVTFTexture->Width() >= m_nSourceWidth );
+ Assert( pVTFTexture->Height() >= m_nSourceHeight );
+
+ // Copy directly from the Quicktime GWorld
+ PixMapHandle thePixMap = GetGWorldPixMap( m_SrcGWorld );
+
+ if ( LockPixels( thePixMap ) )
+ {
+ BYTE *pImageData = pVTFTexture->ImageData();
+ int dstStride = pVTFTexture->RowSizeInBytes( 0 );
+ BYTE *pSrcData = (BYTE*) GetPixBaseAddr( thePixMap );
+ long srcStride = QTGetPixMapHandleRowBytes( thePixMap );
+ int rowSize = m_nSourceWidth * 4;
+
+ for (int y = 0; y < m_nSourceHeight; y++ )
+ {
+ memcpy( pImageData, pSrcData, rowSize );
+ pImageData+= dstStride;
+ pSrcData+= srcStride;
+ }
+
+ UnlockPixels( thePixMap );
+ }
+ else
+ {
+ WarningAssert( "LockPixels Failed" );
+ }
+}
+
+
+void CQuicktimeMaterialRGBTextureRegenerator::Release()
+{
+ // we don't invoke the destructor here, we're not using the no-release extensions
+}
+
+
+
+// ===========================================================================
+// CQuickTimeMaterial class - creates a material, opens a QuickTime movie
+// and plays the movie onto the material
+// ===========================================================================
+
+//-----------------------------------------------------------------------------
+// CQuickTimeMaterial Constructor
+//-----------------------------------------------------------------------------
+CQuickTimeMaterial::CQuickTimeMaterial() :
+ m_pFileName( nullptr ),
+ m_MovieGWorld( nullptr ),
+ m_QTMovie( nullptr ),
+ m_AudioContext( nullptr ),
+ m_bInitCalled( false )
+{
+ Reset();
+}
+
+
+//-----------------------------------------------------------------------------
+// CQuickTimeMaterial Destructor
+//-----------------------------------------------------------------------------
+CQuickTimeMaterial::~CQuickTimeMaterial()
+{
+ Reset();
+}
+
+
+void CQuickTimeMaterial::Reset()
+{
+ SetQTFileName( nullptr );
+
+ DestroyProceduralTexture();
+ DestroyProceduralMaterial();
+
+ m_TexCordU = 0.0f;
+ m_TexCordV = 0.0f;
+
+ m_VideoFrameWidth = 0;
+ m_VideoFrameHeight = 0;
+
+ m_PlaybackFlags = VideoPlaybackFlags::NO_PLAYBACK_OPTIONS;
+
+ m_bMovieInitialized = false;
+ m_bMoviePlaying = false;
+ m_bMovieFinishedPlaying = false;
+ m_bMoviePaused = false;
+ m_bLoopMovie = false;
+
+ m_bHasAudio = false;
+ m_bMuted = false;
+
+ m_CurrentVolume = 0.0f;
+
+ m_QTMovieTimeScale = 0;
+ m_QTMovieDuration = 0;
+ m_QTMovieDurationinSec = 0.0f;
+ m_QTMovieFrameRate.SetFPS( 0, false );
+
+ SAFE_RELEASE_AUDIOCONTEXT( m_AudioContext );
+ SAFE_DISPOSE_GWORLD( m_MovieGWorld );
+ SAFE_DISPOSE_MOVIE( m_QTMovie );
+
+ m_LastResult = VideoResult::SUCCESS;
+}
+
+
+void CQuickTimeMaterial::SetQTFileName( const char *theQTMovieFileName )
+{
+ SAFE_DELETE_ARRAY( m_pFileName );
+
+ if ( theQTMovieFileName != nullptr )
+ {
+ AssertMsg( V_strlen( theQTMovieFileName ) <= MAX_QT_FILENAME_LEN, "Bad Quicktime Movie Filename" );
+ m_pFileName = COPY_STRING( theQTMovieFileName );
+ }
+
+}
+
+
+VideoResult_t CQuickTimeMaterial::SetResult( VideoResult_t status )
+{
+ m_LastResult = status;
+ return status;
+}
+
+
+//-----------------------------------------------------------------------------
+// Video information functions
+//-----------------------------------------------------------------------------
+
+
+//-----------------------------------------------------------------------------
+// Returns the resolved filename of the video, as it might differ from
+// what the user supplied, (also with absolute path)
+//-----------------------------------------------------------------------------
+const char *CQuickTimeMaterial::GetVideoFileName()
+{
+ return m_pFileName;
+}
+
+
+VideoFrameRate_t &CQuickTimeMaterial::GetVideoFrameRate()
+{
+ return m_QTMovieFrameRate;
+}
+
+
+VideoResult_t CQuickTimeMaterial::GetLastResult()
+{
+ return m_LastResult;
+}
+
+
+//-----------------------------------------------------------------------------
+// Audio Functions
+//-----------------------------------------------------------------------------
+bool CQuickTimeMaterial::HasAudio()
+{
+ return m_bHasAudio;
+}
+
+
+bool CQuickTimeMaterial::SetVolume( float fVolume )
+{
+ clamp( fVolume, 0.0f, 1.0f );
+
+ m_CurrentVolume = fVolume;
+
+ if ( m_AudioContext != nullptr && m_bHasAudio )
+ {
+ short movieVolume = (short) ( m_CurrentVolume * 256.0f );
+
+ SetMovieVolume( m_QTMovie, movieVolume );
+
+ SetResult( VideoResult::SUCCESS );
+ return true;
+ }
+
+ SetResult( VideoResult::AUDIO_ERROR_OCCURED );
+ return false;
+}
+
+
+float CQuickTimeMaterial::GetVolume()
+{
+ return m_CurrentVolume;
+}
+
+
+void CQuickTimeMaterial::SetMuted( bool bMuteState )
+{
+ AssertExitFunc( m_bMoviePlaying, SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE) );
+
+ SetResult( VideoResult::SUCCESS );
+
+ if ( bMuteState == m_bMuted ) // no change?
+ {
+ return;
+ }
+
+ m_bMuted = bMuteState;
+
+ if ( m_bHasAudio )
+ {
+ OSStatus result = SetMovieAudioMute( m_QTMovie, m_bMuted, 0 );
+ AssertExitFunc( result == noErr, SetResult( VideoResult::AUDIO_ERROR_OCCURED) );
+ }
+
+ SetResult( VideoResult::SUCCESS );
+}
+
+
+bool CQuickTimeMaterial::IsMuted()
+{
+ return m_bMuted;
+}
+
+
+VideoResult_t CQuickTimeMaterial::SoundDeviceCommand( VideoSoundDeviceOperation_t operation, void *pDevice, void *pData )
+{
+ AssertExitV( m_bMovieInitialized || m_bMoviePlaying, VideoResult::OPERATION_OUT_OF_SEQUENCE );
+
+ switch( operation )
+ {
+ // On win32, we try and create an audio context from a GUID
+ case VideoSoundDeviceOperation::SET_DIRECT_SOUND_DEVICE:
+ {
+#if defined ( WIN32 )
+ SAFE_RELEASE_AUDIOCONTEXT( m_AudioContext );
+ return ( CreateMovieAudioContext( m_bHasAudio, m_QTMovie, &m_AudioContext ) ? SetResult( VideoResult::SUCCESS ) : SetResult( VideoResult::AUDIO_ERROR_OCCURED ) );
+#else
+ // On any other OS, we don't support this operation
+ return SetResult( VideoResult::OPERATION_NOT_SUPPORTED );
+#endif
+ }
+ case VideoSoundDeviceOperation::SET_SOUND_MANAGER_DEVICE:
+ {
+#if defined ( OSX )
+ SAFE_RELEASE_AUDIOCONTEXT( m_AudioContext );
+ return ( CreateMovieAudioContext( m_bHasAudio, m_QTMovie, &m_AudioContext ) ? SetResult( VideoResult::SUCCESS ) : SetResult( VideoResult::AUDIO_ERROR_OCCURED ) );
+#else
+ // On any other OS, we don't support this operation
+ return SetResult( VideoResult::OPERATION_NOT_SUPPORTED );
+#endif
+ }
+
+ case VideoSoundDeviceOperation::SET_LIB_AUDIO_DEVICE:
+ case VideoSoundDeviceOperation::HOOK_X_AUDIO:
+ case VideoSoundDeviceOperation::SET_MILES_SOUND_DEVICE:
+ {
+ return SetResult( VideoResult::OPERATION_NOT_SUPPORTED );
+ }
+ default:
+ {
+ return SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ }
+ }
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Initializes the video material
+//-----------------------------------------------------------------------------
+bool CQuickTimeMaterial::Init( const char *pMaterialName, const char *pFileName, VideoPlaybackFlags_t flags )
+{
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( IS_NOT_EMPTY( pFileName ) );
+ AssertExitF( m_bInitCalled == false );
+
+ m_PlaybackFlags = flags;
+
+ OpenQTMovie( pFileName ); // Open up the Quicktime file
+
+ if ( !m_bMovieInitialized )
+ {
+ return false; // Something bad happened when we went to open
+ }
+
+ // Now we can properly setup our regenerators
+ m_TextureRegen.SetSourceGWorld( m_MovieGWorld, m_VideoFrameWidth, m_VideoFrameHeight );
+
+ CreateProceduralTexture( pMaterialName );
+ CreateProceduralMaterial( pMaterialName );
+
+ // Start movie playback
+ if ( !BITFLAGS_SET( m_PlaybackFlags, VideoPlaybackFlags::DONT_AUTO_START_VIDEO ) )
+ {
+ StartVideo();
+ }
+
+ m_bInitCalled = true; // Look, if you only got one shot...
+
+ return true;
+}
+
+
+void CQuickTimeMaterial::Shutdown( void )
+{
+ StopVideo();
+ Reset();
+}
+
+
+//-----------------------------------------------------------------------------
+// Video playback state functions
+//-----------------------------------------------------------------------------
+bool CQuickTimeMaterial::IsVideoReadyToPlay()
+{
+ return m_bMovieInitialized;
+}
+
+
+bool CQuickTimeMaterial::IsVideoPlaying()
+{
+ return m_bMoviePlaying;
+}
+
+
+//-----------------------------------------------------------------------------
+// Checks to see if the video has a new frame ready to be rendered and
+// downloaded into the texture and eventually display
+//-----------------------------------------------------------------------------
+bool CQuickTimeMaterial::IsNewFrameReady( void )
+{
+ // Are we waiting to start playing the first frame? if so, tell them we are ready!
+ if ( m_bMovieInitialized == true )
+ {
+ return true;
+ }
+
+ // We better be playing the movie
+ AssertExitF( m_bMoviePlaying );
+
+ // paused?
+ if ( m_bMoviePaused )
+ {
+ return false;
+ }
+
+ TimeValue curMovieTime = GetMovieTime( m_QTMovie, nullptr );
+
+ if ( curMovieTime >= m_QTMovieDuration || m_NextInterestingTimeToPlay == NO_MORE_INTERESTING_TIMES )
+ {
+ // if we are looping, we have another frame, otherwise no
+ return m_bLoopMovie;
+ }
+
+ // Enough time passed to get to next frame??
+ if ( curMovieTime < m_NextInterestingTimeToPlay )
+ {
+ // nope.. use the previous frame
+ return false;
+ }
+
+ // we have a new frame we want then..
+ return true;
+}
+
+
+bool CQuickTimeMaterial::IsFinishedPlaying()
+{
+ return m_bMovieFinishedPlaying;
+}
+
+
+void CQuickTimeMaterial::SetLooping( bool bLoopVideo )
+{
+ m_bLoopMovie = bLoopVideo;
+}
+
+
+bool CQuickTimeMaterial::IsLooping()
+{
+ return m_bLoopMovie;
+}
+
+
+void CQuickTimeMaterial::SetPaused( bool bPauseState )
+{
+ if ( !m_bMoviePlaying || m_bMoviePaused == bPauseState )
+ {
+ Assert( m_bMoviePlaying );
+ return;
+ }
+
+ if ( bPauseState ) // Pausing the movie?
+ {
+ // Save off current time and set paused state
+ m_MoviePauseTime = GetMovieTime( m_QTMovie, nullptr );
+ StopMovie( m_QTMovie );
+ }
+ else // unpausing the movie
+ {
+ // Reset the movie to the paused time
+ SetMovieTimeValue( m_QTMovie, m_MoviePauseTime );
+ StartMovie( m_QTMovie );
+ Assert( GetMoviesError() == noErr );
+ }
+
+ m_bMoviePaused = bPauseState;
+}
+
+
+bool CQuickTimeMaterial::IsPaused()
+{
+ return ( m_bMoviePlaying ) ? m_bMoviePaused : false;
+}
+
+
+// Begins playback of the movie
+bool CQuickTimeMaterial::StartVideo()
+{
+ if ( !m_bMovieInitialized )
+ {
+ Assert( false );
+ SetResult( VideoResult::OPERATION_ALREADY_PERFORMED );
+ return false;
+ }
+
+ // Start the movie playing at the first frame
+ SetMovieTimeValue( m_QTMovie, m_MovieFirstFrameTime );
+ Assert( GetMoviesError() == noErr );
+
+ StartMovie( m_QTMovie );
+ Assert( GetMoviesError() == noErr );
+
+ // Transition to playing state
+ m_bMovieInitialized = false;
+ m_bMoviePlaying = true;
+
+ // Deliberately set the next interesting time to the current time to
+ // insure that the ::update() call causes the textures to be downloaded
+ m_NextInterestingTimeToPlay = m_MovieFirstFrameTime;
+ Update();
+
+ return true;
+}
+
+
+// stops movie for good, frees resources, but retains texture & material of last frame rendered
+bool CQuickTimeMaterial::StopVideo()
+{
+ if ( !m_bMoviePlaying )
+ {
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ return false;
+ }
+
+ StopMovie( m_QTMovie );
+
+ m_bMoviePlaying = false;
+ m_bMoviePaused = false;
+ m_bMovieFinishedPlaying = true;
+
+ // free resources
+ CloseQTFile();
+
+ SetResult( VideoResult::SUCCESS );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates our scene
+// Output : true = movie playing ok, false = time to end movie
+// supposed to be: Returns true on a new frame of video being downloaded into the texture
+//-----------------------------------------------------------------------------
+bool CQuickTimeMaterial::Update( void )
+{
+ AssertExitF( m_bMoviePlaying );
+
+ OSType qTypes[1] = { VisualMediaCharacteristic };
+
+ // are we paused? can't update if so...
+ if ( m_bMoviePaused )
+ {
+ return true; // reuse the last frame
+ }
+
+ // Get current time in the movie
+ TimeValue curMovieTime = GetMovieTime( m_QTMovie, nullptr );
+
+ // Did we hit the end of the movie?
+ if ( curMovieTime >= m_QTMovieDuration )
+ {
+ // If we're not looping, then report that we are done updating
+ if ( m_bLoopMovie == false )
+ {
+ StopVideo();
+ return false;
+ }
+
+ // Reset the movie to the start time
+ SetMovieTimeValue( m_QTMovie, m_MovieFirstFrameTime );
+ AssertExitF( GetMoviesError() == noErr );
+
+ // Assure fall through to render a new frame
+ m_NextInterestingTimeToPlay = m_MovieFirstFrameTime;
+ }
+
+ // Are we on the last frame of the movie? (but not past the end of any audio?)
+ if ( m_NextInterestingTimeToPlay == NO_MORE_INTERESTING_TIMES )
+ {
+ return true; // reuse last frame
+ }
+
+ // Enough time passed to get to next frame?
+ if ( curMovieTime < m_NextInterestingTimeToPlay )
+ {
+ // nope.. use the previous frame
+ return true;
+ }
+
+ // move the movie along
+ UpdateMovie( m_QTMovie );
+ AssertExitF( GetMoviesError() == noErr );
+
+ // Let QuickTime render the frame
+ MoviesTask( m_QTMovie, 0L );
+ AssertExitF( GetMoviesError() == noErr );
+
+ // Get the next frame after the current time (the movie may have advanced a bit during UpdateMovie() and MovieTasks()
+ GetMovieNextInterestingTime( m_QTMovie, nextTimeStep | nextTimeEdgeOK, 1, qTypes, GetMovieTime( m_QTMovie, nullptr ), fixed1, &m_NextInterestingTimeToPlay, nullptr );
+
+ // hit the end of the movie?
+ if ( GetMoviesError() == invalidTime || m_NextInterestingTimeToPlay == END_OF_QUICKTIME_MOVIE )
+ {
+ m_NextInterestingTimeToPlay = NO_MORE_INTERESTING_TIMES;
+ }
+
+ // Regenerate our texture, it'll grab from the GWorld Directly
+ m_Texture->Download();
+
+ SetResult( VideoResult::SUCCESS );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the material
+//-----------------------------------------------------------------------------
+IMaterial *CQuickTimeMaterial::GetMaterial()
+{
+ return m_Material;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the texcoord range
+//-----------------------------------------------------------------------------
+void CQuickTimeMaterial::GetVideoTexCoordRange( float *pMaxU, float *pMaxV )
+{
+ AssertExit( pMaxU != nullptr && pMaxV != nullptr );
+
+ if ( m_Texture == nullptr ) // no texture?
+ {
+ *pMaxU = *pMaxV = 1.0f;
+ return;
+ }
+
+ *pMaxU = m_TexCordU;
+ *pMaxV = m_TexCordV;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the frame size of the QuickTime Video in pixels
+//-----------------------------------------------------------------------------
+void CQuickTimeMaterial::GetVideoImageSize( int *pWidth, int *pHeight )
+{
+ Assert( pWidth != nullptr && pHeight != nullptr );
+
+ *pWidth = m_VideoFrameWidth;
+ *pHeight = m_VideoFrameHeight;
+}
+
+
+float CQuickTimeMaterial::GetVideoDuration()
+{
+ return m_QTMovieDurationinSec;
+}
+
+
+int CQuickTimeMaterial::GetFrameCount()
+{
+ return m_QTMovieFrameCount;
+}
+
+
+//-----------------------------------------------------------------------------
+// Sets the frame for an QuickTime Material (use instead of SetTime)
+//-----------------------------------------------------------------------------
+bool CQuickTimeMaterial::SetFrame( int FrameNum )
+{
+ if ( !m_bMoviePlaying )
+ {
+ Assert( false );
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ return false;
+ }
+
+ float theTime = (float) FrameNum * m_QTMovieFrameRate.GetFPS();
+ return SetTime( theTime );
+}
+
+
+int CQuickTimeMaterial::GetCurrentFrame()
+{
+ AssertExitV( m_bMoviePlaying, -1 );
+
+ TimeValue curTime = m_bMoviePaused ? m_MoviePauseTime : GetMovieTime( m_QTMovie, nullptr );
+
+ return curTime / m_QTMovieFrameRate.GetUnitsPerFrame();
+}
+
+
+float CQuickTimeMaterial::GetCurrentVideoTime()
+{
+ AssertExitV( m_bMoviePlaying, -1.0f );
+
+ TimeValue curTime = m_bMoviePaused ? m_MoviePauseTime : GetMovieTime( m_QTMovie, nullptr );
+
+ return curTime / m_QTMovieFrameRate.GetUnitsPerSecond();
+}
+
+
+bool CQuickTimeMaterial::SetTime( float flTime )
+{
+ AssertExitF( m_bMoviePlaying );
+ AssertExitF( flTime >= 0 && flTime < m_QTMovieDurationinSec );
+
+ TimeValue newTime = (TimeValue) ( flTime * m_QTMovieFrameRate.GetUnitsPerSecond() + 0.5f) ;
+
+ clamp( newTime, m_MovieFirstFrameTime, m_QTMovieDuration );
+
+ // Are we paused?
+ if ( m_bMoviePaused )
+ {
+ m_MoviePauseTime = newTime;
+ return true;
+ }
+
+ TimeValue curMovieTime = GetMovieTime( m_QTMovie, nullptr );
+
+ // Don't stop and reset movie if we are within 1 frame of the requested time
+ if ( newTime <= curMovieTime - m_QTMovieFrameRate.GetUnitsPerFrame() || newTime >= curMovieTime + m_QTMovieFrameRate.GetUnitsPerFrame() )
+ {
+ // Reset the movie to the requested time
+ StopMovie( m_QTMovie );
+ SetMovieTimeValue( m_QTMovie, newTime );
+ StartMovie( m_QTMovie );
+
+ Assert( GetMoviesError() == noErr );
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Initializes, shuts down the procedural texture
+//-----------------------------------------------------------------------------
+void CQuickTimeMaterial::CreateProceduralTexture( const char *pTextureName )
+{
+ AssertIncRange( m_VideoFrameWidth, cMinVideoFrameWidth, cMaxVideoFrameWidth );
+ AssertIncRange( m_VideoFrameHeight, cMinVideoFrameHeight, cMaxVideoFrameHeight );
+ AssertStr( pTextureName );
+
+ // Either make the texture the same dimensions as the video,
+ // or choose power-of-two textures which are at least as big as the video
+ bool actualSizeTexture = BITFLAGS_SET( m_PlaybackFlags, VideoPlaybackFlags::TEXTURES_ACTUAL_SIZE );
+
+ int nWidth = ( actualSizeTexture ) ? ALIGN_VALUE( m_VideoFrameWidth, TEXTURE_SIZE_ALIGNMENT ) : ComputeGreaterPowerOfTwo( m_VideoFrameWidth );
+ int nHeight = ( actualSizeTexture ) ? ALIGN_VALUE( m_VideoFrameHeight, TEXTURE_SIZE_ALIGNMENT ) : ComputeGreaterPowerOfTwo( m_VideoFrameHeight );
+
+ // initialize the procedural texture as 32-it RGBA, w/o mipmaps
+ m_Texture.InitProceduralTexture( pTextureName, "VideoCacheTextures", nWidth, nHeight,
+ IMAGE_FORMAT_BGRA8888, TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_NOMIP |
+ TEXTUREFLAGS_PROCEDURAL | TEXTUREFLAGS_SINGLECOPY | TEXTUREFLAGS_NOLOD );
+
+ // Use this to get the updated frame from the remote connection
+ m_Texture->SetTextureRegenerator( &m_TextureRegen /* , false */ );
+
+ // compute the texcoords
+ int nTextureWidth = m_Texture->GetActualWidth();
+ int nTextureHeight = m_Texture->GetActualHeight();
+
+ m_TexCordU = ( nTextureWidth > 0 ) ? (float) m_VideoFrameWidth / (float) nTextureWidth : 0.0f;
+ m_TexCordV = ( nTextureHeight > 0 ) ? (float) m_VideoFrameHeight / (float) nTextureHeight : 0.0f;
+}
+
+
+void CQuickTimeMaterial::DestroyProceduralTexture()
+{
+ if ( m_Texture != nullptr )
+ {
+ // DO NOT Call release on the Texture Regenerator, as it will destroy this object! bad bad bad
+ // instead we tell it to assign a NULL regenerator and flag it to not call release
+ m_Texture->SetTextureRegenerator( nullptr /*, false */ );
+ // Texture, texture go away...
+ m_Texture.Shutdown( true );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Initializes, shuts down the procedural material
+//-----------------------------------------------------------------------------
+void CQuickTimeMaterial::CreateProceduralMaterial( const char *pMaterialName )
+{
+ // create keyvalues if necessary
+ KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" );
+ {
+ pVMTKeyValues->SetString( "$basetexture", m_Texture->GetName() );
+ pVMTKeyValues->SetInt( "$nobasetexture", 1 );
+ pVMTKeyValues->SetInt( "$nofog", 1 );
+ pVMTKeyValues->SetInt( "$spriteorientation", 3 );
+ pVMTKeyValues->SetInt( "$translucent", 1 );
+ pVMTKeyValues->SetInt( "$nolod", 1 );
+ pVMTKeyValues->SetInt( "$nomip", 1 );
+ pVMTKeyValues->SetInt( "$gammacolorread", 0 );
+ }
+
+ // FIXME: gak, this is backwards. Why doesn't the material just see that it has a funky basetexture?
+ m_Material.Init( pMaterialName, pVMTKeyValues );
+ m_Material->Refresh();
+}
+
+
+void CQuickTimeMaterial::DestroyProceduralMaterial()
+{
+ // Store the internal material pointer for later use
+ IMaterial *pMaterial = m_Material;
+ m_Material.Shutdown();
+ materials->UncacheUnusedMaterials();
+
+ // Now be sure to free that material because we don't want to reference it again later, we'll recreate it!
+ if ( pMaterial != nullptr )
+ {
+ pMaterial->DeleteIfUnreferenced();
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Opens a movie file using quicktime
+//-----------------------------------------------------------------------------
+void CQuickTimeMaterial::OpenQTMovie( const char *theQTMovieFileName )
+{
+ AssertExit( IS_NOT_EMPTY( theQTMovieFileName ) );
+
+ // Set graphics port
+#if defined ( WIN32 )
+ SetGWorld ( (CGrafPtr) GetNativeWindowPort( nil ), nil );
+#elif defined ( OSX )
+ SetGWorld( nil, nil );
+#endif
+
+ SetQTFileName( theQTMovieFileName );
+
+ Handle MovieFileDataRef = nullptr;
+ OSType MovieFileDataRefType = 0;
+
+ CFStringRef imageStrRef = CFStringCreateWithCString ( NULL, theQTMovieFileName, 0 );
+ AssertExitFunc( imageStrRef != nullptr, SetResult( VideoResult::SYSTEM_ERROR_OCCURED ) );
+
+ OSErr status = QTNewDataReferenceFromFullPathCFString( imageStrRef, (QTPathStyle) kQTNativeDefaultPathStyle, 0, &MovieFileDataRef, &MovieFileDataRefType );
+ AssertExitFunc( status == noErr, SetResult( VideoResult::FILE_ERROR_OCCURED ) );
+
+ CFRelease( imageStrRef );
+
+ status = NewMovieFromDataRef( &m_QTMovie, newMovieActive, nil, MovieFileDataRef, MovieFileDataRefType );
+ SAFE_DISPOSE_HANDLE( MovieFileDataRef );
+
+ if ( status != noErr )
+ {
+ Assert( false );
+ Reset();
+ SetResult( VideoResult::VIDEO_ERROR_OCCURED );
+ return;
+ }
+
+ // disabling audio?
+ if ( BITFLAGS_SET( m_PlaybackFlags, VideoPlaybackFlags::NO_AUDIO ) )
+ {
+ m_bHasAudio = false;
+ }
+ else
+ {
+ // does movie have audio?
+ Track audioTrack = GetMovieIndTrackType( m_QTMovie, 1, SoundMediaType, movieTrackMediaType );
+ m_bHasAudio = ( audioTrack != nullptr );
+ }
+
+ // Now we need to extract the time info from the QT Movie
+ m_QTMovieTimeScale = GetMovieTimeScale( m_QTMovie );
+ m_QTMovieDuration = GetMovieDuration( m_QTMovie );
+
+ // compute movie duration
+ m_QTMovieDurationinSec = float ( double( m_QTMovieDuration ) / double( m_QTMovieTimeScale ) );
+ if ( !MovieGetStaticFrameRate( m_QTMovie, m_QTMovieFrameRate ) )
+ {
+ WarningAssert( "Couldn't Get Frame Rate" );
+ }
+
+ // and get an estimated frame count
+ m_QTMovieFrameCount = m_QTMovieDuration / m_QTMovieTimeScale;
+
+ if ( m_QTMovieFrameRate.GetUnitsPerSecond() == m_QTMovieTimeScale )
+ {
+ m_QTMovieFrameCount = m_QTMovieDuration / m_QTMovieFrameRate.GetUnitsPerFrame();
+ }
+ else
+ {
+ m_QTMovieFrameCount = (int) ( (float) m_QTMovieDurationinSec * m_QTMovieFrameRate.GetFPS() + 0.5f );
+ }
+
+ // what size do we set the output rect to?
+ GetMovieNaturalBoundsRect(m_QTMovie, &m_QTMovieRect);
+
+ m_VideoFrameWidth = m_QTMovieRect.right;
+ m_VideoFrameHeight = m_QTMovieRect.bottom;
+
+ // Sanity check...
+ AssertExitFunc( m_QTMovieRect.top == 0 && m_QTMovieRect.left == 0 &&
+ m_QTMovieRect.right >= cMinVideoFrameWidth && m_QTMovieRect.right <= cMaxVideoFrameWidth &&
+ m_QTMovieRect.bottom >= cMinVideoFrameHeight && m_QTMovieRect.bottom <= cMaxVideoFrameHeight &&
+ m_QTMovieRect.right % 4 == 0,
+ SetResult( VideoResult::VIDEO_ERROR_OCCURED ) );
+
+ // Setup the QuiuckTime Graphics World for the Movie
+ status = QTNewGWorld( &m_MovieGWorld, k32BGRAPixelFormat, &m_QTMovieRect, nil, nil, 0 );
+ AssertExit( status == noErr );
+
+ // Setup the playback gamma according to the convar
+ SetGWorldDecodeGamma( m_MovieGWorld, VideoPlaybackGamma::USE_GAMMA_CONVAR );
+
+ // Assign the GWorld to this movie
+ SetMovieGWorld( m_QTMovie, m_MovieGWorld, nil );
+
+ // Setup Movie Audio, unless suppressed
+ if ( !CreateMovieAudioContext( m_bHasAudio, m_QTMovie, &m_AudioContext, true, &m_CurrentVolume ) )
+ {
+ SetResult( VideoResult::AUDIO_ERROR_OCCURED );
+ WarningAssert( "Couldn't Set Audio" );
+ }
+
+ // Get the time of the first frame
+ OSType qTypes[1] = { VisualMediaCharacteristic };
+ short qFlags = nextTimeStep | nextTimeEdgeOK; // use nextTimeStep instead of nextTimeMediaSample for MPEG 1-2 compatibility
+
+ GetMovieNextInterestingTime( m_QTMovie, qFlags, 1, qTypes, (TimeValue) 0, fixed1, &m_MovieFirstFrameTime, NULL );
+ AssertExitFunc( GetMoviesError() == noErr, SetResult( VideoResult::VIDEO_ERROR_OCCURED ) );
+
+ // Preroll the movie
+ if ( BITFLAGS_SET( m_PlaybackFlags, VideoPlaybackFlags::PRELOAD_VIDEO ) )
+ {
+ Fixed playRate = GetMoviePreferredRate( m_QTMovie );
+ status = PrerollMovie( m_QTMovie, m_MovieFirstFrameTime, playRate );
+ AssertExitFunc( status == noErr, SetResult( VideoResult::VIDEO_ERROR_OCCURED ) );
+ }
+
+ m_bMovieInitialized = true;
+}
+
+
+void CQuickTimeMaterial::CloseQTFile()
+{
+ if ( m_QTMovie == nullptr )
+ {
+ return;
+ }
+
+ SAFE_RELEASE_AUDIOCONTEXT( m_AudioContext );
+ SAFE_DISPOSE_GWORLD( m_MovieGWorld );
+ SAFE_DISPOSE_MOVIE( m_QTMovie );
+
+ SetQTFileName( nullptr );
+}
+
+
diff --git a/video/webm_material.h b/video/webm_material.h
new file mode 100644
index 0000000..a80b644
--- /dev/null
+++ b/video/webm_material.h
@@ -0,0 +1,221 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+
+#ifndef QUICKTIME_MATERIAL_H
+#define QUICKTIME_MATERIAL_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Forward declarations
+//-----------------------------------------------------------------------------
+class IFileSystem;
+class IMaterialSystem;
+class CQuickTimeMaterial;
+
+//-----------------------------------------------------------------------------
+// Global interfaces - you already did the needed includes, right?
+//-----------------------------------------------------------------------------
+extern IFileSystem *g_pFileSystem;
+extern IMaterialSystem *materials;
+
+//-----------------------------------------------------------------------------
+// Quicktime includes
+//-----------------------------------------------------------------------------
+#if defined ( OSX )
+ #include <quicktime/QTML.h>
+ #include <quicktime/Movies.h>
+ #include <quicktime/MediaHandlers.h>
+#elif defined ( WIN32 )
+ #include <QTML.h>
+ #include <Movies.h>
+ #include <windows.h>
+ #include <MediaHandlers.h>
+#elif
+ #error "Quicktime not supported on this target platform"
+#endif
+
+
+#include "video/ivideoservices.h"
+
+#include "video_macros.h"
+#include "quicktime_common.h"
+
+#include "materialsystem/itexture.h"
+#include "materialsystem/imaterialsystem.h"
+#include "materialsystem/MaterialSystemUtil.h"
+
+
+// -----------------------------------------------------------------------------
+// Texture regenerator - callback to get new movie pixels into the texture
+// -----------------------------------------------------------------------------
+class CQuicktimeMaterialRGBTextureRegenerator : public ITextureRegenerator
+{
+ public:
+ CQuicktimeMaterialRGBTextureRegenerator();
+ ~CQuicktimeMaterialRGBTextureRegenerator();
+
+ void SetSourceGWorld( GWorldPtr theGWorld, int nWidth, int nHeight );
+
+ // Inherited from ITextureRegenerator
+ virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect );
+ virtual void Release();
+
+ private:
+ GWorldPtr m_SrcGWorld;
+ int m_nSourceWidth;
+ int m_nSourceHeight;
+};
+
+
+
+// -----------------------------------------------------------------------------
+// Class used to play a QuickTime video onto a texture
+// -----------------------------------------------------------------------------
+class CQuickTimeMaterial : public IVideoMaterial
+{
+ public:
+ CQuickTimeMaterial();
+ ~CQuickTimeMaterial();
+
+ static const int MAX_QT_FILENAME_LEN = 255;
+ static const int MAX_MATERIAL_NAME_LEN = 255;
+ static const int TEXTURE_SIZE_ALIGNMENT = 8;
+
+ // Initializes, shuts down the material
+ bool Init( const char *pMaterialName, const char *pFileName, VideoPlaybackFlags_t flags );
+ void Shutdown();
+
+ // Video information functions
+ virtual const char *GetVideoFileName(); // Gets the file name of the video this material is playing
+ virtual VideoResult_t GetLastResult(); // Gets detailed info on the last operation
+
+ virtual VideoFrameRate_t &GetVideoFrameRate(); // Returns the frame rate of the associated video in FPS
+
+ // Audio Functions
+ virtual bool HasAudio(); // Query if the video has an audio track
+
+ virtual bool SetVolume( float fVolume ); // Adjust the playback volume
+ virtual float GetVolume(); // Query the current volume
+ virtual void SetMuted( bool bMuteState ); // Mute/UnMutes the audio playback
+ virtual bool IsMuted(); // Query muted status
+
+ virtual VideoResult_t SoundDeviceCommand( VideoSoundDeviceOperation_t operation, void *pDevice = nullptr, void *pData = nullptr ); // Assign Sound Device for this Video Material
+
+ // Video playback state functions
+ virtual bool IsVideoReadyToPlay(); // Queries if the video material was initialized successfully and is ready for playback, but not playing or finished
+ virtual bool IsVideoPlaying(); // Is the video currently playing (and needs update calls, etc)
+ virtual bool IsNewFrameReady(); // Do we have a new frame to get & display?
+ virtual bool IsFinishedPlaying(); // Have we reached the end of the movie
+
+ virtual bool StartVideo(); // Starts the video playing
+ virtual bool StopVideo(); // Terminates the video playing
+
+ virtual void SetLooping( bool bLoopVideo ); // Sets the video to loop (or not)
+ virtual bool IsLooping(); // Queries if the video is looping
+
+ virtual void SetPaused( bool bPauseState ); // Pauses or Unpauses video playback
+ virtual bool IsPaused(); // Queries if the video is paused
+
+ // Position in playback functions
+ virtual float GetVideoDuration(); // Returns the duration of the associated video in seconds
+ virtual int GetFrameCount(); // Returns the total number of (unique) frames in the video
+
+ virtual bool SetFrame( int FrameNum ); // Sets the current frame # in the video to play next
+ virtual int GetCurrentFrame(); // Gets the current frame # for the video playback, 0 Based
+
+ virtual bool SetTime( float flTime ); // Sets the video playback to specified time (in seconds)
+ virtual float GetCurrentVideoTime(); // Gets the current time in the video playback
+
+ // Update function
+ virtual bool Update(); // Updates the video frame to reflect the time passed, true = new frame available
+
+ // Material / Texture Info functions
+ virtual IMaterial *GetMaterial(); // Gets the IMaterial associated with an video material
+
+ virtual void GetVideoTexCoordRange( float *pMaxU, float *pMaxV ) ; // Returns the max texture coordinate of the video portion of the material surface ( 0.0, 0.0 to U, V )
+ virtual void GetVideoImageSize( int *pWidth, int *pHeight ); // Returns the frame size of the Video Image Frame in pixels ( the stored in a subrect of the material itself)
+
+
+
+ private:
+ friend class CQuicktimeMaterialRGBTextureRegenerator;
+
+ void Reset(); // clears internal state
+ void SetQTFileName( const char *theQTMovieFileName );
+ VideoResult_t SetResult( VideoResult_t status );
+
+ // Initializes, shuts down the video stream
+ void OpenQTMovie( const char *theQTMovieFileName );
+ void CloseQTFile();
+
+ // Initializes, shuts down the procedural texture
+ void CreateProceduralTexture( const char *pTextureName );
+ void DestroyProceduralTexture();
+
+ // Initializes, shuts down the procedural material
+ void CreateProceduralMaterial( const char *pMaterialName );
+ void DestroyProceduralMaterial();
+
+ CQuicktimeMaterialRGBTextureRegenerator m_TextureRegen;
+
+ VideoResult_t m_LastResult;
+
+ CMaterialReference m_Material; // Ref to Material used for rendering the video frame
+ CTextureReference m_Texture; // Ref to the renderable texture which contains the most recent video frame (in a sub-rect)
+
+ float m_TexCordU; // Max U texture coordinate of the texture sub-rect which holds the video frame
+ float m_TexCordV; // Max V texture coordinate of the texture sub-rect which holds the video frame
+
+ int m_VideoFrameWidth; // Size of the movie frame in pixels
+ int m_VideoFrameHeight;
+
+ char *m_pFileName; // resolved filename of the movie being played
+ VideoPlaybackFlags_t m_PlaybackFlags; // option flags user supplied
+
+ bool m_bInitCalled;
+ bool m_bMovieInitialized;
+ bool m_bMoviePlaying;
+ bool m_bMovieFinishedPlaying;
+ bool m_bMoviePaused;
+ bool m_bLoopMovie;
+
+ bool m_bHasAudio;
+ bool m_bMuted;
+
+ float m_CurrentVolume;
+
+ // QuickTime Stuff
+ Movie m_QTMovie;
+
+ TimeScale m_QTMovieTimeScale; // Units per second
+ TimeValue m_QTMovieDuration; // movie duration in TimeScale Units Per Second
+ float m_QTMovieDurationinSec; // movie duration in seconds
+ VideoFrameRate_t m_QTMovieFrameRate; // Frame Rate of movie
+ int m_QTMovieFrameCount;
+
+ Rect m_QTMovieRect;
+ GWorldPtr m_MovieGWorld;
+
+ QTAudioContextRef m_AudioContext;
+
+ TimeValue m_MovieFirstFrameTime;
+ TimeValue m_NextInterestingTimeToPlay;
+ TimeValue m_MoviePauseTime;
+
+};
+
+
+
+
+
+
+
+#endif // QUICKTIME_MATERIAL_H
diff --git a/video/webm_recorder.cpp b/video/webm_recorder.cpp
new file mode 100644
index 0000000..e5d46c3
--- /dev/null
+++ b/video/webm_recorder.cpp
@@ -0,0 +1,811 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//----------------------------------------------------------------------------------------
+
+#define WIN32_LEAN_AND_MEAN
+
+#include "webm_video.h"
+#include "webm_recorder.h"
+#include "filesystem.h"
+
+#ifdef _WIN32
+#include "windows.h"
+#endif
+
+
+// Bitrate table for various resolutions, used to compute estimated size of files as well
+// as to set the WebM codec.
+
+struct WebMEncodingDataRateInfo_t
+{
+ int m_XResolution;
+ int m_YResolution;
+ float m_MinDataRate; // in KBits / second
+ float m_MaxDataRate; // in KBits / second
+ float m_MinAudioDataRate; // in KBits / second
+ float m_MaxAudioDataRate; // in KBits / second
+};
+
+// Quality is passed into us as a number between 0 and 100, we use that to scale between the min and max bitrates.
+static WebMEncodingDataRateInfo_t s_WebMEncodeRates[] =
+{
+ { 320, 240, 1000, 1000, 128, 128},
+ { 640, 960, 2000, 2000, 128, 128},
+ { 960, 640, 2000, 2000, 128, 128},
+ { 720, 480, 2500, 2500, 128, 128},
+ {1280, 720, 5000, 5000, 384, 384},
+ {1920, 1080, 8000, 8000, 384, 384},
+};
+
+
+
+
+
+// ===========================================================================
+// CWebMVideoRecorder class - implements IVideoRecorder interface for
+// WebM and buffers commands to the actual encoder object
+// ===========================================================================
+CWebMVideoRecorder::CWebMVideoRecorder() :
+ m_LastResult( VideoResult::SUCCESS ),
+ m_bHasAudio( false ),
+ m_bMovieFinished( false ),
+ m_SrcImageYV12Buffer( NULL ),
+ m_nFramesAdded( 0 ),
+ m_nAudioFramesAdded( 0 ),
+ m_nSamplesAdded( 0 ),
+ m_audioChannels( 2 ),
+ m_audioSampleRate( 44100 ),
+ m_audioBitDepth( 16 ),
+ m_audioSampleGroupSize( 0 )
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::CWebMVideoRecorder() \n");
+#endif
+}
+
+
+CWebMVideoRecorder::~CWebMVideoRecorder()
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::~CWebMVideoRecorder() \n");
+#endif
+ if (m_SrcImageYV12Buffer)
+ {
+ vpx_img_free(m_SrcImageYV12Buffer);
+ m_SrcImageYV12Buffer = NULL;
+ }
+}
+
+
+// All webm movies use 1000000 as a scale
+#define WEBM_TIMECODE_SCALE 1000000
+// All webm movies using 128kbps as audio bitrate
+#define WEBM_AUDIO_BITRATE 128000
+
+
+bool CWebMVideoRecorder::CreateNewMovieFile( const char *pFilename, bool hasAudio )
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::CreateNewMovieFile() \n");
+#endif
+
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( IS_NOT_EMPTY( pFilename ) );
+
+ SetResult( VideoResult::OPERATION_ALREADY_PERFORMED );
+
+ // crete the webm file
+ if (!m_mkvWriter.Open(pFilename))
+ {
+ SetResult( VideoResult::OPERATION_ALREADY_PERFORMED );
+ return false;
+ }
+
+ // init the webm file and write out the header
+ m_mkvMuxerSegment.Init(&m_mkvWriter);
+ m_mkvMuxerSegment.set_mode(mkvmuxer::Segment::kFile);
+ m_mkvMuxerSegment.OutputCues(true);
+
+
+ mkvmuxer::SegmentInfo* const mkvInfo = m_mkvMuxerSegment.GetSegmentInfo();
+ mkvInfo->set_timecode_scale(WEBM_TIMECODE_SCALE);
+
+ // get the app name
+ char appname[ 256 ];
+ KeyValues *modinfo = new KeyValues( "ModInfo" );
+
+ if ( modinfo->LoadFromFile( g_pFullFileSystem, "gameinfo.txt" ) )
+ Q_strncpy( appname, modinfo->GetString( "game" ), sizeof( appname ) );
+ else
+ Q_strncpy( appname, "Source1 Game", sizeof( appname ) );
+
+ modinfo->deleteThis();
+ modinfo = NULL;
+
+ mkvInfo->set_writing_app(appname);
+
+ m_bHasAudio = hasAudio;
+
+ SetResult( VideoResult::SUCCESS );
+
+ return true;
+}
+
+
+bool CWebMVideoRecorder::SetMovieVideoParameters( VideoEncodeCodec_t theCodec, int videoQuality, int movieFrameWidth, int movieFrameHeight, VideoFrameRate_t movieFPS, VideoEncodeGamma_t gamma )
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::SetMovieVideoParameters()\n");
+#endif
+
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( IS_IN_RANGECOUNT( theCodec, VideoEncodeCodec::DEFAULT_CODEC, VideoEncodeCodec::CODEC_COUNT ) );
+ AssertExitF( IS_IN_RANGE( videoQuality, VideoEncodeQuality::MIN_QUALITY, VideoEncodeQuality::MAX_QUALITY ) );
+ AssertExitF( IS_IN_RANGE( movieFrameWidth, cMinVideoFrameWidth, cMaxVideoFrameWidth ) && IS_IN_RANGE( movieFrameHeight, cMinVideoFrameHeight, cMaxVideoFrameHeight ) );
+ AssertExitF( IS_IN_RANGE( movieFPS.GetFPS(), cMinFPS, cMaxFPS ) );
+ AssertExitF( IS_IN_RANGECOUNT( gamma, VideoEncodeGamma::NO_GAMMA_ADJUST, VideoEncodeGamma::GAMMA_COUNT ) );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+
+ // Get the defaults for the WebM encoder
+ if (vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &m_vpxConfig, 0) != VPX_CODEC_OK)
+ {
+ SetResult( VideoResult::INITIALIZATION_ERROR_OCCURED );
+ return false;
+ }
+
+ m_MovieFrameWidth = movieFrameWidth;
+ m_MovieFrameHeight = movieFrameHeight;
+
+ m_MovieGamma = gamma;
+
+ m_vpxConfig.g_h = movieFrameHeight;
+ m_vpxConfig.g_w = movieFrameWidth;
+
+ // How many threads should we use? How about 1 per logical processor
+ const CPUInformation *pi = GetCPUInformation();
+ m_vpxConfig.g_threads = pi->m_nLogicalProcessors;
+
+ // FPS
+ m_vpxConfig.g_timebase.den = movieFPS.GetUnitsPerSecond();
+ m_vpxConfig.g_timebase.num = movieFPS.GetUnitsPerFrame();
+
+ m_MovieRecordFPS = movieFPS;
+
+ m_DurationPerFrame = m_MovieRecordFPS.GetUnitsPerFrame();
+ m_MovieTimeScale = m_MovieRecordFPS.GetUnitsPerSecond();
+
+ // Compute how long each frame is, in nanoseconds
+ m_FrameDuration = (unsigned long)(((double)m_DurationPerFrame*(double)1000000000)/(double)m_MovieTimeScale);
+
+ // Set the bitrate for this size and level of quality
+ m_vpxConfig.rc_target_bitrate = GetVideoDataRate(videoQuality, movieFrameWidth, movieFrameHeight);
+
+
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg( "Video Frame Rate = %f FPS\n %d time units per second\n %d time units per frame\n", m_MovieRecordFPS.GetFPS(), m_MovieRecordFPS.GetUnitsPerSecond(), m_MovieRecordFPS.GetUnitsPerFrame() );
+ if ( m_MovieRecordFPS.IsNTSCRate() )
+ Msg( " IS CONSIDERED NTSC RATE\n");
+ Msg( "Using %d threads for WebM encoding\n", m_vpxConfig.g_threads);
+ Msg( "MovieTimeScale is being set to %d\nDuration Per Frame is %d\n", m_MovieTimeScale, m_DurationPerFrame );
+ Msg( "Time per frame in nanoseconds %d\n\n", m_FrameDuration);
+
+#endif
+
+ // Init the codec
+ if (vpx_codec_enc_init(&m_vpxContext, vpx_codec_vp8_cx(), &m_vpxConfig, 0) != VPX_CODEC_OK)
+ {
+ SetResult( VideoResult::INITIALIZATION_ERROR_OCCURED );
+ return false;
+ }
+
+ // add the video track
+ m_vid_track = m_mkvMuxerSegment.AddVideoTrack(static_cast<int>(m_vpxConfig.g_w),
+ static_cast<int>(m_vpxConfig.g_h),
+ 1);
+
+ mkvmuxer::VideoTrack* const video =
+ static_cast<mkvmuxer::VideoTrack*>(
+ m_mkvMuxerSegment.GetTrackByNumber(m_vid_track));
+
+ video->set_display_width(m_vpxConfig.g_w);
+ video->set_display_height(m_vpxConfig.g_h);
+ video->set_frame_rate(movieFPS.GetFPS());
+
+ return true;
+}
+
+
+bool CWebMVideoRecorder::SetMovieSourceImageParameters( VideoEncodeSourceFormat_t srcImageFormat, int imgWidth, int imgHeight )
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::SetMovieSourceImageParameters()\n");
+#endif
+
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( IS_IN_RANGECOUNT( srcImageFormat, VideoEncodeSourceFormat::VIDEO_FORMAT_FIRST, VideoEncodeSourceFormat::VIDEO_FORMAT_COUNT ) );
+ // WebM Recorder only supports BGRA_32BIT and BGR_24BIT
+ AssertExitF( (srcImageFormat == VideoEncodeSourceFormat::BGRA_32BIT) ||
+ (srcImageFormat == VideoEncodeSourceFormat::BGR_24BIT) );
+
+ AssertExitF( IS_IN_RANGE( imgWidth, cMinVideoFrameWidth, cMaxVideoFrameWidth ) && IS_IN_RANGE( imgHeight, cMinVideoFrameHeight, cMaxVideoFrameHeight ) );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+
+ m_SrcImageWidth = imgWidth;
+ m_SrcImageHeight = imgHeight;
+ m_SrcImageFormat = srcImageFormat;
+
+ // Setup the buffers for encoding the frame. WebM requires a frame in YV12 format
+ m_SrcImageYV12Buffer = vpx_img_alloc(NULL, VPX_IMG_FMT_YV12, m_SrcImageWidth, m_SrcImageHeight, 1);
+
+ // Set Cues element attributes
+ mkvmuxer::Cues* const cues = m_mkvMuxerSegment.GetCues();
+ cues->set_output_block_number(1);
+ m_mkvMuxerSegment.CuesTrack(m_vid_track);
+
+ SetResult( VideoResult::SUCCESS );
+
+ return true;
+}
+
+
+bool CWebMVideoRecorder::SetMovieSourceAudioParameters( AudioEncodeSourceFormat_t srcAudioFormat, int audioSampleRate, AudioEncodeOptions_t audioOptions, int audioSampleGroupSize )
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::SetMovieSourceAudioParameters()\n");
+#endif
+
+ SetResult( VideoResult::ILLEGAL_OPERATION );
+ AssertExitF( m_bHasAudio );
+
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( IS_IN_RANGECOUNT( srcAudioFormat, AudioEncodeSourceFormat::AUDIO_NONE, AudioEncodeSourceFormat::AUDIO_FORMAT_COUNT ) );
+ AssertExitF( audioSampleRate == 0 || IS_IN_RANGE( audioSampleRate, cMinSampleRate, cMaxSampleRate ) );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+
+ m_audioSampleRate = audioSampleRate;
+ m_audioSampleGroupSize = audioSampleGroupSize;
+
+ // now setup the audio
+ vorbis_info_init(&m_vi);
+
+ // We are always using 128kbps for the audio
+ int ret=vorbis_encode_init(&m_vi,m_audioChannels,m_audioSampleRate,-1,WEBM_AUDIO_BITRATE,-1);
+ if (ret)
+ {
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ return false;
+ }
+ /* set up the analysis state and auxiliary encoding storage */
+ vorbis_comment_init(&m_vc);
+
+ // get the app name
+ char appname[ 256 ];
+ KeyValues *modinfo = new KeyValues( "ModInfo" );
+
+ if ( modinfo->LoadFromFile( g_pFullFileSystem, "gameinfo.txt" ) )
+ Q_strncpy( appname, modinfo->GetString( "game" ), sizeof( appname ) );
+ else
+ Q_strncpy( appname, "Source1 Game", sizeof( appname ) );
+
+ modinfo->deleteThis();
+ modinfo = NULL;
+
+ vorbis_comment_add_tag(&m_vc,"ENCODER",appname);
+
+ vorbis_analysis_init(&m_vd,&m_vi);
+ vorbis_block_init(&m_vd,&m_vb);
+
+ // setup the audio track
+ m_aud_track = m_mkvMuxerSegment.AddAudioTrack(static_cast<int>(m_audioSampleRate),
+ static_cast<int>(m_audioChannels),
+ 0);
+ if (!m_aud_track)
+ {
+ printf("\n Could not add audio track.\n");
+ return false;
+ }
+
+ mkvmuxer::AudioTrack* const audio =
+ static_cast<mkvmuxer::AudioTrack*>(
+ m_mkvMuxerSegment.GetTrackByNumber(m_aud_track));
+ if (!audio)
+ {
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ return false;
+ }
+
+ /* Vorbis streams begin with three headers; the initial header (with
+ most of the codec setup parameters) which is mandated by the Ogg
+ bitstream spec. The second header holds any comment fields. The
+ third header holds the bitstream codebook. We merely need to
+ make the headers, then pass them to libvorbis one at a time;
+ libvorbis handles the additional Ogg bitstream constraints */
+
+ {
+ ogg_packet ident_packet;
+ ogg_packet comments_packet;
+ ogg_packet setup_packet;
+ int iHeaderLength;
+ uint8 *privateHeader=NULL;
+ uint8 *pbPrivateHeader=NULL;
+
+ vorbis_analysis_headerout(&m_vd,&m_vc,&ident_packet,&comments_packet,&setup_packet);
+ iHeaderLength = 3 + ident_packet.bytes + comments_packet.bytes + setup_packet.bytes;
+ privateHeader = new uint8[iHeaderLength];
+ pbPrivateHeader = privateHeader;
+
+ *pbPrivateHeader++ = 2; // number of headers - 1
+ *pbPrivateHeader++ = (uint8)ident_packet.bytes;
+ *pbPrivateHeader++ = (uint8)comments_packet.bytes;
+
+ memcpy(pbPrivateHeader, ident_packet.packet, ident_packet.bytes);
+ pbPrivateHeader+= ident_packet.bytes;
+
+ memcpy(pbPrivateHeader, comments_packet.packet, comments_packet.bytes);
+ pbPrivateHeader+= comments_packet.bytes;
+
+ memcpy(pbPrivateHeader, setup_packet.packet, setup_packet.bytes);
+ pbPrivateHeader+= setup_packet.bytes;
+
+ audio->SetCodecPrivate(privateHeader,iHeaderLength);
+ delete [] privateHeader;
+ }
+
+ SetResult( VideoResult::SUCCESS );
+ return true;
+}
+
+
+bool CWebMVideoRecorder::IsReadyToRecord()
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::IsReadyToRecord()\n");
+#endif
+
+ return ( m_SrcImageYV12Buffer != NULL && !m_bMovieFinished);
+}
+
+
+VideoResult_t CWebMVideoRecorder::GetLastResult()
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::GetLastResult()\n");
+#endif
+
+ return m_LastResult;
+}
+
+
+void CWebMVideoRecorder::SetResult( VideoResult_t resultCode )
+{
+ m_LastResult = resultCode;
+}
+
+void CWebMVideoRecorder::ConvertBGRAToYV12( void *pFrameBuffer, int nStrideAdjustBytes, vpx_image_t *m_SrcImageYV12Buffer, bool fIncludesAlpha )
+{
+ int iSrcBytesPerPixel;
+
+ // Handle 32-bit with alpha and 24-bit
+ if (fIncludesAlpha)
+ iSrcBytesPerPixel = 4;
+ else
+ iSrcBytesPerPixel = 3;
+
+ int srcStride = m_SrcImageWidth * iSrcBytesPerPixel + nStrideAdjustBytes;
+ int iX,iY;
+ byte *pSrc;
+ byte *pDstY,*pDstU,*pDstV;
+ byte r,g,b,a;
+ byte y,u,v;
+
+ // This isn't fast or good, but it works and that's good enough for a first pass
+ pSrc = (byte *)pFrameBuffer;
+
+ // YV12 has a complete frame of Y, followed by half sized U and V
+ pDstY = m_SrcImageYV12Buffer->planes[0];
+ pDstU = m_SrcImageYV12Buffer->planes[1];
+ pDstV = m_SrcImageYV12Buffer->planes[2];
+
+
+ for (iY=0;iY<m_MovieFrameHeight;iY++)
+ {
+ for(iX=0;iX<m_MovieFrameWidth;iX++)
+ {
+ b = pSrc[iSrcBytesPerPixel*iX+0];
+ g = pSrc[iSrcBytesPerPixel*iX+1];
+ r = pSrc[iSrcBytesPerPixel*iX+2];
+ if (fIncludesAlpha)
+ a = pSrc[iSrcBytesPerPixel*iX+3];
+
+ y = (byte)((66*r + 129*g + 25*b + 128) >> 8) + 16;
+
+ pDstY[iX] = y;
+ if ((iY%2 == 0) && (iX%2 == 0))
+ {
+ u = (byte)((-38*r - 74*g + 112*b + 128) >> 8) + 128;
+ v = (byte)((112*r - 94*g - 18*b + 128) >> 8) + 128;
+
+ pDstU[iX/2] = u;
+ pDstV[iX/2] = v;
+ }
+ }
+ // next row, using strides
+ pDstY += m_SrcImageYV12Buffer->stride[0];
+ if ((iY%2) == 0)
+ {
+ pDstU += m_SrcImageYV12Buffer->stride[1];
+ pDstV += m_SrcImageYV12Buffer->stride[2];
+ }
+ pSrc += srcStride;
+ }
+
+}
+
+bool CWebMVideoRecorder::AppendVideoFrame( void *pFrameBuffer, int nStrideAdjustBytes )
+{
+ uint64 time_ns;
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::AppendVideoFrame()\n");
+#endif
+
+// $$$DEBUG Dump the Frame $$$DEBUG
+// If you are trying to debug how the webm libraries work it's very difficult to use the full TF2 replay system as a testbed
+// so this section of code here and in the AppendAudioSamples writes out the data TF2 would send to the video recorder to
+// plain files on the disc. You can then use those files with an extracted framework to work with the webm libraries
+#ifdef NEVER
+ {
+ FILE *fp=NULL;
+ char rgch[256];
+ int i,j;
+ byte *pByte;
+
+ sprintf(rgch, "./frames/vid_%d", m_nFramesAdded);
+ fp = fopen(rgch, "wb");
+
+ pByte = (byte *)pFrameBuffer;
+ for (i=0;i<m_MovieFrameHeight;i++)
+ {
+ fwrite(pByte, 4, m_MovieFrameWidth, fp);
+ pByte += (m_MovieFrameWidth*4 + nStrideAdjustBytes);
+ }
+ fclose(fp);
+
+ }
+#endif
+
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( pFrameBuffer != nullptr );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( IsReadyToRecord() );
+
+ // Convert the frame in pFrameBuffer into YV12 and add it to the stream
+ // only convert BGRA_32BIT and BGR_24BIT right now
+ switch(m_SrcImageFormat)
+ {
+ case VideoEncodeSourceFormat::BGR_24BIT:
+ ConvertBGRAToYV12(pFrameBuffer, nStrideAdjustBytes, m_SrcImageYV12Buffer, false);
+ break;
+ case VideoEncodeSourceFormat::BGRA_32BIT:
+ ConvertBGRAToYV12(pFrameBuffer, nStrideAdjustBytes, m_SrcImageYV12Buffer, true);
+ break;
+ default:
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ return false;
+ }
+
+
+ // Compress it with the webm codec
+
+ time_ns = ((uint64)m_FrameDuration*(uint64)(m_nFramesAdded+1));
+
+ vpx_codec_err_t vpxError = vpx_codec_encode(&m_vpxContext, m_SrcImageYV12Buffer, time_ns, m_FrameDuration, 0, 0);
+
+ if (vpxError != VPX_CODEC_OK)
+ {
+ SetResult( VideoResult::VIDEO_ERROR_OCCURED );
+ return false;
+ }
+
+ // Add this frame to the stream
+ vpx_codec_iter_t vpxIter = NULL;
+ const vpx_codec_cx_pkt_t *vpxPacket;
+ bool bKeyframe=false;
+
+ while ( ( vpxPacket = vpx_codec_get_cx_data(&m_vpxContext, &vpxIter)) != NULL )
+ {
+ if (vpxPacket->kind == VPX_CODEC_CX_FRAME_PKT)
+ {
+ // Extract if this is a keyframe from the first packet of data for each frame
+ bKeyframe = vpxPacket->data.frame.flags & VPX_FRAME_IS_KEY;
+
+ m_mkvMuxerSegment.AddFrame((const uint8 *)vpxPacket->data.frame.buf, vpxPacket->data.frame.sz, m_vid_track,
+ time_ns, bKeyframe);
+ }
+
+ }
+ m_nFramesAdded++;
+
+ SetResult( VideoResult::SUCCESS );
+
+ return true;
+}
+
+
+bool CWebMVideoRecorder::FlushAudioSamples()
+{
+ ogg_packet op;
+
+ // See if there are any samples available
+
+ while (vorbis_analysis_blockout(&m_vd, &m_vb) == 1)
+ {
+ int status_analysis = vorbis_analysis(&m_vb, NULL);
+ if (status_analysis)
+ {
+ return false;
+ }
+ status_analysis = vorbis_bitrate_addblock(&m_vb);
+ if (status_analysis)
+ {
+ return false;
+ }
+ while ((status_analysis = vorbis_bitrate_flushpacket(&m_vd, &op)) > 0)
+ {
+ // Add this packet to the webm stream
+ uint64 time_ns = ((uint64)m_FrameDuration*(uint64)(m_nFramesAdded+1));
+
+ if (!m_mkvMuxerSegment.AddFrame(op.packet,
+ op.bytes,
+ m_aud_track,
+ time_ns,
+ true))
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool CWebMVideoRecorder::AppendAudioSamples( void *pSampleBuffer, size_t sampleSize )
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::AppendAudioSamples()\n");
+#endif
+
+ SetResult( VideoResult::ILLEGAL_OPERATION );
+ AssertExitF( m_bHasAudio );
+
+ SetResult( VideoResult::BAD_INPUT_PARAMETERS );
+ AssertExitF( pSampleBuffer != nullptr );
+
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+ AssertExitF( IsReadyToRecord() );
+
+// $$$DEBUG Dump the Audio $$$DEBUG
+// The audio version of the dumping of video/audio data to files for debugging
+// You may need to change the path to put these files where you want them.
+#ifdef NEVER
+ {
+ FILE *fp=NULL;
+ char rgch[256];
+ int i,j;
+ byte *pByte;
+ static int i_AudSample_batch=0;
+ static int i_AudSample_frame=-1;
+
+ if (m_nFramesAdded != i_AudSample_frame)
+ i_AudSample_batch = 0;
+
+ i_AudSample_frame = m_nFramesAdded;
+
+ sprintf(rgch, "./frames/aud_%d_%d", i_AudSample_frame, i_AudSample_batch);
+ fp = fopen(rgch, "wb");
+
+ // Size
+ fwrite(&sampleSize, sizeof(sampleSize), 1, fp);
+
+ // Data
+ pByte = (byte *)pSampleBuffer;
+ fwrite(pByte, sampleSize, 1, fp);
+
+ fclose(fp);
+
+ i_AudSample_batch++;
+
+ }
+#endif
+ int num_blocks = sampleSize / ((m_audioBitDepth/8)*m_audioChannels);
+ float **buffer=vorbis_analysis_buffer(&m_vd,num_blocks);
+
+ // Deinterleave input samples, convert them to float, and store them in
+ // buffer
+ const int16* pPCMsamples = (int16*)pSampleBuffer;
+
+ for (int i = 0; i < num_blocks; ++i)
+ {
+ for (int c = 0; c < m_audioChannels; ++c)
+ {
+ buffer[c][i] = pPCMsamples[i * m_audioChannels + c] / 32768.f;
+ }
+ }
+
+ vorbis_analysis_wrote(&m_vd, num_blocks);
+
+ FlushAudioSamples();
+
+ return true;
+}
+
+
+int CWebMVideoRecorder::GetFrameCount()
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::GetFrameCount()\n");
+#endif
+
+ return m_nFramesAdded;
+}
+
+
+int CWebMVideoRecorder::GetSampleCount()
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::GetSampleCount()\n");
+#endif
+
+// return ( m_pEncoder == nullptr ) ? 0 : m_pEncoder->GetSampleCount();
+ return true;
+}
+
+
+VideoFrameRate_t CWebMVideoRecorder::GetFPS()
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::GetFPS()\n");
+#endif
+
+ return m_MovieRecordFPS;
+}
+
+
+int CWebMVideoRecorder::GetSampleRate()
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::GetSampleRate()\n");
+#endif
+// return ( m_pEncoder == nullptr ) ? 0 : m_pEncoder->GetSampleRate();
+ return true;
+}
+
+
+bool CWebMVideoRecorder::AbortMovie()
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::AbortMovie()\n");
+#endif
+ SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
+// AssertExitF( m_pEncoder != nullptr && !m_bMovieFinished );
+
+ m_bMovieFinished = true;
+// return m_pEncoder->AbortMovie();
+ return true;
+ }
+
+
+bool CWebMVideoRecorder::FinishMovie( bool SaveMovieToDisk )
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::FinishMovie()\n");
+#endif
+ // Have to finish out the compress with a NULL frame
+ // Compress it with the webm codec
+ m_nFramesAdded++;
+ uint64 time_ns = ((uint64)m_FrameDuration*(uint64)(m_nFramesAdded+1));
+
+ vpx_codec_err_t vpxError = vpx_codec_encode(&m_vpxContext, NULL, time_ns, m_FrameDuration, 0, 0);
+
+ if (vpxError != VPX_CODEC_OK)
+ {
+ return false;
+ }
+
+ // Add this frame to the stream
+ vpx_codec_iter_t vpxIter = NULL;
+ const vpx_codec_cx_pkt_t *vpxPacket;
+
+ while ( ( vpxPacket = vpx_codec_get_cx_data(&m_vpxContext, &vpxIter) ) != NULL )
+ {
+ if (vpxPacket->kind == VPX_CODEC_CX_FRAME_PKT)
+ {
+ uint64 time_ns;
+ bool bKeyframe=false;
+
+ // Extract if this is a keyframe from the first packet of data for each frame
+ bKeyframe = vpxPacket->data.frame.flags & VPX_FRAME_IS_KEY;
+ time_ns = ((uint64)m_FrameDuration*(uint64)m_nFramesAdded);
+
+ m_mkvMuxerSegment.AddFrame((const uint8 *)vpxPacket->data.frame.buf, vpxPacket->data.frame.sz, m_vid_track, time_ns, bKeyframe);
+ }
+ }
+
+ m_mkvMuxerSegment.Finalize();
+ m_mkvWriter.Close();
+
+ vorbis_dsp_clear(&m_vd);
+ vorbis_block_clear(&m_vb);
+ vorbis_info_clear(&m_vi);
+
+ m_bMovieFinished = true;
+
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::FinishMovie() movie encoded.\n");
+ Msg("Total frames written: %d Time for movie in nanoseconds: %lu\n", m_nFramesAdded, ((unsigned long)m_nFramesAdded*m_FrameDuration));
+#endif
+
+ SetResult( VideoResult::SUCCESS );
+
+ return true;
+}
+
+bool CWebMVideoRecorder::EstimateMovieFileSize( size_t *pEstSize, int movieWidth, int movieHeight, VideoFrameRate_t movieFps, float movieDuration, VideoEncodeCodec_t theCodec, int videoQuality, AudioEncodeSourceFormat_t srcAudioFormat, int audioSampleRate )
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::EstimateMovieFileSize()\n");
+#endif
+ float fVidRate;
+ float fAudRate;
+ float movieDurationInSeconds;
+
+ fVidRate = GetVideoDataRate(videoQuality, movieWidth, movieHeight);
+ fAudRate = GetAudioDataRate(videoQuality, movieWidth, movieHeight);
+ movieDurationInSeconds = movieDuration;
+
+ // data rates is in killobits/second so convert to bytes/second
+ *pEstSize = (size_t)((fVidRate*1000*movieDurationInSeconds/8) + (fAudRate*1000*movieDuration*movieDurationInSeconds/8));
+
+ SetResult( VideoResult::SUCCESS );
+ return true;
+}
+
+
+float CWebMVideoRecorder::GetVideoDataRate( int quality, int width, int height )
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::GetDataRate()\n");
+#endif
+ for(int i = 0; i< ARRAYSIZE( s_WebMEncodeRates ); i++ )
+ {
+ if (s_WebMEncodeRates[i].m_XResolution == width && s_WebMEncodeRates[i].m_YResolution == height)
+ {
+ return s_WebMEncodeRates[i].m_MinDataRate + (((s_WebMEncodeRates[i].m_MaxDataRate - s_WebMEncodeRates[i].m_MinDataRate)*(float)quality)/100.0);
+ }
+ }
+ // Didn't find the resolution, odd
+ Msg("Unable to find WebM resolution (%d, %d) at quality %d\n", width, height, quality);
+ // Default to 2kb/s
+ return 2000.0f;
+}
+
+float CWebMVideoRecorder::GetAudioDataRate( int quality, int width, int height )
+{
+#ifdef LOG_ENCODER_OPERATIONS
+ Msg("CWebMVideoRecorder::GetDataRate()\n");
+#endif
+ for(int i = 0; i< ARRAYSIZE( s_WebMEncodeRates ); i++ )
+ {
+ if (s_WebMEncodeRates[i].m_XResolution == width && s_WebMEncodeRates[i].m_YResolution == height)
+ {
+ return s_WebMEncodeRates[i].m_MinAudioDataRate + (((s_WebMEncodeRates[i].m_MaxAudioDataRate - s_WebMEncodeRates[i].m_MinAudioDataRate)*(float)quality)/100.0);
+ }
+ }
+ // Didn't find the resolution, odd
+ Msg("Unable to find WebM resolution (%d, %d) at quality %d\n", width, height, quality);
+
+ // Default to 128kb/s for audio
+ return 128.0f;
+}
diff --git a/video/webm_recorder.h b/video/webm_recorder.h
new file mode 100644
index 0000000..1c68a69
--- /dev/null
+++ b/video/webm_recorder.h
@@ -0,0 +1,111 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//----------------------------------------------------------------------------------------
+
+#ifndef WEBM_RECORDER_H
+#define WEBM_RECORDER_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+//--------------------------------------------------------------------------------
+//#include ""
+
+#include "video/ivideoservices.h"
+
+#include "video_macros.h"
+#include "webm_common.h"
+
+// comment out to prevent logging of creation data
+//#define LOG_ENCODER_OPERATIONS
+
+#if defined( LOG_ENCODER_OPERATIONS ) || defined( LOG_ENCODER_AUDIO_OPERATIONS ) || defined ( LOG_FRAMES_TO_TGA ) || defined ( ENABLE_EXTERNAL_ENCODER_LOGGING )
+ #include <filesystem.h>
+#endif
+
+
+class CWebMVideoRecorder : public IVideoRecorder
+{
+public:
+ CWebMVideoRecorder();
+ ~CWebMVideoRecorder();
+
+ virtual bool EstimateMovieFileSize( size_t *pEstSize, int movieWidth, int movieHeight, VideoFrameRate_t movieFps, float movieDuration, VideoEncodeCodec_t theCodec, int videoQuality, AudioEncodeSourceFormat_t srcAudioFormat = AudioEncodeSourceFormat::AUDIO_NONE, int audioSampleRate = 0 );
+
+ virtual bool CreateNewMovieFile( const char *pFilename, bool hasAudioTrack = false );
+
+ virtual bool SetMovieVideoParameters( VideoEncodeCodec_t theCodec, int videoQuality, int movieFrameWidth, int movieFrameHeight, VideoFrameRate_t movieFPS, VideoEncodeGamma_t gamma = VideoEncodeGamma::NO_GAMMA_ADJUST );
+ virtual bool SetMovieSourceImageParameters( VideoEncodeSourceFormat_t srcImageFormat, int imgWidth, int imgHeight );
+ virtual bool SetMovieSourceAudioParameters( AudioEncodeSourceFormat_t srcAudioFormat = AudioEncodeSourceFormat::AUDIO_NONE, int audioSampleRate = 0, AudioEncodeOptions_t audioOptions = AudioEncodeOptions::NO_AUDIO_OPTIONS, int audioSampleGroupSize = 0 );
+
+ virtual bool IsReadyToRecord();
+ virtual VideoResult_t GetLastResult();
+
+ virtual bool AppendVideoFrame( void *pFrameBuffer, int nStrideAdjustBytes = 0 );
+ virtual bool AppendAudioSamples( void *pSampleBuffer, size_t sampleSize );
+
+ virtual int GetFrameCount();
+ virtual int GetSampleCount();
+ virtual int GetSampleRate();
+ virtual VideoFrameRate_t GetFPS();
+
+ virtual bool AbortMovie();
+ virtual bool FinishMovie( bool SaveMovieToDisk = true );
+
+private:
+ bool FlushAudioSamples();
+ void ConvertBGRAToYV12( void *pFrameBuffer, int nStrideAdjustBytes, vpx_image_t *m_SrcImageYV12Buffer, bool fIncludesAlpha );
+ void SetResult( VideoResult_t resultCode );
+
+ float GetVideoDataRate( int quality, int width, int height );
+ float GetAudioDataRate( int quality, int width, int height );
+
+ VideoResult_t m_LastResult;
+ bool m_bHasAudio;
+ bool m_bMovieFinished;
+
+ int m_nFramesAdded;
+ int m_nAudioFramesAdded;
+ int m_nSamplesAdded;
+
+ VideoFrameRate_t m_MovieRecordFPS;
+ int m_MovieTimeScale;
+ int m_DurationPerFrame;
+
+ unsigned long m_FrameDuration;
+
+ int m_MovieFrameWidth;
+ int m_MovieFrameHeight;
+
+ vpx_image_t *m_SrcImageYV12Buffer;
+
+ VideoEncodeGamma_t m_MovieGamma;
+
+ VideoEncodeSourceFormat_t m_SrcImageFormat;
+ int m_SrcImageWidth;
+ int m_SrcImageHeight;
+
+ // WebM VPX
+ vpx_codec_ctx_t m_vpxContext;
+ vpx_codec_enc_cfg_t m_vpxConfig;
+ mkvmuxer::MkvWriter m_mkvWriter;
+ mkvmuxer::Segment m_mkvMuxerSegment;
+ uint64 m_vid_track;
+
+ // Vorbis audio
+ uint64 m_aud_track;
+ int m_audioChannels;
+ int m_audioSampleRate;
+ int m_audioSampleGroupSize;
+ int m_audioBitDepth;
+
+ vorbis_info m_vi;
+ vorbis_dsp_state m_vd;
+ vorbis_block m_vb;
+ vorbis_comment m_vc;
+};
+
+
+
+#endif // WEBM_RECORDER_H
diff --git a/video/webm_video.cpp b/video/webm_video.cpp
new file mode 100644
index 0000000..fb9fab8
--- /dev/null
+++ b/video/webm_video.cpp
@@ -0,0 +1,353 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#include "webm_video.h"
+#include "webm_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"
+
+
+#include "tier0/memdbgon.h"
+
+
+// ===========================================================================
+// Singleton to expose WebM video subsystem
+// ===========================================================================
+static CWebMVideoSubSystem g_WebMSystem;
+EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CWebMVideoSubSystem, IVideoSubSystem, VIDEO_SUBSYSTEM_INTERFACE_VERSION, g_WebMSystem );
+
+
+// ===========================================================================
+// List of file extensions and features supported by this subsystem
+// ===========================================================================
+VideoFileExtensionInfo_t s_WebMExtensions[] =
+{
+ { ".webm", VideoSystem::WEBM, VideoSystemFeature::FULL_ENCODE },
+};
+
+const int s_WebMExtensionCount = ARRAYSIZE( s_WebMExtensions );
+
+const VideoSystemFeature_t CWebMVideoSubSystem::DEFAULT_FEATURE_SET = VideoSystemFeature::FULL_ENCODE;
+
+
+// ===========================================================================
+// CWebMVideoSubSystem class
+// ===========================================================================
+CWebMVideoSubSystem::CWebMVideoSubSystem() :
+ m_bWebMInitialized( false ),
+ m_LastResult( VideoResult::SUCCESS ),
+ m_CurrentStatus( VideoSystemStatus::NOT_INITIALIZED ),
+ m_AvailableFeatures( CWebMVideoSubSystem::DEFAULT_FEATURE_SET ),
+ m_pCommonServices( nullptr )
+{
+
+}
+
+
+CWebMVideoSubSystem::~CWebMVideoSubSystem()
+{
+ ShutdownWebM(); // Super redundant safety check
+}
+
+
+// ===========================================================================
+// IAppSystem methods
+// ===========================================================================
+bool CWebMVideoSubSystem::Connect( CreateInterfaceFn factory )
+{
+ if ( !BaseClass::Connect( factory ) )
+ {
+ return false;
+ }
+
+ if ( g_pFullFileSystem == nullptr || materials == nullptr )
+ {
+ Msg( "WebM video subsystem failed to connect to missing a required system\n" );
+ return false;
+ }
+ return true;
+}
+
+
+void CWebMVideoSubSystem::Disconnect()
+{
+ BaseClass::Disconnect();
+}
+
+
+void* CWebMVideoSubSystem::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 CWebMVideoSubSystem::Init()
+{
+ InitReturnVal_t nRetVal = BaseClass::Init();
+ if ( nRetVal != INIT_OK )
+ {
+ return nRetVal;
+ }
+
+ return INIT_OK;
+
+}
+
+
+void CWebMVideoSubSystem::Shutdown()
+{
+ // Make sure we shut down WebM
+ ShutdownWebM();
+
+ BaseClass::Shutdown();
+}
+
+
+// ===========================================================================
+// IVideoSubSystem identification methods
+// ===========================================================================
+VideoSystem_t CWebMVideoSubSystem::GetSystemID()
+{
+ return VideoSystem::WEBM;
+}
+
+
+VideoSystemStatus_t CWebMVideoSubSystem::GetSystemStatus()
+{
+ return m_CurrentStatus;
+}
+
+
+VideoSystemFeature_t CWebMVideoSubSystem::GetSupportedFeatures()
+{
+ return m_AvailableFeatures;
+}
+
+
+const char* CWebMVideoSubSystem::GetVideoSystemName()
+{
+ return "WebM";
+}
+
+
+// ===========================================================================
+// IVideoSubSystem setup and shutdown services
+// ===========================================================================
+bool CWebMVideoSubSystem::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;
+
+ return ( m_bWebMInitialized ) ? true : SetupWebM();
+}
+
+
+bool CWebMVideoSubSystem::ShutdownVideoSystem()
+{
+ return ( m_bWebMInitialized ) ? ShutdownWebM() : true;
+}
+
+
+VideoResult_t CWebMVideoSubSystem::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 CWebMVideoSubSystem::GetSupportedFileExtensionCount()
+{
+ return s_WebMExtensionCount;
+}
+
+
+const char* CWebMVideoSubSystem::GetSupportedFileExtension( int num )
+{
+ return ( num < 0 || num >= s_WebMExtensionCount ) ? nullptr : s_WebMExtensions[num].m_FileExtension;
+}
+
+
+VideoSystemFeature_t CWebMVideoSubSystem::GetSupportedFileExtensionFeatures( int num )
+{
+ return ( num < 0 || num >= s_WebMExtensionCount ) ? VideoSystemFeature::NO_FEATURES : s_WebMExtensions[num].m_VideoFeatures;
+}
+
+
+// ===========================================================================
+// IVideoSubSystem Video Playback and Recording Services
+// ===========================================================================
+VideoResult_t CWebMVideoSubSystem::PlayVideoFileFullScreen( const char *filename, void *mainWindow, int windowWidth, int windowHeight, int desktopWidth, int desktopHeight, bool windowed, float forcedMinTime, VideoPlaybackFlags_t playbackFlags )
+{
+ return SetResult( VideoResult::FEATURE_NOT_AVAILABLE );
+}
+
+
+// ===========================================================================
+// IVideoSubSystem Video Material Services
+// note that the filename is absolute and has already resolved any paths
+// ===========================================================================
+IVideoMaterial* CWebMVideoSubSystem::CreateVideoMaterial( const char *pMaterialName, const char *pVideoFileName, VideoPlaybackFlags_t flags )
+{
+ SetResult( VideoResult::FEATURE_NOT_AVAILABLE );
+ return NULL;
+}
+
+
+VideoResult_t CWebMVideoSubSystem::DestroyVideoMaterial( IVideoMaterial *pVideoMaterial )
+{
+ return SetResult (VideoResult::FEATURE_NOT_AVAILABLE );
+
+}
+
+
+// ===========================================================================
+// IVideoSubSystem Video Recorder Services
+// ===========================================================================
+IVideoRecorder* CWebMVideoSubSystem::CreateVideoRecorder()
+{
+ SetResult( VideoResult::SYSTEM_NOT_AVAILABLE );
+ AssertExitN( m_CurrentStatus == VideoSystemStatus::OK );
+
+ CWebMVideoRecorder *pVideoRecorder = new CWebMVideoRecorder();
+
+ 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 CWebMVideoSubSystem::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 )
+ {
+ CWebMVideoRecorder *pVideoRecorder = (CWebMVideoRecorder*) pRecorder;
+ delete pVideoRecorder;
+
+ m_RecorderList.FindAndFastRemove( pRecorder );
+
+ return SetResult( VideoResult::SUCCESS );
+ }
+
+ return SetResult( VideoResult::RECORDER_NOT_FOUND );
+}
+
+VideoResult_t CWebMVideoSubSystem::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 ) );
+
+ return SetResult( VideoResult::FEATURE_NOT_AVAILABLE );
+}
+
+
+// ===========================================================================
+// Status support
+// ===========================================================================
+VideoResult_t CWebMVideoSubSystem::GetLastResult()
+{
+ return m_LastResult;
+}
+
+
+VideoResult_t CWebMVideoSubSystem::SetResult( VideoResult_t status )
+{
+ m_LastResult = status;
+ return status;
+}
+
+
+// ===========================================================================
+// WebM Initialization & Shutdown
+// ===========================================================================
+bool CWebMVideoSubSystem::SetupWebM()
+{
+ SetResult( VideoResult::INITIALIZATION_ERROR_OCCURED);
+ AssertExitF( m_bWebMInitialized == false );
+
+ // This is set early to indicate we have already been through here, even if we error out for some reason
+ m_bWebMInitialized = true;
+ m_CurrentStatus = VideoSystemStatus::OK;
+ m_AvailableFeatures = DEFAULT_FEATURE_SET;
+ // $$INIT CODE HERE$$
+
+
+ // Note that we are now open for business....
+ m_bWebMInitialized = true;
+ SetResult( VideoResult::SUCCESS );
+
+ return true;
+}
+
+
+bool CWebMVideoSubSystem::ShutdownWebM()
+{
+ if ( m_bWebMInitialized && m_CurrentStatus == VideoSystemStatus::OK )
+ {
+
+ }
+
+ m_bWebMInitialized = false;
+ m_CurrentStatus = VideoSystemStatus::NOT_INITIALIZED;
+ m_AvailableFeatures = VideoSystemFeature::NO_FEATURES;
+ SetResult( VideoResult::SUCCESS );
+
+ return true;
+}
+
diff --git a/video/webm_video.h b/video/webm_video.h
new file mode 100644
index 0000000..eb38f9d
--- /dev/null
+++ b/video/webm_video.h
@@ -0,0 +1,133 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+
+#ifndef WEBM_VIDEO_H
+#define WEBM_VIDEO_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Forward declarations
+//-----------------------------------------------------------------------------
+class IFileSystem;
+class IMaterialSystem;
+class CQuickTimeMaterial;
+
+//-----------------------------------------------------------------------------
+// Global interfaces - you already did the needed includes, right?
+//-----------------------------------------------------------------------------
+extern IFileSystem *g_pFileSystem;
+extern IMaterialSystem *materials;
+
+
+//-----------------------------------------------------------------------------
+// WebM Header files, anything that seems strange here is to make it so their headers
+// can mesh with ours
+//-----------------------------------------------------------------------------
+#define VPX_CODEC_DISABLE_COMPAT 1
+#include "vpx/vpx_codec.h"
+#include "vpx/vpx_encoder.h"
+#include "vpx/vpx_image.h"
+#include "vpx/vp8cx.h"
+#ifdef UNUSED
+#undef UNUSED
+#endif
+
+// libwebm, support for reading/writing webm files
+#include "mkvreader.hpp"
+#include "mkvparser.hpp"
+#include "mkvmuxer.hpp"
+#include "mkvwriter.hpp"
+#include "mkvmuxerutil.hpp"
+
+#include "vorbis/vorbisenc.h"
+#include "vorbis/codec.h"
+
+#include "video/ivideoservices.h"
+#include "videosubsystem.h"
+
+#include "utlvector.h"
+#include "tier1/KeyValues.h"
+#include "tier0/platform.h"
+
+// -----------------------------------------------------------------------------
+// CQuickTimeVideoSubSystem - Implementation of IVideoSubSystem
+// -----------------------------------------------------------------------------
+class CWebMVideoSubSystem : public CTier2AppSystem< IVideoSubSystem >
+{
+ typedef CTier2AppSystem< IVideoSubSystem > BaseClass;
+
+ public:
+ CWebMVideoSubSystem();
+ ~CWebMVideoSubSystem();
+
+ // Inherited from IAppSystem
+ virtual bool Connect( CreateInterfaceFn factory );
+ virtual void Disconnect();
+ virtual void *QueryInterface( const char *pInterfaceName );
+ virtual InitReturnVal_t Init();
+ virtual void Shutdown();
+
+ // Inherited from IVideoSubSystem
+
+ // SubSystem Identification functions
+ virtual VideoSystem_t GetSystemID();
+ virtual VideoSystemStatus_t GetSystemStatus();
+ virtual VideoSystemFeature_t GetSupportedFeatures();
+ virtual const char *GetVideoSystemName();
+
+ // Setup & Shutdown Services
+ virtual bool InitializeVideoSystem( IVideoCommonServices *pCommonServices );
+ virtual bool ShutdownVideoSystem();
+
+ virtual VideoResult_t VideoSoundDeviceCMD( VideoSoundDeviceOperation_t operation, void *pDevice = nullptr, void *pData = nullptr );
+
+ // get list of file extensions and features we support
+ virtual int GetSupportedFileExtensionCount();
+ virtual const char *GetSupportedFileExtension( int num );
+ virtual VideoSystemFeature_t GetSupportedFileExtensionFeatures( int num );
+
+ // Video Playback and Recording Services
+ virtual VideoResult_t PlayVideoFileFullScreen( const char *filename, void *mainWindow, int windowWidth, int windowHeight, int desktopWidth, int desktopHeight, bool windowed, float forcedMinTime, VideoPlaybackFlags_t playbackFlags );
+
+ // Create/destroy a video material
+ virtual IVideoMaterial *CreateVideoMaterial( const char *pMaterialName, const char *pVideoFileName, VideoPlaybackFlags_t flags );
+ virtual VideoResult_t DestroyVideoMaterial( IVideoMaterial *pVideoMaterial );
+
+ // Create/destroy a video encoder
+ virtual IVideoRecorder *CreateVideoRecorder();
+ virtual VideoResult_t DestroyVideoRecorder( IVideoRecorder *pRecorder );
+
+ virtual VideoResult_t CheckCodecAvailability( VideoEncodeCodec_t codec );
+
+ virtual VideoResult_t GetLastResult();
+
+ private:
+
+ bool SetupWebM();
+ bool ShutdownWebM();
+
+ VideoResult_t SetResult( VideoResult_t status );
+
+ bool m_bWebMInitialized;
+ VideoResult_t m_LastResult;
+
+ VideoSystemStatus_t m_CurrentStatus;
+ VideoSystemFeature_t m_AvailableFeatures;
+
+ IVideoCommonServices *m_pCommonServices;
+
+ CUtlVector< IVideoMaterial* > m_MaterialList;
+ CUtlVector< IVideoRecorder* > m_RecorderList;
+
+ static const VideoSystemFeature_t DEFAULT_FEATURE_SET;
+};
+
+#endif // WEBM_VIDEO_H