diff --git a/stepmania/src/arch/MovieTexture/MovieTexture_Theora.cpp b/stepmania/src/arch/MovieTexture/MovieTexture_Theora.cpp new file mode 100644 index 0000000000..7b198fc7b4 --- /dev/null +++ b/stepmania/src/arch/MovieTexture/MovieTexture_Theora.cpp @@ -0,0 +1,317 @@ +#include "global.h" +#include "MovieTexture_Theora.h" +#include "RageDisplay.h" +#include "RageFile.h" +#include "RageLog.h" +#include "RageSurface.h" +#include "RageTextureManager.h" +#include "RageUtil.h" +#include "MovieTexture_FFMpeg.h" /* for AVCodecCreateCompatibleSurface */ + +#include + +#if defined(_MSC_VER) +#pragma comment(lib, XIPH_LIB_DIR "vorbis_static.lib") +#pragma comment(lib, XIPH_LIB_DIR "theora_static.lib") +#endif + +namespace avcodec +{ +#include /* for avcodec::img_convert */ +}; + +class MovieDecoder_Theora: public MovieDecoder +{ +public: + MovieDecoder_Theora(); + ~MovieDecoder_Theora(); + + CString Open( CString sFile ); + void Close(); + + int GetFrame(); + RageSurface *CreateCompatibleSurface( int iTextureWidth, int iTextureHeight ); + void ConvertToSurface( RageSurface *pSurface ) const; + + int GetWidth() const { return m_TheoraInfo.frame_width; } + int GetHeight() const { return m_TheoraInfo.frame_height; } + float GetSourceAspectRatio() const; + + float GetTimestamp() const; + float GetFrameDuration() const; + bool SkippableFrame() const; + +private: + void Init(); + int ReadPage( ogg_page *pOggPage ); + + RageFile m_File; + + ogg_sync_state m_OggSync; + ogg_stream_state m_OggStream; + theora_info m_TheoraInfo; + theora_comment m_TheoraComment; + theora_state m_TheoraState; + + ogg_int64_t m_iGranulepos; + + avcodec::PixelFormat m_InputPixFmt; /* PixelFormat of YUV input surface */ + avcodec::PixelFormat m_OutputPixFmt; /* PixelFormat of RGB output surface */ +}; + +MovieDecoder_Theora::MovieDecoder_Theora() +{ + avcodec::avcodec_init(); + + memset( &m_TheoraInfo, 0, sizeof(m_TheoraInfo) ); + memset( &m_TheoraComment, 0, sizeof(m_TheoraComment) ); + memset( &m_OggStream, 0, sizeof(m_OggStream) ); + memset( &m_TheoraState, 0, sizeof(m_TheoraState) ); +} + +MovieDecoder_Theora::~MovieDecoder_Theora() +{ + Close(); +} + +void MovieDecoder_Theora::Init() +{ + theora_info_init( &m_TheoraInfo ); + theora_comment_init( &m_TheoraComment ); + ogg_sync_init( &m_OggSync ); +} + +int MovieDecoder_Theora::ReadPage( ogg_page *pOggPage ) +{ + while(1) + { + int iRet = ogg_sync_pageout(&m_OggSync, pOggPage) > 0; + if( iRet > 0 ) + return iRet; + if( iRet < 0 ) + { + LOG->Trace( "%s: hole in stream", m_File.GetPath().c_str() ); + continue; + } + + if( iRet == 0 ) + { + char *pBuffer = ogg_sync_buffer( &m_OggSync, 4096 ); + int iBytes = m_File.Read( pBuffer, 4096 ); + if( iBytes == 0 ) + return 0; + if( iBytes == -1 ) + return -1; + + ogg_sync_wrote( &m_OggSync, iBytes ); + } + } +} + +CString MovieDecoder_Theora::Open( CString sFile ) +{ + if( !m_File.Open(sFile) ) + return ssprintf( "error opening %s: %s", sFile.c_str(), m_File.GetError().c_str() ); + + Init(); + + int iTheoraPacketsProcessed = 0; + while( iTheoraPacketsProcessed < 3 ) + { + ogg_page OggPage; + int ret = ReadPage( &OggPage ); + if( ret == 0 ) + return ssprintf( "error opening %s: EOF while searching for codec headers", m_File.GetPath().c_str() ); + if( ret<0 ) + return ssprintf( "error reading %s: %s", m_File.GetPath().c_str(), m_File.GetError().c_str() ); + + if( m_OggStream.body_data == NULL ) + { + /* We don't have a stream yet. If this packet doesn't start a stream, + * then there aren't any Theora streams. */ + if( !ogg_page_bos(&OggPage) ) + return ssprintf( "error opening %s: no Theora streams found", m_File.GetPath().c_str() ); + + ogg_stream_init( &m_OggStream, ogg_page_serialno(&OggPage) ); + ogg_stream_pagein( &m_OggStream, &OggPage ); + ogg_packet op; + ogg_stream_packetout( &m_OggStream, &op ); + + if( theora_decode_header(&m_TheoraInfo, &m_TheoraComment, &op) != 0 ) + { + ogg_stream_clear( &m_OggStream ); + continue; + } + + /* We've found the Theora stream. */ + iTheoraPacketsProcessed++; + } + else + ogg_stream_pagein( &m_OggStream, &OggPage ); + + /* Look for more headers in this page. */ + while( iTheoraPacketsProcessed < 3 ) + { + ogg_packet op; + int ret = ogg_stream_packetout( &m_OggStream, &op ); + if( ret == 0 ) + break; + if( ret < 0 ) + return ssprintf( "error opening %s: error parsing Theora stream headers", m_File.GetPath().c_str() ); + if( theora_decode_header(&m_TheoraInfo, &m_TheoraComment, &op) < 0 ) + return ssprintf( "error opening %s: error parsing Theora stream headers", m_File.GetPath().c_str() ); + iTheoraPacketsProcessed++; + } + } + + theora_decode_init( &m_TheoraState, &m_TheoraInfo ); + + CString sOutput = ssprintf( "MovieDecoder_Theora: Opened \"%s\". Serial: 0x%08lx, FPS: %.02f", + m_File.GetPath().c_str(), m_OggStream.serialno, GetFrameDuration() ); + + switch( m_TheoraInfo.pixelformat ) + { + case OC_PF_420: + m_InputPixFmt = avcodec::PIX_FMT_YUV420P; + sOutput += ", Format: YUV420"; + break; + case OC_PF_422: + m_InputPixFmt = avcodec::PIX_FMT_YUV422P; + sOutput += ", Format: YUV422"; + break; + case OC_PF_444: + m_InputPixFmt = avcodec::PIX_FMT_YUV422P; + sOutput += ", Format: YUV444"; + break; + default: + return ssprintf( "error opening %s: unsupported pixel format %i", m_File.GetPath().c_str(), m_TheoraInfo.pixelformat ); + } + LOG->Trace( "%s", sOutput.c_str() ); + + return CString(); +} + +int MovieDecoder_Theora::GetFrame() +{ + while(1) + { + ogg_packet op; + if( ogg_stream_packetout(&m_OggStream, &op) != 0 ) + { + theora_decode_packetin( &m_TheoraState, &op ); + m_iGranulepos = m_TheoraState.granulepos; + return 1; + } + + /* Read more data. */ + ogg_page OggPage; + int ret = ReadPage( &OggPage ); + if( ret == 0 ) + return 0; + if( ret < 0 ) + { + LOG->Warn( "error reading %s: %s", m_File.GetPath().c_str(), m_File.GetError().c_str() ); + return -1; + } + if( ret > 0 ) + ogg_stream_pagein( &m_OggStream, &OggPage ); + } +} + +RageSurface *MovieDecoder_Theora::CreateCompatibleSurface( int iTextureWidth, int iTextureHeight ) +{ + return MovieTexture_FFMpeg::AVCodecCreateCompatibleSurface( iTextureWidth, iTextureHeight, m_OutputPixFmt ); +} + +void MovieDecoder_Theora::ConvertToSurface( RageSurface *pSurface ) const +{ + yuv_buffer yuv; + theora_decode_YUVout( (theora_state *) &m_TheoraState, &yuv ); + + /* XXX: non-zero offset untested */ + int iUVOffsetX = m_TheoraInfo.offset_x; + int iUVOffsetY = m_TheoraInfo.offset_y; + if( m_InputPixFmt == OC_PF_420 || m_InputPixFmt == OC_PF_422 ) + iUVOffsetX /= 2; + if( m_InputPixFmt == OC_PF_420 ) + iUVOffsetY /= 2; + + avcodec::AVPicture YUVIn; + YUVIn.data[0] = yuv.y + m_TheoraInfo.offset_x + yuv.y_stride*m_TheoraInfo.offset_y; + YUVIn.data[1] = yuv.u + iUVOffsetX + yuv.uv_stride*iUVOffsetY; + YUVIn.data[2] = yuv.v + iUVOffsetX + yuv.uv_stride*iUVOffsetY; + YUVIn.data[3] = NULL; + YUVIn.linesize[0] = yuv.y_stride; + YUVIn.linesize[1] = yuv.uv_stride; + YUVIn.linesize[2] = yuv.uv_stride; + YUVIn.linesize[3] = 0; + + avcodec::AVPicture RGBOut; + RGBOut.data[0] = (unsigned char *) pSurface->pixels; + RGBOut.linesize[0] = pSurface->pitch; + + avcodec::img_convert( &RGBOut, m_OutputPixFmt, + &YUVIn, m_InputPixFmt, + m_TheoraInfo.frame_width, m_TheoraInfo.frame_height ); +} + +float MovieDecoder_Theora::GetSourceAspectRatio() const +{ + return (float) m_TheoraInfo.aspect_numerator / m_TheoraInfo.aspect_denominator; +} + +float MovieDecoder_Theora::GetTimestamp() const +{ + return (float) theora_granule_time( (theora_state *) &m_TheoraState, m_iGranulepos ); +} + +float MovieDecoder_Theora::GetFrameDuration() const +{ + return (float) m_TheoraInfo.fps_numerator / m_TheoraInfo.fps_denominator; +} + +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 ); + theora_clear( &m_TheoraState ); + theora_comment_clear( &m_TheoraComment ); + theora_info_clear( &m_TheoraInfo ); + ogg_sync_clear( &m_OggSync ); +} + +MovieTexture_Theora::MovieTexture_Theora( RageTextureID ID ): + MovieTexture_Generic( ID, new MovieDecoder_Theora ) +{ +} + +/* + * (c) 2005 Glenn Maynard + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/stepmania/src/arch/MovieTexture/MovieTexture_Theora.h b/stepmania/src/arch/MovieTexture/MovieTexture_Theora.h new file mode 100644 index 0000000000..8adf407c6d --- /dev/null +++ b/stepmania/src/arch/MovieTexture/MovieTexture_Theora.h @@ -0,0 +1,40 @@ +/* MovieTexture_Theora - Ogg Theora movie renderer. */ + +#ifndef RAGE_MOVIE_TEXTURE_THEORA_H +#define RAGE_MOVIE_TEXTURE_THEORA_H + +#include "MovieTexture_Generic.h" + +class MovieTexture_Theora: public MovieTexture_Generic +{ +public: + MovieTexture_Theora( RageTextureID ID ); +}; +#define USE_MOVIE_TEXTURE_THEORA + +#endif + +/* + * (c) 2005 Glenn Maynard + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */