Pass the surface directly to GetFrame, and a timestamp if frame skipping is

enabled.
 - This is simpler--two fewer entry points, and a trivial decode path.
 - Puts the mechanics of frame skipping in the decoder's control.  Previously,
   all we did was skip conversion and texture updates.  Now, the decoder
   can be smarter: skip B-frames, or skip to a keyframe.
 - This allows for faster decodes, using slice-based conversion: YUV conversion
   can be done at the same time as decoding.
This commit is contained in:
Glenn Maynard
2005-10-21 07:47:06 +00:00
parent 24f62bcb9a
commit 0bc413262d
4 changed files with 56 additions and 68 deletions
@@ -164,9 +164,7 @@ public:
CString Open( CString sFile );
void Close();
int GetFrame();
void ConvertToSurface( RageSurface *pSurface ) const;
int GetFrame( RageSurface *pOut, float fTargetTime );
int GetWidth() const { return m_pStream->codec.width; }
int GetHeight() const { return m_pStream->codec.height; }
@@ -176,12 +174,11 @@ public:
float GetTimestamp() const;
float GetFrameDuration() const;
bool SkippableFrame() const { return (m_pStream->codec.frame_number % 2) == 0; }
private:
void Init();
int ReadPacket();
int DecodePacket();
void ConvertToSurface( RageSurface *pSurface ) const;
avcodec::AVStream *m_pStream;
avcodec::AVFrame m_Frame;
@@ -241,8 +238,9 @@ void MovieDecoder_FFMpeg::Init()
}
/* Read until we get a frame, EOF or error. Return -1 on error, 0 on EOF, 1 if we have a frame. */
int MovieDecoder_FFMpeg::GetFrame()
int MovieDecoder_FFMpeg::GetFrame( RageSurface *pOut, float fTargetTime )
{
restart:
while( 1 )
{
int ret = DecodePacket();
@@ -279,6 +277,13 @@ int MovieDecoder_FFMpeg::GetFrame()
}
}
if( fTargetTime != -1 &&
GetTimestamp() + GetFrameDuration() <= fTargetTime &&
(m_pStream->codec.frame_number % 2) == 0 )
goto restart;
ConvertToSurface( pOut );
return 1;
}
@@ -38,8 +38,11 @@ CString MovieTexture_Generic::Init()
if( sError != "" )
return sError;
CreateTexture();
CreateFrameRects();
/* Decode one frame, to guarantee that the texture is drawn when this function returns. */
int ret = m_pDecoder->GetFrame();
int ret = m_pDecoder->GetFrame( m_pSurface, -1 );
if( ret == -1 )
return ssprintf( "%s: error getting first frame", GetID().filename.c_str() );
if( ret == 0 )
@@ -48,16 +51,12 @@ CString MovieTexture_Generic::Init()
return ssprintf( "%s: EOF getting first frame", GetID().filename.c_str() );
}
m_ImageWaiting = FRAME_DECODED;
m_ImageWaiting = FRAME_WAITING;
CreateTexture();
LOG->Trace( "Resolution: %ix%i (%ix%i, %ix%i)",
m_iSourceWidth, m_iSourceHeight,
m_iImageWidth, m_iImageHeight, m_iTextureWidth, m_iTextureHeight );
CreateFrameRects();
ConvertFrame();
UpdateFrame();
CHECKPOINT;
@@ -198,7 +197,11 @@ bool MovieTexture_Generic::DecodeFrame()
CHECKPOINT;
/* Read a frame. */
int ret = m_pDecoder->GetFrame();
float fTargetTime = -1;
if( m_bFrameSkipMode && m_fClock > m_pDecoder->GetTimestamp() )
fTargetTime = m_fClock;
int ret = m_pDecoder->GetFrame( m_pSurface, fTargetTime );
if( ret == -1 )
return false;
@@ -228,7 +231,6 @@ bool MovieTexture_Generic::DecodeFrame()
* Returns:
* == 0 if the currently decoded frame is ready to be displayed
* > 0 (seconds) if it's not yet time to display;
* == -1 if we're behind and the frame should be skipped
*/
float MovieTexture_Generic::CheckFrameTime()
{
@@ -277,9 +279,6 @@ float MovieTexture_Generic::CheckFrameTime()
m_bFrameSkipMode = true;
}
if( m_bFrameSkipMode && m_pDecoder->SkippableFrame() )
return -1; /* skip */
return 0;
}
@@ -314,11 +313,7 @@ void MovieTexture_Generic::DecoderThread()
}
const float fTime = CheckFrameTime();
if( fTime == -1 ) // skip frame
{
DiscardFrame();
}
else if( fTime > 0 ) // not time to decode a new frame yet
if( fTime > 0 ) // not time to decode a new frame yet
{
/* This needs to be relatively short so that we wake up quickly
* from being paused or for changes in m_fRate. */
@@ -332,7 +327,8 @@ void MovieTexture_Generic::DecoderThread()
int n = m_BufferFinished.GetValue();
ASSERT_M( n == 0 || m_State == DECODER_QUIT, ssprintf("%i, %i", n, m_State) );
}
ConvertFrame();
m_ImageWaiting = FRAME_WAITING;
/* We just went into FRAME_WAITING. Don't actually check; the main thread
* will change us back to FRAME_NONE without locking, and poke m_BufferFinished.
@@ -367,10 +363,8 @@ void MovieTexture_Generic::Update(float fDeltaTime)
float fTime = CheckFrameTime();
if( fTime > 0 )
return;
else if( fTime == -1 )
DiscardFrame();
else
ConvertFrame();
m_ImageWaiting = FRAME_WAITING;
}
}
@@ -390,17 +384,6 @@ void MovieTexture_Generic::Update(float fDeltaTime)
LOG->MapLog( "movie_looping", "MovieTexture_Generic::Update looping" );
}
/* Convert the frame from the native (typically YUV) internal format to
* our RGB RageSurface. */
void MovieTexture_Generic::ConvertFrame()
{
ASSERT_M( m_ImageWaiting == FRAME_DECODED, ssprintf("%i", m_ImageWaiting ) );
m_pDecoder->ConvertToSurface( m_pSurface );
m_ImageWaiting = FRAME_WAITING;
}
/* Call from the main thread when m_ImageWaiting == FRAME_WAITING to update the
* texture. Sets FRAME_NONE. Does not signal m_BufferFinished. */
void MovieTexture_Generic::UpdateFrame()
@@ -15,12 +15,17 @@ public:
virtual CString Open( CString sFile ) = 0;
virtual void Close() = 0;
/* Decode a frame internally. */
virtual int GetFrame() = 0;
/* Convert the current frame to an RGB surface. This may be skipped
* in frame skip mode. */
virtual void ConvertToSurface( RageSurface *pSurface ) const = 0;
/*
* Decode a frame. Return 1 on success, 0 on EOF, -1 on fatal error.
*
* If we're lagging behind the video, fTargetTime will be the target
* timestamp. The decoder may skip frames to catch up. On return,
* the current timestamp must be <= fTargetTime.
*
* Otherwise, fTargetTime will be -1, and the next frame should be
* decoded; skip frames only if necessary to recover from errors.
*/
virtual int GetFrame( RageSurface *pOut, float fTargetTime ) = 0;
/* Return the dimensions of the image, in pixels (before aspect ratio
* adjustments). */
@@ -30,12 +35,11 @@ public:
/* Return the aspect ratio of a pixel in the image. Usually 1. */
virtual float GetSourceAspectRatio() const { return 1.0f; }
/* Create a surface. This must be compatible with ConvertToSurface,
* should be a surface which is realtime-compatible with DISPLAY, and
* should attempt to obey TEXTUREMAN->GetPrefs().m_iMovieColorDepth.
* The given size will usually be the next power of two higher than
* GetWidth/GetHeight, but on systems with limited texture resolution,
* may be smaller. */
/* Create a surface acceptable to pass to GetFrame. This should be
* a surface which is realtime-compatible with DISPLAY, and should
* attempt to obey bPreferHighColor. The given size will usually be
* the next power of two higher than GetWidth/GetHeight, but on systems
* with limited texture resolution, may be smaller. */
virtual RageSurface *CreateCompatibleSurface( int iTextureWidth, int iTextureHeight, bool bPreferHighColor ) = 0;
/* The following functions return information about the current frame,
@@ -48,11 +52,6 @@ public:
/* Get the duration, in seconds, to display the current frame. */
virtual float GetFrameDuration() const = 0;
/* Ugly: return true if, when in frame skip mode, this frame should be skipped.
* Typically returns true on even frames, false on odd frames. (Frame skip mode
* should be smarter, and skip to a keyframe.) */
virtual bool SkippableFrame() const = 0;
};
@@ -81,8 +80,8 @@ private:
float m_fRate;
enum {
FRAME_NONE, /* no frame available; call GetFrame to get one */
FRAME_DECODED, /* frame decoded; call ConvertFrame */
FRAME_WAITING /* frame converted and waiting to be uploaded */
FRAME_DECODED, /* frame decoded; waiting until it's time to display it */
FRAME_WAITING /* frame waiting to be uploaded */
} m_ImageWaiting;
bool m_bLoop;
bool m_bWantRewind;
@@ -115,7 +114,6 @@ private:
void DecoderThread();
RageThread m_DecoderThread;
void ConvertFrame();
void UpdateFrame();
void CreateTexture();
@@ -28,9 +28,8 @@ public:
CString Open( CString sFile );
void Close();
int GetFrame();
int GetFrame( RageSurface *pOut, float fTargetTime );
RageSurface *CreateCompatibleSurface( int iTextureWidth, int iTextureHeight, bool bPreferHighColor );
void ConvertToSurface( RageSurface *pSurface ) const;
int GetWidth() const { return m_TheoraInfo.frame_width; }
int GetHeight() const { return m_TheoraInfo.frame_height; }
@@ -38,12 +37,12 @@ public:
float GetTimestamp() const;
float GetFrameDuration() const;
bool SkippableFrame() const;
private:
void Init();
CString ProcessHeaders();
int ReadPage( ogg_page *pOggPage );
void ConvertToSurface( RageSurface *pSurface ) const;
RageFile m_File;
@@ -215,7 +214,7 @@ CString MovieDecoder_Theora::Open( CString sFile )
return CString();
}
int MovieDecoder_Theora::GetFrame()
int MovieDecoder_Theora::GetFrame( RageSurface *pOut, float fTargetTime )
{
while(1)
{
@@ -223,6 +222,16 @@ int MovieDecoder_Theora::GetFrame()
if( ogg_stream_packetout(&m_OggStream, &op) != 0 )
{
theora_decode_packetin( &m_TheoraState, &op );
if( fTargetTime != -1 &&
GetTimestamp() + GetFrameDuration() <= fTargetTime )
{
ogg_int64_t iFrame = theora_granule_frame( (theora_state *) &m_TheoraState, m_iGranulepos );
if( (iFrame % 2) == 0 )
continue;
}
ConvertToSurface( pOut );
m_iGranulepos = m_TheoraState.granulepos;
return 1;
}
@@ -294,13 +303,6 @@ float MovieDecoder_Theora::GetFrameDuration() const
return (float) m_TheoraInfo.fps_denominator / m_TheoraInfo.fps_numerator;
}
bool MovieDecoder_Theora::SkippableFrame() const
{
ogg_int64_t iFrame = theora_granule_frame( (theora_state *) &m_TheoraState, m_iGranulepos );
return (iFrame % 2) == 0;
}
void MovieDecoder_Theora::Close()
{
ogg_stream_clear( &m_OggStream );