You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1079 lines
29 KiB
1079 lines
29 KiB
//========= 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 "bink_material.h" |
|
#include "tier0/memdbgon.h" |
|
|
|
extern "C" { |
|
#include "yuv_rgb.h" |
|
} |
|
|
|
|
|
// makes a copy of a string |
|
char *COPY_STRING( const char *pString ) |
|
{ |
|
if ( pString == nullptr ) |
|
return nullptr; |
|
|
|
size_t strLen = V_strlen( pString ); |
|
|
|
char *pNewStr = new char[ strLen+ 1 ]; |
|
if ( strLen > 0 ) |
|
V_memcpy( pNewStr, pString, strLen ); |
|
|
|
pNewStr[strLen] = nullchar; |
|
|
|
return pNewStr; |
|
} |
|
|
|
int open_codec_context(int *stream_idx, AVCodecContext **dec_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type) |
|
{ |
|
int ret, stream_index; |
|
AVStream *st; |
|
const AVCodec *dec = NULL; |
|
|
|
ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0); |
|
if (ret < 0) |
|
{ |
|
Warning("Could not find %s stream\n", |
|
av_get_media_type_string(type)); |
|
return ret; |
|
} |
|
else |
|
{ |
|
stream_index = ret; |
|
st = fmt_ctx->streams[stream_index]; |
|
|
|
/* find decoder for the stream */ |
|
dec = avcodec_find_decoder(st->codecpar->codec_id); |
|
if (!dec) |
|
{ |
|
Warning("Failed to find %s codec\n", |
|
av_get_media_type_string(type)); |
|
return AVERROR(EINVAL); |
|
} |
|
|
|
/* Allocate a codec context for the decoder */ |
|
*dec_ctx = avcodec_alloc_context3(dec); |
|
if (!*dec_ctx) |
|
{ |
|
Warning("Failed to allocate the %s codec context\n", |
|
av_get_media_type_string(type)); |
|
return AVERROR(ENOMEM); |
|
} |
|
|
|
/* Copy codec parameters from input stream to output codec context */ |
|
if ((ret = avcodec_parameters_to_context(*dec_ctx, st->codecpar)) < 0) |
|
{ |
|
Warning("Failed to copy %s codec parameters to decoder context\n", |
|
av_get_media_type_string(type)); |
|
return ret; |
|
} |
|
|
|
/* Init the decoders */ |
|
if ((ret = avcodec_open2(*dec_ctx, dec, NULL)) < 0) |
|
{ |
|
Warning("Failed to open %s codec\n", |
|
av_get_media_type_string(type)); |
|
return ret; |
|
} |
|
|
|
*stream_idx = stream_index; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
// =========================================================================== |
|
// CBinkMaterialRGBTextureRegenerator - Inherited from ITextureRegenerator |
|
// Copies and converts the buffer bits to texture bits |
|
// Currently only supports 32-bit BGR |
|
// =========================================================================== |
|
CBinkMaterialRGBTextureRegenerator::CBinkMaterialRGBTextureRegenerator() : |
|
m_nSourceWidth( 0 ), |
|
m_nSourceHeight( 0 ) |
|
{ |
|
} |
|
|
|
|
|
CBinkMaterialRGBTextureRegenerator::~CBinkMaterialRGBTextureRegenerator() |
|
{ |
|
// nothing to do |
|
} |
|
|
|
void CBinkMaterialRGBTextureRegenerator::SetSourceImage( uint8_t *SrcImage, int nWidth, int nHeight ) |
|
{ |
|
m_SrcImage = SrcImage; |
|
m_nSourceWidth = nWidth; |
|
m_nSourceHeight = nHeight; |
|
} |
|
|
|
void CBinkMaterialRGBTextureRegenerator::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_RGB888 ); |
|
Assert( pVTFTexture->RowSizeInBytes( 0 ) >= pVTFTexture->Width() * 4 ); |
|
Assert( pVTFTexture->Width() >= m_nSourceWidth ); |
|
Assert( pVTFTexture->Height() >= m_nSourceHeight ); |
|
|
|
// Copy directly from the Quicktime GWorld |
|
BYTE *pImageData = pVTFTexture->ImageData(); |
|
int dstStride = pVTFTexture->RowSizeInBytes( 0 ); |
|
|
|
BYTE *pSrcData = m_SrcImage; |
|
|
|
for (int y = 0; y < m_nSourceHeight; y++ ) |
|
{ |
|
memcpy( pImageData, pSrcData, m_nSourceWidth*3 ); |
|
|
|
pImageData += dstStride; |
|
pSrcData += m_nSourceWidth*3; |
|
} |
|
} |
|
|
|
|
|
void CBinkMaterialRGBTextureRegenerator::Release() |
|
{ |
|
// we don't invoke the destructor here, we're not using the no-release extensions |
|
} |
|
|
|
|
|
|
|
// =========================================================================== |
|
// CBinkMaterial class - creates a material, opens a QuickTime movie |
|
// and plays the movie onto the material |
|
// =========================================================================== |
|
|
|
//----------------------------------------------------------------------------- |
|
// CBinkMaterial Constructor |
|
//----------------------------------------------------------------------------- |
|
CBinkMaterial::CBinkMaterial() : |
|
m_pFileName( nullptr ), |
|
m_bInitCalled( false ), |
|
m_AVFrame( nullptr ), |
|
m_AVPkt( nullptr ) |
|
{ |
|
memset( m_AVVideoData, 0, sizeof(m_AVVideoData) ); |
|
memset( m_AVVideoLinesize, 0, sizeof(m_AVVideoLinesize) ); |
|
|
|
Reset(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// CBinkMaterial Destructor |
|
//----------------------------------------------------------------------------- |
|
CBinkMaterial::~CBinkMaterial() |
|
{ |
|
SetFileName( nullptr ); |
|
|
|
DestroyProceduralTexture(); |
|
DestroyProceduralMaterial(); |
|
|
|
av_frame_free( &m_AVFrame ); |
|
av_packet_free( &m_AVPkt ); |
|
|
|
if( m_AVVideoData[0] ) |
|
av_free(m_AVVideoData[0]); |
|
|
|
if( m_AVFmtCtx ) |
|
avformat_close_input( &m_AVFmtCtx ); |
|
} |
|
|
|
|
|
void CBinkMaterial::Reset() |
|
{ |
|
printf("CBinkMaterial::Reset()\n"); |
|
|
|
SetFileName( nullptr ); |
|
|
|
DestroyProceduralTexture(); |
|
DestroyProceduralMaterial(); |
|
|
|
m_TexCordU = 0.0f; |
|
m_TexCordV = 0.0f; |
|
|
|
m_VideoFrameWidth = 0; |
|
m_VideoFrameHeight = 0; |
|
|
|
m_AVPixFormat = 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 ); |
|
|
|
if( !m_AVFrame ) |
|
m_AVFrame = av_frame_alloc(); |
|
if( !m_AVPkt) |
|
m_AVPkt = av_packet_alloc(); |
|
|
|
m_RGBData = nullptr; |
|
|
|
m_AVFmtCtx = nullptr; |
|
m_AVAudioStream = nullptr; |
|
m_AVVideoStream = nullptr; |
|
|
|
AssertMsg( m_AVFrame, "av_frame_alloc return nullptr\n" ); |
|
AssertMsg( m_AVPkt, "av_packet_alloc return nullptr\n" ); |
|
|
|
if( m_AVVideoData[0] ) |
|
{ |
|
av_free(m_AVVideoData[0]); |
|
m_AVVideoData[0] = nullptr; |
|
} |
|
|
|
if( m_AVFmtCtx ) |
|
{ |
|
avformat_close_input( &m_AVFmtCtx ); |
|
m_AVFmtCtx = nullptr; |
|
} |
|
|
|
m_LastResult = VideoResult::SUCCESS; |
|
} |
|
|
|
|
|
void CBinkMaterial::SetFileName( const char *theMovieFileName ) |
|
{ |
|
SAFE_DELETE_ARRAY( m_pFileName ); |
|
|
|
if ( theMovieFileName != nullptr ) |
|
{ |
|
AssertMsg( V_strlen( theQTMovieFileName ) <= MAX_FILENAME_LEN, "Bad Quicktime Movie Filename" ); |
|
m_pFileName = COPY_STRING( theMovieFileName ); |
|
} |
|
} |
|
|
|
|
|
VideoResult_t CBinkMaterial::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 *CBinkMaterial::GetVideoFileName() |
|
{ |
|
return m_pFileName; |
|
} |
|
|
|
|
|
VideoFrameRate_t &CBinkMaterial::GetVideoFrameRate() |
|
{ |
|
return m_QTMovieFrameRate; |
|
} |
|
|
|
|
|
VideoResult_t CBinkMaterial::GetLastResult() |
|
{ |
|
return m_LastResult; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Audio Functions |
|
//----------------------------------------------------------------------------- |
|
bool CBinkMaterial::HasAudio() |
|
{ |
|
return m_bHasAudio; |
|
} |
|
|
|
|
|
bool CBinkMaterial::SetVolume( float fVolume ) |
|
{ |
|
clamp( fVolume, 0.0f, 1.0f ); |
|
|
|
m_CurrentVolume = fVolume; |
|
|
|
SetResult( VideoResult::AUDIO_ERROR_OCCURED ); |
|
return false; |
|
} |
|
|
|
|
|
float CBinkMaterial::GetVolume() |
|
{ |
|
return m_CurrentVolume; |
|
} |
|
|
|
|
|
void CBinkMaterial::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 ) |
|
{ |
|
|
|
} |
|
|
|
SetResult( VideoResult::SUCCESS ); |
|
} |
|
|
|
|
|
bool CBinkMaterial::IsMuted() |
|
{ |
|
return m_bMuted; |
|
} |
|
|
|
|
|
VideoResult_t CBinkMaterial::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 CBinkMaterial::Init( const char *pMaterialName, const char *pFileName, VideoPlaybackFlags_t flags ) |
|
{ |
|
printf("CBinkMaterial::Init\n"); |
|
|
|
SetResult( VideoResult::BAD_INPUT_PARAMETERS ); |
|
AssertExitF( IS_NOT_EMPTY( pFileName ) ); |
|
AssertExitF( m_bInitCalled == false ); |
|
|
|
m_PlaybackFlags = flags; |
|
|
|
OpenMovie( 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 CBinkMaterial::Shutdown( void ) |
|
{ |
|
StopVideo(); |
|
Reset(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Video playback state functions |
|
//----------------------------------------------------------------------------- |
|
bool CBinkMaterial::IsVideoReadyToPlay() |
|
{ |
|
return m_bMovieInitialized; |
|
} |
|
|
|
|
|
bool CBinkMaterial::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 CBinkMaterial::IsNewFrameReady( void ) |
|
{ |
|
// Are we waiting to start playing the first frame? if so, tell them we are ready! |
|
if ( m_bMovieInitialized == true ) |
|
{ |
|
return true; |
|
} |
|
|
|
// paused? |
|
if ( m_bMoviePaused ) |
|
{ |
|
return false; |
|
} |
|
|
|
// float curMovieTime; |
|
// 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 CBinkMaterial::IsFinishedPlaying() |
|
{ |
|
return m_bMovieFinishedPlaying; |
|
} |
|
|
|
|
|
void CBinkMaterial::SetLooping( bool bLoopVideo ) |
|
{ |
|
m_bLoopMovie = bLoopVideo; |
|
} |
|
|
|
|
|
bool CBinkMaterial::IsLooping() |
|
{ |
|
return m_bLoopMovie; |
|
} |
|
|
|
|
|
void CBinkMaterial::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 CBinkMaterial::IsPaused() |
|
{ |
|
return ( m_bMoviePlaying ) ? m_bMoviePaused : false; |
|
} |
|
|
|
|
|
// Begins playback of the movie |
|
bool CBinkMaterial::StartVideo() |
|
{ |
|
if ( !m_bMovieInitialized ) |
|
{ |
|
Assert( false ); |
|
SetResult( VideoResult::OPERATION_ALREADY_PERFORMED ); |
|
return false; |
|
} |
|
|
|
m_NextInterestingTimeToPlay = Plat_FloatTime(); |
|
|
|
printf("Movie start time = %lf\n", Plat_FloatTime()); |
|
|
|
// Transition to playing state |
|
m_bMovieInitialized = false; |
|
m_bMoviePlaying = true; |
|
|
|
Update(); |
|
|
|
return true; |
|
} |
|
|
|
|
|
// stops movie for good, frees resources, but retains texture & material of last frame rendered |
|
bool CBinkMaterial::StopVideo() |
|
{ |
|
if ( !m_bMoviePlaying ) |
|
{ |
|
SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE ); |
|
return false; |
|
} |
|
|
|
m_bMoviePlaying = false; |
|
m_bMoviePaused = false; |
|
m_bMovieFinishedPlaying = true; |
|
|
|
// free resources |
|
CloseFile(); |
|
|
|
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 CBinkMaterial::Update( void ) |
|
{ |
|
AssertExitF( m_bMoviePlaying ); |
|
|
|
|
|
// are we paused? can't update if so... |
|
if ( m_bMoviePaused ) |
|
return true; // reuse the last frame |
|
|
|
// Get current time in the movie |
|
float curMovieTime; // = GetMovieTime( m_QTMovie, nullptr ); |
|
|
|
if( m_NextInterestingTimeToPlay > Plat_FloatTime() ) |
|
return true; |
|
|
|
m_NextInterestingTimeToPlay += m_MovieFrameDuration; |
|
|
|
/* read frames from the file */ |
|
|
|
int ret; |
|
while( (ret = av_read_frame(m_AVFmtCtx, m_AVPkt)) >= 0 ) |
|
{ |
|
if (m_AVPkt->stream_index == m_AVVideoStreamID) |
|
{ |
|
avcodec_send_packet(m_AVVideoDecCtx, m_AVPkt); |
|
|
|
ret = avcodec_receive_frame(m_AVVideoDecCtx, m_AVFrame); |
|
if (ret < 0) |
|
{ |
|
av_packet_unref(m_AVPkt); |
|
return true; |
|
} |
|
|
|
// write the frame data to output file |
|
if (m_AVVideoDecCtx->codec->type == AVMEDIA_TYPE_VIDEO) |
|
{ |
|
av_image_copy(m_AVVideoData, m_AVVideoLinesize, (const uint8_t **)(m_AVFrame->data), m_AVFrame->linesize, m_AVPixFormat, m_VideoFrameWidth, m_VideoFrameHeight); |
|
} |
|
|
|
av_frame_unref(m_AVFrame); |
|
break; |
|
} |
|
|
|
av_packet_unref(m_AVPkt); |
|
} |
|
|
|
|
|
if( ret < 0 ) |
|
{ |
|
StopVideo(); |
|
return false; |
|
} |
|
|
|
|
|
|
|
yuv420_rgb24_std( m_VideoFrameWidth, m_VideoFrameHeight, m_AVVideoData[0], |
|
m_AVVideoData[0]+m_VideoFrameHeight*m_VideoFrameWidth, |
|
m_AVVideoData[0]+m_VideoFrameWidth*m_VideoFrameHeight+((m_VideoFrameWidth+1)/2)*((m_VideoFrameHeight+1)/2), |
|
m_VideoFrameWidth, (m_VideoFrameWidth+1)/2, m_RGBData, m_VideoFrameWidth*3, YCBCR_601 |
|
); |
|
|
|
m_Texture->Download(); |
|
|
|
SetResult( VideoResult::SUCCESS ); |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the material |
|
//----------------------------------------------------------------------------- |
|
IMaterial *CBinkMaterial::GetMaterial() |
|
{ |
|
return m_Material; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the texcoord range |
|
//----------------------------------------------------------------------------- |
|
void CBinkMaterial::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 CBinkMaterial::GetVideoImageSize( int *pWidth, int *pHeight ) |
|
{ |
|
Assert( pWidth != nullptr && pHeight != nullptr ); |
|
|
|
*pWidth = m_VideoFrameWidth; |
|
*pHeight = m_VideoFrameHeight; |
|
} |
|
|
|
|
|
float CBinkMaterial::GetVideoDuration() |
|
{ |
|
return m_QTMovieDurationinSec; |
|
} |
|
|
|
|
|
int CBinkMaterial::GetFrameCount() |
|
{ |
|
return m_QTMovieFrameCount; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Sets the frame for an QuickTime Material (use instead of SetTime) |
|
//----------------------------------------------------------------------------- |
|
bool CBinkMaterial::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 CBinkMaterial::GetCurrentFrame() |
|
{ |
|
AssertExitV( m_bMoviePlaying, -1 ); |
|
|
|
float curTime; // = m_bMoviePaused ? m_MoviePauseTime : GetMovieTime( m_QTMovie, nullptr ); |
|
|
|
return curTime / m_QTMovieFrameRate.GetUnitsPerFrame(); |
|
} |
|
|
|
|
|
float CBinkMaterial::GetCurrentVideoTime() |
|
{ |
|
AssertExitV( m_bMoviePlaying, -1.0f ); |
|
|
|
float curTime; // = m_bMoviePaused ? m_MoviePauseTime : GetMovieTime( m_QTMovie, nullptr ); |
|
|
|
return curTime / m_QTMovieFrameRate.GetUnitsPerSecond(); |
|
} |
|
|
|
|
|
bool CBinkMaterial::SetTime( float flTime ) |
|
{ |
|
AssertExitF( m_bMoviePlaying ); |
|
AssertExitF( flTime >= 0 && flTime < m_QTMovieDurationinSec ); |
|
|
|
float newTime = ( flTime * m_QTMovieFrameRate.GetUnitsPerSecond() + 0.5f) ; |
|
|
|
clamp( newTime, m_MovieFirstFrameTime, m_QTMovieDuration ); |
|
|
|
// Are we paused? |
|
if ( m_bMoviePaused ) |
|
{ |
|
m_MoviePauseTime = newTime; |
|
return true; |
|
} |
|
|
|
float 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 CBinkMaterial::CreateProceduralTexture( const char *pTextureName ) |
|
{ |
|
printf("CBinkMaterial::CreateProceduralTexture\n"); |
|
|
|
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_RGB888, 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 CBinkMaterial::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 CBinkMaterial::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 CBinkMaterial::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 CBinkMaterial::OpenMovie( const char *theMovieFileName ) |
|
{ |
|
AssertExit( IS_NOT_EMPTY( theMovieFileName ) ); |
|
/* |
|
// Set graphics port |
|
#if defined ( WIN32 ) |
|
SetGWorld ( (CGrafPtr) GetNativeWindowPort( nil ), nil ); |
|
#elif defined ( OSX ) |
|
SetGWorld( nil, nil ); |
|
#endif |
|
*/ |
|
|
|
SetFileName( theMovieFileName ); |
|
printf("CBinkMaterial::OpenMovie( \"%s\" )\n", theMovieFileName); |
|
|
|
if (avformat_open_input(&m_AVFmtCtx, theMovieFileName, NULL, NULL) < 0) |
|
{ |
|
Warning("Could not open source file %s\n", theMovieFileName); |
|
SetResult( VideoResult::FILE_ERROR_OCCURED ) ; |
|
Reset(); |
|
return; |
|
} |
|
|
|
if (avformat_find_stream_info(m_AVFmtCtx, NULL) < 0) |
|
{ |
|
Warning("Could not find stream information for %s\n", theMovieFileName); |
|
SetResult( VideoResult::FILE_ERROR_OCCURED ) ; |
|
Reset(); |
|
return; |
|
} |
|
|
|
if (open_codec_context(&m_AVVideoStreamID, &m_AVVideoDecCtx, m_AVFmtCtx, AVMEDIA_TYPE_VIDEO) == 0) |
|
{ |
|
m_AVVideoStream = m_AVFmtCtx->streams[m_AVVideoStreamID]; |
|
|
|
/* allocate image where the decoded image will be put */ |
|
m_VideoFrameWidth = m_AVVideoDecCtx->width; |
|
m_VideoFrameHeight = m_AVVideoDecCtx->height; |
|
m_AVPixFormat = m_AVVideoDecCtx->pix_fmt; |
|
size_t size = av_image_alloc(m_AVVideoData, m_AVVideoLinesize, |
|
m_VideoFrameWidth, m_VideoFrameHeight, m_AVPixFormat, 1); |
|
|
|
m_RGBData = calloc( m_VideoFrameWidth*m_VideoFrameHeight*3, 1 ); |
|
|
|
printf("m_AVVideoData size = %zu\nm_VideoFrameWidth=%d\nm_VideoFrameHeight=%d\n", size, m_VideoFrameWidth, m_VideoFrameHeight); |
|
|
|
if (size < 0) |
|
{ |
|
Warning("Could not allocate raw video buffer\n", theMovieFileName); |
|
SetResult( VideoResult::SYSTEM_ERROR_OCCURED ) ; |
|
Reset(); |
|
return; |
|
} |
|
} |
|
else |
|
{ |
|
Warning("open_codec_context failed for %s\n", theMovieFileName); |
|
SetResult( VideoResult::SYSTEM_ERROR_OCCURED ) ; |
|
Reset(); |
|
return; |
|
} |
|
|
|
m_MovieFrameDuration = 1.0/((double)m_AVVideoStream->r_frame_rate.num/(double)m_AVVideoStream->r_frame_rate.den); |
|
m_TextureRegen.SetSourceImage( m_RGBData, m_VideoFrameWidth, m_VideoFrameHeight ); |
|
printf("Video FPS: %lf\n", (double)m_AVVideoStream->r_frame_rate.num/(double)m_AVVideoStream->r_frame_rate.den); |
|
|
|
#if 0 |
|
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 ) ); |
|
}*/ |
|
|
|
#endif |
|
m_bMovieInitialized = true; |
|
} |
|
|
|
|
|
void CBinkMaterial::CloseFile() |
|
{ |
|
av_freep( &m_AVVideoData[0] ); |
|
avformat_close_input( &m_AVFmtCtx ); |
|
m_AVFmtCtx = nullptr; |
|
free(m_RGBData); |
|
|
|
SetFileName( nullptr ); |
|
} |
|
|
|
|
|
|