diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /video | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'video')
| -rw-r--r-- | video/quicktime_common.h | 100 | ||||
| -rw-r--r-- | video/quicktime_material.cpp | 962 | ||||
| -rw-r--r-- | video/quicktime_material.h | 221 | ||||
| -rw-r--r-- | video/quicktime_recorder.cpp | 2146 | ||||
| -rw-r--r-- | video/quicktime_recorder.h | 263 | ||||
| -rw-r--r-- | video/quicktime_video.cpp | 1096 | ||||
| -rw-r--r-- | video/quicktime_video.h | 128 | ||||
| -rw-r--r-- | video/video_macros.h | 114 | ||||
| -rw-r--r-- | video/video_quicktime.vpc | 84 | ||||
| -rw-r--r-- | video/video_services.vpc | 72 | ||||
| -rw-r--r-- | video/video_webm.vpc | 86 | ||||
| -rw-r--r-- | video/videoservices.cpp | 1464 | ||||
| -rw-r--r-- | video/videoservices.h | 198 | ||||
| -rw-r--r-- | video/videosubsystem.h | 97 | ||||
| -rw-r--r-- | video/webm_common.h | 48 | ||||
| -rw-r--r-- | video/webm_material.cpp | 962 | ||||
| -rw-r--r-- | video/webm_material.h | 221 | ||||
| -rw-r--r-- | video/webm_recorder.cpp | 811 | ||||
| -rw-r--r-- | video/webm_recorder.h | 111 | ||||
| -rw-r--r-- | video/webm_video.cpp | 353 | ||||
| -rw-r--r-- | video/webm_video.h | 133 |
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 |