This commit is contained in:
FluorescentCIAAfricanAmerican
2020-04-22 12:56:21 -04:00
commit 3bf9df6b27
15370 changed files with 5489726 additions and 0 deletions

100
video/quicktime_common.h Normal file
View File

@ -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

View File

@ -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 );
}

221
video/quicktime_material.h Normal file
View File

@ -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

2146
video/quicktime_recorder.cpp Normal file

File diff suppressed because it is too large Load Diff

263
video/quicktime_recorder.h Normal file
View File

@ -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

1096
video/quicktime_video.cpp Normal file

File diff suppressed because it is too large Load Diff

128
video/quicktime_video.h Normal file
View File

@ -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

114
video/video_macros.h Normal file
View File

@ -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

84
video/video_quicktime.vpc Normal file
View File

@ -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]
}
}

72
video/video_services.vpc Normal file
View File

@ -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
}
}

86
video/video_webm.vpc Normal file
View File

@ -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
}
}

1464
video/videoservices.cpp Normal file

File diff suppressed because it is too large Load Diff

198
video/videoservices.h Normal file
View File

@ -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

97
video/videosubsystem.h Normal file
View File

@ -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

48
video/webm_common.h Normal file
View File

@ -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

962
video/webm_material.cpp Normal file
View File

@ -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 );
}

221
video/webm_material.h Normal file
View File

@ -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

811
video/webm_recorder.cpp Normal file
View File

@ -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;
}

111
video/webm_recorder.h Normal file
View File

@ -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

353
video/webm_video.cpp Normal file
View File

@ -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;
}

133
video/webm_video.h Normal file
View File

@ -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