diff --git a/MOULOpenSourceClientPlugin/Plasma20/MSVC10Projects/Plasma/Apps/AllClient/AllClient.sln b/MOULOpenSourceClientPlugin/Plasma20/MSVC10Projects/Plasma/Apps/AllClient/AllClient.sln index 1d542156..c7d7eb2c 100644 --- a/MOULOpenSourceClientPlugin/Plasma20/MSVC10Projects/Plasma/Apps/AllClient/AllClient.sln +++ b/MOULOpenSourceClientPlugin/Plasma20/MSVC10Projects/Plasma/Apps/AllClient/AllClient.sln @@ -1,5 +1,5 @@ Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual C++ Express 2010 +# Visual Studio 2010 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AllClient", "AllClient.vcxproj", "{23DA297E-0E5A-5BA8-006F-7C7F5AF694C0}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CoreLib", "..\..\CoreLib\CoreLib.vcxproj", "{83A96477-BAEF-4DB7-8134-AEADF4B2E63F}" @@ -222,6 +222,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plPythonPack", "..\plPython {797721D5-0EFE-4912-8F4F-953A71A69B04} = {797721D5-0EFE-4912-8F4F-953A71A69B04} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pfMoviePlayer", "..\..\FeatureLib\pfMoviePlayer\pfMoviePlayer.vcxproj", "{90045C91-576B-4639-8C2F-A6B5B70EC6A1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug_Internal|Win32 = Debug_Internal|Win32 @@ -1022,6 +1024,14 @@ Global {84868043-5563-435A-A176-76A059653D5C}.Release_Internal|Win32.Build.0 = Release_Internal|Win32 {84868043-5563-435A-A176-76A059653D5C}.Release|Win32.ActiveCfg = Release|Win32 {84868043-5563-435A-A176-76A059653D5C}.Release|Win32.Build.0 = Release|Win32 + {90045C91-576B-4639-8C2F-A6B5B70EC6A1}.Debug_Internal|Win32.ActiveCfg = Debug|Win32 + {90045C91-576B-4639-8C2F-A6B5B70EC6A1}.Debug_Internal|Win32.Build.0 = Debug|Win32 + {90045C91-576B-4639-8C2F-A6B5B70EC6A1}.Debug|Win32.ActiveCfg = Debug|Win32 + {90045C91-576B-4639-8C2F-A6B5B70EC6A1}.Debug|Win32.Build.0 = Debug|Win32 + {90045C91-576B-4639-8C2F-A6B5B70EC6A1}.Release_Internal|Win32.ActiveCfg = Release_Internal|Win32 + {90045C91-576B-4639-8C2F-A6B5B70EC6A1}.Release_Internal|Win32.Build.0 = Release_Internal|Win32 + {90045C91-576B-4639-8C2F-A6B5B70EC6A1}.Release|Win32.ActiveCfg = Release|Win32 + {90045C91-576B-4639-8C2F-A6B5B70EC6A1}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MOULOpenSourceClientPlugin/Plasma20/MSVC10Projects/Plasma/Apps/plClient/plClient.vcxproj b/MOULOpenSourceClientPlugin/Plasma20/MSVC10Projects/Plasma/Apps/plClient/plClient.vcxproj index 209b44a7..9a218b20 100644 --- a/MOULOpenSourceClientPlugin/Plasma20/MSVC10Projects/Plasma/Apps/plClient/plClient.vcxproj +++ b/MOULOpenSourceClientPlugin/Plasma20/MSVC10Projects/Plasma/Apps/plClient/plClient.vcxproj @@ -109,7 +109,7 @@ winhttp.lib;ws2_32.lib;strmiids.lib;vfw32.lib;version.lib;Rpcrt4.lib;d3dx9.lib;dinput8.lib;dxerr.lib;dxguid.lib;dsound.lib;OpenAL32.lib;libeay32.lib;NxCharacter.lib;PhysXLoader.lib;libogg_static.lib;libvorbis_static.lib;libvorbisfile_static.lib;libspeex.lib;libjpeg.lib;zlibd.lib;libpngd.lib;NxCooking.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) true - ..\..\..\..\..\StaticSDKs\Win32\DX9.0c\Lib\x86;..\..\..\..\..\StaticSDKs\Win32\OpenAL 1.1 with EFX SDK\libs\Win32;..\..\..\..\..\StaticSDKs\Win32\OpenSSL\lib;..\..\..\..\..\StaticSDKs\Win32\PhysX\lib\win32;..\..\..\..\..\StaticSDKs\Win32\xiph\lib\Debug;..\..\..\..\..\StaticSDKs\XPlatform\expat-1.95.7\StaticLibs\Win32;..\..\..\..\..\StaticSDKs\XPlatform\jpeg-8c-rgba;..\..\..\..\..\StaticSDKs\XPlatform\zlib\lib;..\..\..\..\..\StaticSDKs\XPlatform\png\lib;%(AdditionalLibraryDirectories) + ..\..\..\..\..\StaticSDKs\Win32\DX9.0c\Lib\x86;..\..\..\..\..\StaticSDKs\Win32\OpenAL 1.1 with EFX SDK\libs\Win32;..\..\..\..\..\StaticSDKs\Win32\OpenSSL\lib;..\..\..\..\..\StaticSDKs\Win32\PhysX\lib\win32;..\..\..\..\..\StaticSDKs\Win32\opus\opus-1.3.1\win32\VS2015\Win32\Release\opus;D:\Users\richards\Desktop\Minkata-build-VS2010\CWE-ou-minkata\MOULOpenSourceClientPlugin\StaticSDKs\Win32\webm\libvpx_build_win32_vs14;..\..\..\..\..\StaticSDKs\Win32\xiph\lib\Debug;..\..\..\..\..\StaticSDKs\XPlatform\expat-1.95.7\StaticLibs\Win32;..\..\..\..\..\StaticSDKs\XPlatform\jpeg-8c-rgba;..\..\..\..\..\StaticSDKs\XPlatform\zlib\lib;..\..\..\..\..\StaticSDKs\XPlatform\png\lib;%(AdditionalLibraryDirectories) libc.lib;libcd.lib;libci.lib;libcid.lib;libcmtd.lib;libcmt.lib;%(IgnoreSpecificDefaultLibraries) true true @@ -164,7 +164,7 @@ winhttp.lib;ws2_32.lib;strmiids.lib;vfw32.lib;version.lib;Rpcrt4.lib;d3dx9.lib;dinput8.lib;dxerr.lib;dxguid.lib;dsound.lib;OpenAL32.lib;libeay32.lib;NxCharacter.lib;PhysXLoader.lib;libogg_static.lib;libvorbis_static.lib;libvorbisfile_static.lib;libspeex.lib;libjpeg.lib;zlibd.lib;libpngd.lib;NxCooking.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) true - ..\..\..\..\..\StaticSDKs\Win32\DX9.0c\Lib\x86;..\..\..\..\..\StaticSDKs\Win32\OpenAL 1.1 with EFX SDK\libs\Win32;..\..\..\..\..\StaticSDKs\Win32\OpenSSL\lib;..\..\..\..\..\StaticSDKs\Win32\PhysX\lib\win32;..\..\..\..\..\StaticSDKs\Win32\xiph\lib\Debug;..\..\..\..\..\StaticSDKs\XPlatform\expat-1.95.7\StaticLibs\Win32;..\..\..\..\..\StaticSDKs\XPlatform\jpeg-8c-rgba;..\..\..\..\..\StaticSDKs\XPlatform\zlib\lib;..\..\..\..\..\StaticSDKs\XPlatform\png\lib;%(AdditionalLibraryDirectories) + ..\..\..\..\..\StaticSDKs\Win32\DX9.0c\Lib\x86;..\..\..\..\..\StaticSDKs\Win32\OpenAL 1.1 with EFX SDK\libs\Win32;..\..\..\..\..\StaticSDKs\Win32\OpenSSL\lib;..\..\..\..\..\StaticSDKs\Win32\PhysX\lib\win32;..\..\..\..\..\StaticSDKs\Win32\opus\opus-1.3.1\win32\VS2015\Win32\Release\opus;D:\Users\richards\Desktop\Minkata-build-VS2010\CWE-ou-minkata\MOULOpenSourceClientPlugin\StaticSDKs\Win32\webm\libvpx_build_win32_vs14;..\..\..\..\..\StaticSDKs\Win32\xiph\lib\Debug;..\..\..\..\..\StaticSDKs\XPlatform\expat-1.95.7\StaticLibs\Win32;..\..\..\..\..\StaticSDKs\XPlatform\jpeg-8c-rgba;..\..\..\..\..\StaticSDKs\XPlatform\zlib\lib;..\..\..\..\..\StaticSDKs\XPlatform\png\lib;%(AdditionalLibraryDirectories) libc.lib;libcd.lib;libci.lib;libcid.lib;libcmtd.lib;libcmt.lib;%(IgnoreSpecificDefaultLibraries) true true @@ -199,7 +199,7 @@ Full OnlyExplicitInline ../../../../Sources/Plasma/CoreLib;../../../../Sources/Plasma/NucleusLib;../../../../Sources/Plasma/NucleusLib/inc;../../../../Sources/Plasma/PubUtilLib;../../../../Sources/Plasma/PubUtilLib/inc;../../../../Sources/Plasma/FeatureLib;../../../../Sources/Plasma/FeatureLib/inc;../../../../SDKs/XPlatform/Cypython-2.3.3/Include;../../../../SDKs/XPlatform/Cypython-2.3.3/PC;../../../../SDKs/XPlatform/Cypython-2.3.3/pyconfig_static;../../../../../StaticSDKs/Win32/DX9.0c/include;../../../../../StaticSDKs/Win32/OpenAL 1.1 with EFX SDK/include;../../../../../StaticSDKs/Win32/OpenSSL/include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + WIN32;NDEBUG;PLASMA_USE_WEBM;_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) true MultiThreadedDLL true @@ -223,7 +223,7 @@ winhttp.lib;ws2_32.lib;strmiids.lib;vfw32.lib;version.lib;Rpcrt4.lib;d3dx9.lib;dinput8.lib;dxerr.lib;dxguid.lib;dsound.lib;OpenAL32.lib;libeay32.lib;NxCharacter.lib;PhysXLoader.lib;libogg_static.lib;libvorbis_static.lib;libvorbisfile_static.lib;libspeex.lib;libjpeg.lib;zlib.lib;libpng.lib;NxCooking.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) true - ..\..\..\..\..\StaticSDKs\Win32\DX9.0c\Lib\x86;..\..\..\..\..\StaticSDKs\Win32\OpenAL 1.1 with EFX SDK\libs\Win32;..\..\..\..\..\StaticSDKs\Win32\OpenSSL\lib;..\..\..\..\..\StaticSDKs\Win32\PhysX\lib\win32;..\..\..\..\..\StaticSDKs\Win32\xiph\lib\Release;..\..\..\..\..\StaticSDKs\XPlatform\expat-1.95.7\StaticLibs\Win32;..\..\..\..\..\StaticSDKs\XPlatform\jpeg-8c-rgba;..\..\..\..\..\StaticSDKs\XPlatform\zlib\lib;..\..\..\..\..\StaticSDKs\XPlatform\png\lib;%(AdditionalLibraryDirectories) + ..\..\..\..\..\StaticSDKs\Win32\DX9.0c\Lib\x86;..\..\..\..\..\StaticSDKs\Win32\OpenAL 1.1 with EFX SDK\libs\Win32;..\..\..\..\..\StaticSDKs\Win32\OpenSSL\lib;..\..\..\..\..\StaticSDKs\Win32\PhysX\lib\win32;..\..\..\..\..\StaticSDKs\Win32\opus\opus-1.3.1\win32\VS2015\Win32\Release\opus;D:\Users\richards\Desktop\Minkata-build-VS2010\CWE-ou-minkata\MOULOpenSourceClientPlugin\StaticSDKs\Win32\webm\libvpx_build_win32_vs14;..\..\..\..\..\StaticSDKs\Win32\xiph\lib\Release;..\..\..\..\..\StaticSDKs\XPlatform\expat-1.95.7\StaticLibs\Win32;..\..\..\..\..\StaticSDKs\XPlatform\jpeg-8c-rgba;..\..\..\..\..\StaticSDKs\XPlatform\zlib\lib;..\..\..\..\..\StaticSDKs\XPlatform\png\lib;%(AdditionalLibraryDirectories) libc.lib;libci.lib;libcmt.lib;%(IgnoreSpecificDefaultLibraries) true false @@ -258,7 +258,7 @@ Full OnlyExplicitInline ../../../../Sources/Plasma/CoreLib;../../../../Sources/Plasma/NucleusLib;../../../../Sources/Plasma/NucleusLib/inc;../../../../Sources/Plasma/PubUtilLib;../../../../Sources/Plasma/PubUtilLib/inc;../../../../Sources/Plasma/FeatureLib;../../../../Sources/Plasma/FeatureLib/inc;../../../../SDKs/XPlatform/Cypython-2.3.3/Include;../../../../SDKs/XPlatform/Cypython-2.3.3/PC;../../../../SDKs/XPlatform/Cypython-2.3.3/pyconfig_static;../../../../../StaticSDKs/Win32/DX9.0c/include;../../../../../StaticSDKs/Win32/OpenAL 1.1 with EFX SDK/include;../../../../../StaticSDKs/Win32/OpenSSL/include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;PLASMA_EXTERNAL_RELEASE;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;PLASMA_EXTERNAL_RELEASE;PLASMA_USE_WEBM;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) true MultiThreadedDLL @@ -283,7 +283,7 @@ winhttp.lib;ws2_32.lib;strmiids.lib;vfw32.lib;version.lib;Rpcrt4.lib;d3dx9.lib;dinput8.lib;dxerr.lib;dxguid.lib;dsound.lib;OpenAL32.lib;libeay32.lib;NxCharacter.lib;PhysXLoader.lib;libogg_static.lib;libvorbis_static.lib;libvorbisfile_static.lib;libspeex.lib;libjpeg.lib;zlib.lib;libpng.lib;NxCooking.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) true - ..\..\..\..\..\StaticSDKs\Win32\DX9.0c\Lib\x86;..\..\..\..\..\StaticSDKs\Win32\OpenAL 1.1 with EFX SDK\libs\Win32;..\..\..\..\..\StaticSDKs\Win32\OpenSSL\lib;..\..\..\..\..\StaticSDKs\Win32\PhysX\lib\win32;..\..\..\..\..\StaticSDKs\Win32\xiph\lib\Release;..\..\..\..\..\StaticSDKs\XPlatform\expat-1.95.7\StaticLibs\Win32;..\..\..\..\..\StaticSDKs\XPlatform\jpeg-8c-rgba;..\..\..\..\..\StaticSDKs\XPlatform\zlib\lib;..\..\..\..\..\StaticSDKs\XPlatform\png\lib;%(AdditionalLibraryDirectories) + ..\..\..\..\..\StaticSDKs\Win32\DX9.0c\Lib\x86;..\..\..\..\..\StaticSDKs\Win32\OpenAL 1.1 with EFX SDK\libs\Win32;..\..\..\..\..\StaticSDKs\Win32\OpenSSL\lib;..\..\..\..\..\StaticSDKs\Win32\PhysX\lib\win32;..\..\..\..\..\StaticSDKs\Win32\opus\opus-1.3.1\win32\VS2015\Win32\Release\opus;D:\Users\richards\Desktop\Minkata-build-VS2010\CWE-ou-minkata\MOULOpenSourceClientPlugin\StaticSDKs\Win32\webm\libvpx_build_win32_vs14;..\..\..\..\..\StaticSDKs\Win32\xiph\lib\Release;..\..\..\..\..\StaticSDKs\XPlatform\expat-1.95.7\StaticLibs\Win32;..\..\..\..\..\StaticSDKs\XPlatform\jpeg-8c-rgba;..\..\..\..\..\StaticSDKs\XPlatform\zlib\lib;..\..\..\..\..\StaticSDKs\XPlatform\png\lib;%(AdditionalLibraryDirectories) libc.lib;libci.lib;libcmt.lib;%(IgnoreSpecificDefaultLibraries) true false @@ -495,6 +495,9 @@ {cd679637-008c-7e09-198f-51059c2b48e8} false + + {90045c91-576b-4639-8c2f-a6b5b70ec6a1} + {797721d5-0efe-4912-8f4f-953a71a69b04} false diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/plClient.cpp b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/plClient.cpp index bcffb4e4..4367ad07 100644 --- a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/plClient.cpp +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/plClient.cpp @@ -115,6 +115,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com #include "../plProgressMgr/plProgressMgr.h" #include "../plPipeline/plDTProgressMgr.h" #include "../plPipeline/plBinkPlayer.h" +#include "../pfMoviePlayer/plMoviePlayer.h" #include "../plMessage/plMovieMsg.h" #include "../plSDL/plSDL.h" @@ -273,9 +274,6 @@ hsBool plClient::Shutdown() IKillMovies(); plgAudioSys::Activate(false); -#ifdef USE_BINK_SDK - plBinkPlayer::DeInit(); -#endif // // Get any proxies to commit suicide. plProxyDrawMsg* nuke = TRACKED_NEW plProxyDrawMsg(plProxyDrawMsg::kAllTypes @@ -887,8 +885,7 @@ hsBool plClient::IHandleMovieMsg(plMovieMsg* mov) } if( i == fMovies.GetCount() ) { - - fMovies.Append(TRACKED_NEW plBinkPlayer); + fMovies.Append(new plMoviePlayer); fMovies[i]->SetFileName(mov->GetFileName()); } @@ -930,7 +927,7 @@ hsBool plClient::IHandleMovieMsg(plMovieMsg* mov) fMovies[i]->SetVolume(mov->GetVolume()); if( mov->GetCmd() & plMovieMsg::kStart ) - fMovies[i]->Start(fPipeline, fWindowHndl); + fMovies[i]->Start(); if( mov->GetCmd() & plMovieMsg::kPause ) fMovies[i]->Pause(true); if( mov->GetCmd() & plMovieMsg::kResume ) @@ -1514,14 +1511,10 @@ hsBool plClient::StartInit() plgAudioSys::Activate(true); -#ifdef USE_BINK_SDK plConst(hsScalar) delay(2.f); - //commenting out publisher splash for MORE - //IPlayIntroBink("avi/intro0.bik", delay, 0.f, 0.f, 1.f, 1.f, 0.75); - //if( GetDone() ) return false; - IPlayIntroBink("avi/intro1.bik", 0.f, 0.f, 0.f, 1.f, 1.f, 0.75); - if( GetDone() ) return false; -#endif // USE_BINK_SDK + IPlayIntroMovie("avi/intro1.webm", 0.f, 0.f, 0.f, 1.f, 1.f, 0.75); + IPlayIntroMovie("avi/URULiveIntro.webm", 0.f, 0.f, 0.f, 1.f, 1.f, 0.75); + plgDispatch::Dispatch()->RegisterForExactType(plMovieMsg::Index(), GetKey()); // @@ -1984,56 +1977,55 @@ void plClient::IKillMovies() fMovies.Reset(); } -#ifdef USE_BINK_SDK -hsBool plClient::IPlayIntroBink(const char* movieName, hsScalar endDelay, hsScalar posX, hsScalar posY, hsScalar scaleX, hsScalar scaleY, hsScalar volume /* = 1.0 */) +hsBool plClient::IPlayIntroMovie(const char* movieName, hsScalar endDelay, hsScalar posX, hsScalar posY, hsScalar scaleX, hsScalar scaleY, hsScalar volume /* = 1.0 */) { - SetQuitIntro(false); - plBinkPlayer player; - player.SetPosition(posX, posY); - player.SetScale(scaleX, scaleY); - player.SetFileName(movieName); - player.SetFadeToTime(endDelay); - player.SetFadeToColor(hsColorRGBA().Set(0, 0, 0, 1.f)); - player.SetVolume(volume); - bool firstTry = true; // flag to make sure that we don't quit before we even start - - if( player.Start(fPipeline, fWindowHndl) ) - { - while( true ) - { - if( fInstance ) - fInstance->fMessagePumpProc(); - - if( GetDone() ) - return true; - if (firstTry) - { - firstTry = false; - SetQuitIntro(false); - } - else - { - if( GetQuitIntro() ) - return true; - } - - hsBool done = false; - if( !fPipeline->BeginRender() ) - { - fPipeline->ClearRenderTarget(); - done = !player.NextFrame(); - - fPipeline->EndRender(); - } - - if( done ) - return true; - } - return true; - } - return false; + SetQuitIntro(false); + plMoviePlayer player; + player.SetPosition(posX, posY); + player.SetScale(scaleX, scaleY); + player.SetFileName(movieName); + player.SetFadeToTime(endDelay); + player.SetFadeToColor(hsColorRGBA().Set(0, 0, 0, 1.f)); + player.SetVolume(volume); + bool firstTry = true; // flag to make sure that we don't quit before we even start + + if (player.Start()) + { + while (true) + { + if (fInstance) + fInstance->fMessagePumpProc(); + + if (GetDone()) + return true; + if (firstTry) + { + firstTry = false; + SetQuitIntro(false); + } + else + { + if (GetQuitIntro()) + return true; + } + + bool done = false; + if (!fPipeline->BeginRender()) + { + fPipeline->ClearRenderTarget(); + done = !player.NextFrame(); + + fPipeline->RenderScreenElements(); + fPipeline->EndRender(); + } + + if (done) + return true; + } + return true; + } + return false; } -#endif // USE_BINK_SDK hsBool plClient::IFlushRenderRequests() { diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/plClient.h b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/plClient.h index 165760b2..5b718edc 100644 --- a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/plClient.h +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/plClient.h @@ -79,6 +79,7 @@ class plClientMsg; class plLocation; class plMovieMsg; class plBinkPlayer; +class plMoviePlayer; class plPreloaderMsg; class plNetCommAuthMsg; class plAgeLoaded2Msg; @@ -150,7 +151,7 @@ protected: int fQuality; hsBool fQuitIntro; - hsTArray fMovies; + hsTArray fMovies; hsBool fPatchGlobalAges; @@ -191,9 +192,7 @@ protected: void IProcessRenderRequests(hsTArray& reqs); void IAddRenderRequest(plRenderRequest* req); -#ifdef USE_BINK_SDK - hsBool IPlayIntroBink(const char* movieName, hsScalar endDelay, hsScalar posX, hsScalar posY, hsScalar scaleX, hsScalar scaleY, hsScalar volume = 1.0); -#endif // USE_BINK_SDK + hsBool IPlayIntroMovie(const char* movieName, hsScalar endDelay, hsScalar posX, hsScalar posY, hsScalar scaleX, hsScalar scaleY, hsScalar volume = 1.0); hsBool IHandleMovieMsg(plMovieMsg* mov); void IKillMovies(); void IServiceMovies(); diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfLocalizationMgr/pfLocalizationDataMgr.cpp b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfLocalizationMgr/pfLocalizationDataMgr.cpp index 8df445a9..bf2d7ffa 100644 --- a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfLocalizationMgr/pfLocalizationDataMgr.cpp +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfLocalizationMgr/pfLocalizationDataMgr.cpp @@ -624,7 +624,7 @@ void LocalizationDatabase::IVerifyElement(const std::wstring &ageName, const std int numLocales = plLocalization::GetNumLocales(); for (int curLocale = 0; curLocale <= numLocales; curLocale++) { - char *name = plLocalization::GetLanguageName((plLocalization::Language)curLocale); + const char *name = plLocalization::GetLanguageName((plLocalization::Language)curLocale); wchar_t *wName = hsStringToWString(name); languageNames.push_back(wName); delete [] wName; @@ -939,7 +939,7 @@ pfLocalizationDataMgr::localizedElement pfLocalizationDataMgr::ICreateLocalizedE for (int curLocale = 0; curLocale <= numLocales; curLocale++) { - char *name = plLocalization::GetLanguageName((plLocalization::Language)curLocale); + const char *name = plLocalization::GetLanguageName((plLocalization::Language)curLocale); wchar_t *wName = hsStringToWString(name); retVal[wName] = L""; delete [] wName; @@ -953,7 +953,7 @@ pfLocalizationDataMgr::localizedElement pfLocalizationDataMgr::ICreateLocalizedE std::wstring pfLocalizationDataMgr::IGetCurrentLanguageName() { std::wstring retVal; - char *name = plLocalization::GetLanguageName(plLocalization::GetLanguage()); + const char *name = plLocalization::GetLanguageName(plLocalization::GetLanguage()); wchar_t *wName = hsStringToWString(name); retVal = wName; delete [] wName; @@ -969,7 +969,7 @@ std::vector pfLocalizationDataMgr::IGetAllLanguageNames() for (int curLocale = 0; curLocale <= numLocales; curLocale++) { - char *name = plLocalization::GetLanguageName((plLocalization::Language)curLocale); + const char *name = plLocalization::GetLanguageName((plLocalization::Language)curLocale); wchar_t *wName = hsStringToWString(name); retVal.push_back(wName); delete [] wName; diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/plMoviePlayer.cpp b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/plMoviePlayer.cpp new file mode 100644 index 00000000..e528719b --- /dev/null +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/plMoviePlayer.cpp @@ -0,0 +1,452 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com + or by snail mail at: + Cyan Worlds, Inc. + 14617 N Newport Hwy + Mead, WA 99021 + +*==LICENSE==*/ + +#include "plMoviePlayer.h" + +#include "hsConfig.h" +#ifdef PLASMA_USE_WEBM +# define VPX_CODEC_DISABLE_COMPAT 1 +# include +# include +# define iface (vpx_codec_vp9_dx()) +# include + +# define WEBM_CODECID_VP9 "V_VP9" +# define WEBM_CODECID_OPUS "A_OPUS" +#endif + +#include "hsResMgr.h" +#include "hsTimer.h" +#include "../plAudio/plWin32VideoSound.h" +#include "../plGImage/plMipmap.h" +#include "../pnKeyedObject/plUoid.h" +#include "../plPipeline/hsGDeviceRef.h" +#include "../plPipeline/plPlates.h" +#include "../plResMgr/plLocalization.h" +#include "../plStatusLog/plStatusLog.h" +#include "../plFile/plFileUtils.h" + +#include "plPlanarImage.h" +#include "webm/mkvreader.hpp" +#include "webm/mkvparser.hpp" + +#define SAFE_OP(x, err) \ +{ \ + Int64 ret = 0; \ + ret = x; \ + if (ret < 0) { \ + hsAssert(false, "failed to " err); \ + return false; \ + } \ +} + +// ===================================================== + +class VPX +{ + VPX() { } + +#ifdef PLASMA_USE_WEBM +public: + vpx_codec_ctx_t codec; + + ~VPX() + { + if (vpx_codec_destroy(&codec)) + hsAssert(false, vpx_codec_error_detail(&codec)); + } + + static VPX* Create() + { + VPX* instance = new VPX; + if (vpx_codec_dec_init(&instance->codec, iface, nullptr, 0)) { + hsAssert(false, vpx_codec_error_detail(&instance->codec)); + delete instance; + return nullptr; + } + return instance; + } + + vpx_image_t* Decode(UInt8* buf, UInt32 size) + { + if (vpx_codec_decode(&codec, buf, size, nullptr, 0) != VPX_CODEC_OK) { + const char* detail = vpx_codec_error_detail(&codec); + hsAssert(false, detail ? detail : "unspecified decode error"); + return nullptr; + } + + vpx_codec_iter_t iter = nullptr; + // ASSUMPTION: only one image per frame + // if this proves false, move decoder function into IProcessVideoFrame + return vpx_codec_get_frame(&codec, &iter); + } +#endif +}; + +// ===================================================== + +class TrackMgr +{ +protected: + const mkvparser::Track* fTrack; + const mkvparser::BlockEntry* fCurrentBlock; + Int32 fStatus; + +public: + TrackMgr(const mkvparser::Track* track) : fTrack(track), fCurrentBlock(nullptr), fStatus(0) { } + + const mkvparser::Track* GetTrack() { return fTrack; } + + bool GetFrames(mkvparser::MkvReader* reader, Int64 movieTimeNs, std::vector& frames) + { + // If we have no block yet, grab the first one + if (!fCurrentBlock) + fStatus = fTrack->GetFirst(fCurrentBlock); + + // Continue through the blocks until our current movie time + while (fCurrentBlock && fStatus == 0) { + const mkvparser::Block* block = fCurrentBlock->GetBlock(); + Int64 time = block->GetTime(fCurrentBlock->GetCluster()) - fTrack->GetCodecDelay(); + if (time <= movieTimeNs) { + // We want to play this block, add it to the frames buffer + frames.reserve(frames.size() + block->GetFrameCount()); + for (Int32 i = 0; i < block->GetFrameCount(); i++) { + const mkvparser::Block::Frame data = block->GetFrame(i); + UInt8* buf = new UInt8[data.len]; + data.Read(reader, buf); + frames.push_back(std::make_pair(std::auto_ptr(buf), static_cast(data.len))); + } + fStatus = fTrack->GetNext(fCurrentBlock, fCurrentBlock); + } else { + // We've got all frames that have to play... come back for more later! + return true; + } + } + + return false; // No more blocks... We're done! + } +}; + +// ===================================================== + +plMoviePlayer::plMoviePlayer() + : fPlate(nullptr), + fTexture(nullptr), + fReader(nullptr), + fMovieTime(0), + fLastFrameTime(0), + fPosition(hsPoint2()), + fPlaying(false), + fPaused(false), + fMoviePath(nullptr) +{ + fScale.Set(1.0f, 1.0f); +} + +plMoviePlayer::~plMoviePlayer() +{ + if (fPlate) + // The plPlate owns the Mipmap Texture, so it destroys it for us + plPlateManager::Instance().DestroyPlate(fPlate); +#ifdef PLASMA_USE_WEBM + if (fReader) { + fReader->Close(); + delete fReader; + } +#endif +} + +bool plMoviePlayer::IOpenMovie() +{ +#ifdef PLASMA_USE_WEBM + if (!plFileUtils::FileExists(fMoviePath)) { + plStatusLog::AddLineS("movie.log", "%s: Tried to play a movie that doesn't exist.", fMoviePath); + return false; + } + + // open the movie with libwebm + fReader = new mkvparser::MkvReader; + SAFE_OP(fReader->Open(fMoviePath), "open movie"); + + // opens the segment + // it contains everything you ever want to know about the movie + long long pos = 0; + mkvparser::EBMLHeader ebmlHeader; + SAFE_OP(ebmlHeader.Parse(fReader, pos), "read mkv header"); + mkvparser::Segment* seg; + SAFE_OP(mkvparser::Segment::CreateInstance(fReader, pos, seg), "get segment info"); + SAFE_OP(seg->Load(), "load segment from webm"); + fSegment.reset(seg); + + // Use first tracks unless another one matches the current game language + const mkvparser::Tracks* tracks = fSegment->GetTracks(); + for (UInt32 i = 0; i < tracks->GetTracksCount(); ++i) { + const mkvparser::Track* track = tracks->GetTrackByIndex(i); + if (!track) + continue; + + switch (track->GetType()) { + case mkvparser::Track::kAudio: + if (!fAudioTrack.get() || ICheckLanguage(track)) + fAudioTrack.reset(new TrackMgr(track)); + break; + case mkvparser::Track::kVideo: + if (!fVideoTrack.get() || ICheckLanguage(track)) + fVideoTrack.reset(new TrackMgr(track)); + break; + } + } + return true; +#else + return false; +#endif +} + +bool plMoviePlayer::ILoadAudio() +{ +#ifdef PLASMA_USE_WEBM + // Fetch audio track information + if (!fAudioTrack.get()) + return false; + const mkvparser::AudioTrack* audio = static_cast(fAudioTrack->GetTrack()); + plWAVHeader header; + header.fFormatTag = plWAVHeader::kPCMFormatTag; + header.fNumChannels = audio->GetChannels(); + header.fBitsPerSample = audio->GetBitDepth() == 8 ? 8 : 16; + header.fNumSamplesPerSec = 48000; // OPUS specs say we shall always decode at 48kHz + header.fBlockAlign = header.fNumChannels * header.fBitsPerSample / 8; + header.fAvgBytesPerSec = header.fNumSamplesPerSec * header.fBlockAlign; + fAudioSound.reset(new plWin32VideoSound(header)); + + // Initialize Opus + if (strncmp(audio->GetCodecId(), WEBM_CODECID_OPUS, arrsize(WEBM_CODECID_OPUS)) != 0) { + plStatusLog::AddLineS("movie.log", "%s: Not an Opus audio track!", fMoviePath); + return false; + } + int error; + OpusDecoder* opus = opus_decoder_create(48000, audio->GetChannels(), &error); + if (error != OPUS_OK) + hsAssert(false, "Error occured initalizing opus"); + + // Decode audio track + std::vector frames; + fAudioTrack->GetFrames(fReader, fSegment->GetDuration(), frames); + static const int maxFrameSize = 5760; // for max packet duration at 48kHz + std::vector decoded; + decoded.reserve(frames.size() * audio->GetChannels() * maxFrameSize); + + Int16* frameData = new Int16[maxFrameSize * audio->GetChannels()]; + for (std::vector::iterator& frame = frames.begin(); frame != frames.end(); frame++) { + const std::auto_ptr& buf = frame->first; + Int32 size = frame->second; + + int samples = opus_decode(opus, buf.get(), size, frameData, maxFrameSize, 0); + if (samples < 0) + hsAssert(false, "opus error"); + for (size_t i = 0; i < samples * audio->GetChannels(); i++) + decoded.push_back(frameData[i]); + } + delete[] frameData; + + fAudioSound->FillSoundBuffer(reinterpret_cast(decoded.data()), decoded.size() * sizeof(Int16)); + opus_decoder_destroy(opus); + return true; +#else + return false; +#endif +} + +bool plMoviePlayer::ICheckLanguage(const mkvparser::Track* track) +{ + std::set codes = plLocalization::GetLanguageCodes(plLocalization::GetLanguage()); + if (codes.find(track->GetLanguage()) != codes.end()) + return true; + return false; +} + +void plMoviePlayer::IProcessVideoFrame(const std::vector& frames) +{ +#ifdef PLASMA_USE_WEBM + vpx_image_t* img = nullptr; + + // We have to decode all the frames, but we only want to display the most recent one to the user. + for (std::vector::const_iterator frame = frames.begin(); frame != frames.end(); frame++) { + const std::auto_ptr& buf = frame->first; + UInt32 size = static_cast(frame->second); + img = fVpx->Decode(buf.get(), size); + } + + if (img) { + // According to VideoLAN[1], I420 is the most common image format in videos. I am inclined to believe this as our + // attemps to convert the common Uru videos use I420 image data. So, as a shortcut, we will only implement that format. + // If for some reason we need other formats, please, be my guest! + // [1] = http://wiki.videolan.org/YUV#YUV_4:2:0_.28I420.2FJ420.2FYV12.29 + switch (img->fmt) { + case VPX_IMG_FMT_I420: + plPlanarImage::Yuv420ToRgba(img->d_w, img->d_h, reinterpret_cast(img->stride), img->planes, reinterpret_cast(fTexture->GetImage())); + break; + + default: + hsAssert(false, "image format"); + } + + // Flush new data to the device + if (fTexture->GetDeviceRef()) + fTexture->GetDeviceRef()->SetDirty(true); + fPlate->SetVisible(true); + } +#endif +} + +bool plMoviePlayer::Start() +{ + if (fPlaying) + return false; + +#ifdef PLASMA_USE_WEBM + if (!IOpenMovie()) + return false; + hsAssert(fVideoTrack, "nil video track -- expect bad things to happen!"); + + // Initialize VPX + const mkvparser::VideoTrack* video = static_cast(fVideoTrack->GetTrack()); + if (strncmp(video->GetCodecId(), WEBM_CODECID_VP9, arrsize(WEBM_CODECID_VP9)) != 0) { + plStatusLog::AddLineS("movie.log", "%s: Not a VP9 video track!", fMoviePath); + return false; + } + if (VPX* vpx = VPX::Create()) + fVpx.reset(vpx); + else + return false; + + // Decode the audio track and load it into a sound buffer + if (!ILoadAudio()) + return false; + + fLastFrameTime = static_cast(hsTimer::GetMilliSeconds()); + fAudioSound->Play(); + fPlaying = true; + + return true; +#else + return false; +#endif // MOVIE_AVAILABLE +} + +void plMoviePlayer::IInitPlate(UInt32 width, UInt32 height) +{ + // Need to figure out scaling based on pipe size. + plPlateManager& plateMgr = plPlateManager::Instance(); + float plateWidth = width * fScale.fX; + float plateHeight = height * fScale.fY; + if (plateWidth > plateMgr.GetPipeWidth() || plateHeight > plateMgr.GetPipeHeight()) { + float scale = std::min(plateMgr.GetPipeWidth() / plateWidth, plateMgr.GetPipeHeight() / plateHeight); + plateWidth *= scale; + plateHeight *= scale; + } + plateMgr.CreatePlate(&fPlate, fPosition.fX, fPosition.fY, 0, 0); + plateMgr.SetPlatePixelSize(fPlate, plateWidth, plateHeight); + fTexture = fPlate->CreateMaterial(width, height, false); +} + +bool plMoviePlayer::NextFrame() +{ + if (!fPlaying) + return false; + + Int64 frameTime = static_cast(hsTimer::GetMilliSeconds()); + Int64 frameTimeDelta = frameTime - fLastFrameTime; + fLastFrameTime = frameTime; + + if (fPaused) + return true; + +#ifdef PLASMA_USE_WEBM + // Get our current timecode + fMovieTime += frameTimeDelta; + + std::vector video; + if (fVideoTrack.get() != nullptr || !fVideoTrack->GetFrames(fReader, fMovieTime * 1000000, video)) { + Stop(); + return false; + } + + // If the pipeline's device was invalidated, the plate will be invalid. Recreate now. + if (!fPlate) { + const mkvparser::VideoTrack* vt = static_cast(fVideoTrack->GetTrack()); + IInitPlate(static_cast(vt->GetWidth()), static_cast(vt->GetHeight())); + hsAssert(fPlate, "failed to init plMoviePlayer plate -- bad things will happen!"); + } + + // Show our mess + IProcessVideoFrame(video); + fAudioSound->RefreshVolume(); + + return true; +#else + return false; +#endif // MOVIE_AVAILABLE +} + +bool plMoviePlayer::Pause(bool on) +{ + if (!fPlaying) + return false; + + fAudioSound->Pause(on); + fPaused = on; + return true; +} + +bool plMoviePlayer::Stop() +{ + fPlaying = false; + if (fAudioSound.get() != nullptr) + fAudioSound->Stop(); + if (fPlate) + fPlate->SetVisible(false); + + for (std::vector::iterator cb = fCallbacks.begin(); cb != fCallbacks.end(); cb++) + (*cb)->Send(); + fCallbacks.clear(); + return true; +} diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/plMoviePlayer.h b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/plMoviePlayer.h new file mode 100644 index 00000000..64b45a0b --- /dev/null +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/plMoviePlayer.h @@ -0,0 +1,131 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com + or by snail mail at: + Cyan Worlds, Inc. + 14617 N Newport Hwy + Mead, WA 99021 + +*==LICENSE==*/ + +#ifndef _plMoviePlayer_inc +#define _plMoviePlayer_inc + +#include "HeadSpin.h" +#include "hsPoint2.h" +#include "hsColorRGBA.h" +#include "../plMessage/plMovieMsg.h" + +#include +#include +#include + +namespace mkvparser +{ + class BlockEntry; + class MkvReader; + class Segment; + class Track; +} + +typedef std::pair, UInt32> blkbuf_t; + +class plMoviePlayer +{ +protected: + class plPlate* fPlate; + class plMipmap* fTexture; + + mkvparser::MkvReader* fReader; + std::auto_ptr fSegment; + std::auto_ptr fAudioTrack, fVideoTrack; // TODO: vector of tracks? + std::auto_ptr fAudioSound; + std::auto_ptr fVpx; + + UInt64 fMovieTime, fLastFrameTime; // in ms + hsPoint2 fPosition, fScale; + const char *fMoviePath; + + bool fPlaying; + bool fPaused; + + void IInitPlate(UInt32 width, UInt32 height); + + bool IOpenMovie(); + bool ILoadAudio(); + bool ICheckLanguage(const mkvparser::Track* track); + void IProcessVideoFrame(const std::vector& frames); + +public: + plMoviePlayer(); + ~plMoviePlayer(); + + bool Start(); + bool Pause(bool on); + bool Stop(); + bool NextFrame(); + + void AddCallback(plMessage* msg) { hsRefCnt_SafeRef(msg); fCallbacks.push_back(msg); } + UInt32 GetNumCallbacks() const { return 0; } + plMessage* GetCallback(int i) const { return nullptr; } + + const char *GetFileName() const { return fMoviePath; } + void SetFileName(const char* filename) { fMoviePath = filename; } + + void SetColor(const hsColorRGBA& c) { } + const hsColorRGBA GetColor() const { return hsColorRGBA(); } + + /** The volume is handled by the options menu slider, as of now. */ + void SetVolume(float v) { } + + hsPoint2 GetPosition() const { return fPosition; } + void SetPosition(const hsPoint2& pos) { fPosition = pos; } + void SetPosition(float x, float y) { fPosition.Set(x, y); } + + hsPoint2 GetScale() const { return fScale; } + void SetScale(const hsPoint2& scale) { fScale = scale; } + void SetScale(float x, float y) { fScale.Set(x, y); } + + void SetFadeFromTime(float secs) { } + void SetFadeFromColor(hsColorRGBA c) { } + + void SetFadeToTime(float secs) { } + void SetFadeToColor(hsColorRGBA c) { } + +private: + std::vector fCallbacks; +}; + +#endif // _plMoviePlayer_inc diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/plPlanarImage.cpp b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/plPlanarImage.cpp new file mode 100644 index 00000000..898e3e29 --- /dev/null +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/plPlanarImage.cpp @@ -0,0 +1,97 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com + or by snail mail at: + Cyan Worlds, Inc. + 14617 N Newport Hwy + Mead, WA 99021 + +*==LICENSE==*/ + +#include "plPlanarImage.h" + +/////////////////////////////////////////////////////////////////////////////// + +static UInt8 Clip(UInt32 val) { + if (val < 0) { + return 0; + } else if (val > 255) { + return 255; + } + return static_cast(val); +} + +#define YG 74 /* static_cast(1.164 * 64 + 0.5) */ + +#define UB 127 /* min(63,static_cast(2.018 * 64)) */ +#define UG -25 /* static_cast(-0.391 * 64 - 0.5) */ +#define UR 0 + +#define VB 0 +#define VG -52 /* static_cast(-0.813 * 64 - 0.5) */ +#define VR 102 /* static_cast(1.596 * 64 + 0.5) */ + +// Bias +#define BB UB * 128 + VB * 128 +#define BG UG * 128 + VG * 128 +#define BR UR * 128 + VR * 128 + +void plPlanarImage::Yuv420ToRgba(UInt32 w, UInt32 h, const Int32* stride, UInt8** planes, UInt8* const dest) +{ + const UInt8* y_src = planes[0]; + const UInt8* u_src = planes[1]; + const UInt8* v_src = planes[2]; + + for (UInt32 i = 0; i < h; ++i) + { + for (UInt32 j = 0; j < w; ++j) + { + size_t y_idx = stride[0] * i + j; + size_t u_idx = stride[1] * (i/2) + (j/2); + size_t v_idx = stride[2] * (i/2) + (j/2); + size_t dest_idx = w * i + j; + + UInt32 y = static_cast(y_src[y_idx]); + UInt32 u = static_cast(u_src[u_idx]); + UInt32 v = static_cast(v_src[v_idx]); + UInt32 y1 = (y - 16) * YG; + + dest[dest_idx*4+0] = Clip(((u * UB + v * VB) - (BB) + y1) >> 6); + dest[dest_idx*4+1] = Clip(((u * UG + v * VG) - (BG) + y1) >> 6); + dest[dest_idx*4+2] = Clip(((u * UR + v * VR) - (BR) + y1) >> 6); + dest[dest_idx*4+3] = 0xff; + } + } +} diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/plPlanarImage.h b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/plPlanarImage.h new file mode 100644 index 00000000..33ee4262 --- /dev/null +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/plPlanarImage.h @@ -0,0 +1,53 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com + or by snail mail at: + Cyan Worlds, Inc. + 14617 N Newport Hwy + Mead, WA 99021 + +*==LICENSE==*/ + +#ifndef _plPlanarImage_inc +#define _plPlanarImage_inc + +#include "HeadSpin.h" + +namespace plPlanarImage +{ + void Yuv420ToRgba(UInt32 w, UInt32 h, const Int32* stride, UInt8** planes, UInt8* const dest); +}; + +#endif // _plPlanarImage_inc diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/webm/mkvparser.cpp b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/webm/mkvparser.cpp new file mode 100644 index 00000000..6ccaa8f8 --- /dev/null +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/webm/mkvparser.cpp @@ -0,0 +1,8354 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#include "mkvparser.hpp" +#include +#include +#include +#include + +#ifdef _MSC_VER +// Disable MSVC warnings that suggest making code non-portable. +#pragma warning(disable : 4996) +#endif + +mkvparser::IMkvReader::~IMkvReader() {} + +void mkvparser::GetVersion(int& major, int& minor, int& build, int& revision) { + major = 1; + minor = 0; + build = 0; + revision = 29; +} + +long long mkvparser::ReadUInt(IMkvReader* pReader, long long pos, long& len) { + assert(pReader); + assert(pos >= 0); + + int status; + + //#ifdef _DEBUG + // long long total, available; + // status = pReader->Length(&total, &available); + // assert(status >= 0); + // assert((total < 0) || (available <= total)); + // assert(pos < available); + // assert((available - pos) >= 1); //assume here max u-int len is 8 + //#endif + + len = 1; + + unsigned char b; + + status = pReader->Read(pos, 1, &b); + + if (status < 0) // error or underflow + return status; + + if (status > 0) // interpreted as "underflow" + return E_BUFFER_NOT_FULL; + + if (b == 0) // we can't handle u-int values larger than 8 bytes + return E_FILE_FORMAT_INVALID; + + unsigned char m = 0x80; + + while (!(b & m)) { + m >>= 1; + ++len; + } + + //#ifdef _DEBUG + // assert((available - pos) >= len); + //#endif + + long long result = b & (~m); + ++pos; + + for (int i = 1; i < len; ++i) { + status = pReader->Read(pos, 1, &b); + + if (status < 0) { + len = 1; + return status; + } + + if (status > 0) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result <<= 8; + result |= b; + + ++pos; + } + + return result; +} + +long long mkvparser::GetUIntLength(IMkvReader* pReader, long long pos, + long& len) { + assert(pReader); + assert(pos >= 0); + + long long total, available; + + int status = pReader->Length(&total, &available); + assert(status >= 0); + assert((total < 0) || (available <= total)); + + len = 1; + + if (pos >= available) + return pos; // too few bytes available + + unsigned char b; + + status = pReader->Read(pos, 1, &b); + + if (status < 0) + return status; + + assert(status == 0); + + if (b == 0) // we can't handle u-int values larger than 8 bytes + return E_FILE_FORMAT_INVALID; + + unsigned char m = 0x80; + + while (!(b & m)) { + m >>= 1; + ++len; + } + + return 0; // success +} + +// TODO(vigneshv): This function assumes that unsigned values never have their +// high bit set. +long long mkvparser::UnserializeUInt(IMkvReader* pReader, long long pos, + long long size) { + assert(pReader); + assert(pos >= 0); + + if ((size <= 0) || (size > 8)) + return E_FILE_FORMAT_INVALID; + + long long result = 0; + + for (long long i = 0; i < size; ++i) { + unsigned char b; + + const long status = pReader->Read(pos, 1, &b); + + if (status < 0) + return status; + + result <<= 8; + result |= b; + + ++pos; + } + + return result; +} + +long mkvparser::UnserializeFloat(IMkvReader* pReader, long long pos, + long long size_, double& result) { + assert(pReader); + assert(pos >= 0); + + if ((size_ != 4) && (size_ != 8)) + return E_FILE_FORMAT_INVALID; + + const long size = static_cast(size_); + + unsigned char buf[8]; + + const int status = pReader->Read(pos, size, buf); + + if (status < 0) // error + return status; + + if (size == 4) { + union { + float f; + unsigned long ff; + }; + + ff = 0; + + for (int i = 0;;) { + ff |= buf[i]; + + if (++i >= 4) + break; + + ff <<= 8; + } + + result = f; + } else { + assert(size == 8); + + union { + double d; + unsigned long long dd; + }; + + dd = 0; + + for (int i = 0;;) { + dd |= buf[i]; + + if (++i >= 8) + break; + + dd <<= 8; + } + + result = d; + } + + return 0; +} + +long mkvparser::UnserializeInt(IMkvReader* pReader, long long pos, long size, + long long& result) { + assert(pReader); + assert(pos >= 0); + assert(size > 0); + assert(size <= 8); + + { + signed char b; + + const long status = pReader->Read(pos, 1, (unsigned char*)&b); + + if (status < 0) + return status; + + result = b; + + ++pos; + } + + for (long i = 1; i < size; ++i) { + unsigned char b; + + const long status = pReader->Read(pos, 1, &b); + + if (status < 0) + return status; + + result <<= 8; + result |= b; + + ++pos; + } + + return 0; // success +} + +long mkvparser::UnserializeString(IMkvReader* pReader, long long pos, + long long size_, char*& str) { + delete[] str; + str = NULL; + + if (size_ >= LONG_MAX) // we need (size+1) chars + return E_FILE_FORMAT_INVALID; + + const long size = static_cast(size_); + + str = new (std::nothrow) char[size + 1]; + + if (str == NULL) + return -1; + + unsigned char* const buf = reinterpret_cast(str); + + const long status = pReader->Read(pos, size, buf); + + if (status) { + delete[] str; + str = NULL; + + return status; + } + + str[size] = '\0'; + + return 0; // success +} + +long mkvparser::ParseElementHeader(IMkvReader* pReader, long long& pos, + long long stop, long long& id, + long long& size) { + if ((stop >= 0) && (pos >= stop)) + return E_FILE_FORMAT_INVALID; + + long len; + + id = ReadUInt(pReader, pos, len); + + if (id < 0) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume id + + if ((stop >= 0) && (pos >= stop)) + return E_FILE_FORMAT_INVALID; + + size = ReadUInt(pReader, pos, len); + + if (size < 0) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume length of size + + // pos now designates payload + + if ((stop >= 0) && ((pos + size) > stop)) + return E_FILE_FORMAT_INVALID; + + return 0; // success +} + +bool mkvparser::Match(IMkvReader* pReader, long long& pos, unsigned long id_, + long long& val) { + assert(pReader); + assert(pos >= 0); + + long long total, available; + + const long status = pReader->Length(&total, &available); + assert(status >= 0); + assert((total < 0) || (available <= total)); + if (status < 0) + return false; + + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); + assert(len > 0); + assert(len <= 8); + assert((pos + len) <= available); + + if ((unsigned long)id != id_) + return false; + + pos += len; // consume id + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert(size <= 8); + assert(len > 0); + assert(len <= 8); + assert((pos + len) <= available); + + pos += len; // consume length of size of payload + + val = UnserializeUInt(pReader, pos, size); + assert(val >= 0); + + pos += size; // consume size of payload + + return true; +} + +bool mkvparser::Match(IMkvReader* pReader, long long& pos, unsigned long id_, + unsigned char*& buf, size_t& buflen) { + assert(pReader); + assert(pos >= 0); + + long long total, available; + + long status = pReader->Length(&total, &available); + assert(status >= 0); + assert((total < 0) || (available <= total)); + if (status < 0) + return false; + + long len; + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); + assert(len > 0); + assert(len <= 8); + assert((pos + len) <= available); + + if ((unsigned long)id != id_) + return false; + + pos += len; // consume id + + const long long size_ = ReadUInt(pReader, pos, len); + assert(size_ >= 0); + assert(len > 0); + assert(len <= 8); + assert((pos + len) <= available); + + pos += len; // consume length of size of payload + assert((pos + size_) <= available); + + const long buflen_ = static_cast(size_); + + buf = new (std::nothrow) unsigned char[buflen_]; + assert(buf); // TODO + + status = pReader->Read(pos, buflen_, buf); + assert(status == 0); // TODO + + buflen = buflen_; + + pos += size_; // consume size of payload + return true; +} + +namespace mkvparser { + +EBMLHeader::EBMLHeader() : m_docType(NULL) { Init(); } + +EBMLHeader::~EBMLHeader() { delete[] m_docType; } + +void EBMLHeader::Init() { + m_version = 1; + m_readVersion = 1; + m_maxIdLength = 4; + m_maxSizeLength = 8; + + if (m_docType) { + delete[] m_docType; + m_docType = NULL; + } + + m_docTypeVersion = 1; + m_docTypeReadVersion = 1; +} + +long long EBMLHeader::Parse(IMkvReader* pReader, long long& pos) { + assert(pReader); + + long long total, available; + + long status = pReader->Length(&total, &available); + + if (status < 0) // error + return status; + + pos = 0; + long long end = (available >= 1024) ? 1024 : available; + + for (;;) { + unsigned char b = 0; + + while (pos < end) { + status = pReader->Read(pos, 1, &b); + + if (status < 0) // error + return status; + + if (b == 0x1A) + break; + + ++pos; + } + + if (b != 0x1A) { + if (pos >= 1024) + return E_FILE_FORMAT_INVALID; // don't bother looking anymore + + if ((total >= 0) && ((total - available) < 5)) + return E_FILE_FORMAT_INVALID; + + return available + 5; // 5 = 4-byte ID + 1st byte of size + } + + if ((total >= 0) && ((total - pos) < 5)) + return E_FILE_FORMAT_INVALID; + + if ((available - pos) < 5) + return pos + 5; // try again later + + long len; + + const long long result = ReadUInt(pReader, pos, len); + + if (result < 0) // error + return result; + + if (result == 0x0A45DFA3) { // EBML Header ID + pos += len; // consume ID + break; + } + + ++pos; // throw away just the 0x1A byte, and try again + } + + // pos designates start of size field + + // get length of size field + + long len; + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return result; + + if (result > 0) // need more data + return result; + + assert(len > 0); + assert(len <= 8); + + if ((total >= 0) && ((total - pos) < len)) + return E_FILE_FORMAT_INVALID; + + if ((available - pos) < len) + return pos + len; // try again later + + // get the EBML header size + + result = ReadUInt(pReader, pos, len); + + if (result < 0) // error + return result; + + pos += len; // consume size field + + // pos now designates start of payload + + if ((total >= 0) && ((total - pos) < result)) + return E_FILE_FORMAT_INVALID; + + if ((available - pos) < result) + return pos + result; + + end = pos + result; + + Init(); + + while (pos < end) { + long long id, size; + + status = ParseElementHeader(pReader, pos, end, id, size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + return E_FILE_FORMAT_INVALID; + + if (id == 0x0286) { // version + m_version = UnserializeUInt(pReader, pos, size); + + if (m_version <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == 0x02F7) { // read version + m_readVersion = UnserializeUInt(pReader, pos, size); + + if (m_readVersion <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == 0x02F2) { // max id length + m_maxIdLength = UnserializeUInt(pReader, pos, size); + + if (m_maxIdLength <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == 0x02F3) { // max size length + m_maxSizeLength = UnserializeUInt(pReader, pos, size); + + if (m_maxSizeLength <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == 0x0282) { // doctype + if (m_docType) + return E_FILE_FORMAT_INVALID; + + status = UnserializeString(pReader, pos, size, m_docType); + + if (status) // error + return status; + } else if (id == 0x0287) { // doctype version + m_docTypeVersion = UnserializeUInt(pReader, pos, size); + + if (m_docTypeVersion <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == 0x0285) { // doctype read version + m_docTypeReadVersion = UnserializeUInt(pReader, pos, size); + + if (m_docTypeReadVersion <= 0) + return E_FILE_FORMAT_INVALID; + } + + pos += size; + } + + assert(pos == end); + return 0; +} + +Segment::Segment(IMkvReader* pReader, long long elem_start, + // long long elem_size, + long long start, long long size) + : m_pReader(pReader), + m_element_start(elem_start), + // m_element_size(elem_size), + m_start(start), + m_size(size), + m_pos(start), + m_pUnknownSize(0), + m_pSeekHead(NULL), + m_pInfo(NULL), + m_pTracks(NULL), + m_pCues(NULL), + m_pChapters(NULL), + m_clusters(NULL), + m_clusterCount(0), + m_clusterPreloadCount(0), + m_clusterSize(0) {} + +Segment::~Segment() { + const long count = m_clusterCount + m_clusterPreloadCount; + + Cluster** i = m_clusters; + Cluster** j = m_clusters + count; + + while (i != j) { + Cluster* const p = *i++; + assert(p); + + delete p; + } + + delete[] m_clusters; + + delete m_pTracks; + delete m_pInfo; + delete m_pCues; + delete m_pChapters; + delete m_pSeekHead; +} + +long long Segment::CreateInstance(IMkvReader* pReader, long long pos, + Segment*& pSegment) { + assert(pReader); + assert(pos >= 0); + + pSegment = NULL; + + long long total, available; + + const long status = pReader->Length(&total, &available); + + if (status < 0) // error + return status; + + if (available < 0) + return -1; + + if ((total >= 0) && (available > total)) + return -1; + + // I would assume that in practice this loop would execute + // exactly once, but we allow for other elements (e.g. Void) + // to immediately follow the EBML header. This is fine for + // the source filter case (since the entire file is available), + // but in the splitter case over a network we should probably + // just give up early. We could for example decide only to + // execute this loop a maximum of, say, 10 times. + // TODO: + // There is an implied "give up early" by only parsing up + // to the available limit. We do do that, but only if the + // total file size is unknown. We could decide to always + // use what's available as our limit (irrespective of whether + // we happen to know the total file length). This would have + // as its sense "parse this much of the file before giving up", + // which a slightly different sense from "try to parse up to + // 10 EMBL elements before giving up". + + for (;;) { + if ((total >= 0) && (pos >= total)) + return E_FILE_FORMAT_INVALID; + + // Read ID + long len; + long long result = GetUIntLength(pReader, pos, len); + + if (result) // error, or too few available bytes + return result; + + if ((total >= 0) && ((pos + len) > total)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + const long long idpos = pos; + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) // error + return id; + + pos += len; // consume ID + + // Read Size + + result = GetUIntLength(pReader, pos, len); + + if (result) // error, or too few available bytes + return result; + + if ((total >= 0) && ((pos + len) > total)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + long long size = ReadUInt(pReader, pos, len); + + if (size < 0) // error + return size; + + pos += len; // consume length of size of element + + // Pos now points to start of payload + + // Handle "unknown size" for live streaming of webm files. + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (id == 0x08538067) { // Segment ID + if (size == unknown_size) + size = -1; + + else if (total < 0) + size = -1; + + else if ((pos + size) > total) + size = -1; + + pSegment = new (std::nothrow) Segment(pReader, idpos, + // elem_size + pos, size); + + if (pSegment == 0) + return -1; // generic error + + return 0; // success + } + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && ((pos + size) > total)) + return E_FILE_FORMAT_INVALID; + + if ((pos + size) > available) + return pos + size; + + pos += size; // consume payload + } +} + +long long Segment::ParseHeaders() { + // Outermost (level 0) segment object has been constructed, + // and pos designates start of payload. We need to find the + // inner (level 1) elements. + long long total, available; + + const int status = m_pReader->Length(&total, &available); + + if (status < 0) // error + return status; + + assert((total < 0) || (available <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + assert((segment_stop < 0) || (total < 0) || (segment_stop <= total)); + assert((segment_stop < 0) || (m_pos <= segment_stop)); + + for (;;) { + if ((total >= 0) && (m_pos >= total)) + break; + + if ((segment_stop >= 0) && (m_pos >= segment_stop)) + break; + + long long pos = m_pos; + const long long element_start = pos; + + if ((pos + 1) > available) + return (pos + 1); + + long len; + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return result; + + if (result > 0) // underflow (weird) + return (pos + 1); + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + const long long idpos = pos; + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id < 0) // error + return id; + + if (id == 0x0F43B675) // Cluster ID + break; + + pos += len; // consume ID + + if ((pos + 1) > available) + return (pos + 1); + + // Read Size + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return result; + + if (result > 0) // underflow (weird) + return (pos + 1); + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) // error + return size; + + pos += len; // consume length of size of element + + const long long element_size = size + pos - element_start; + + // Pos now points to start of payload + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + // We read EBML elements either in total or nothing at all. + + if ((pos + size) > available) + return pos + size; + + if (id == 0x0549A966) { // Segment Info ID + if (m_pInfo) + return E_FILE_FORMAT_INVALID; + + m_pInfo = new (std::nothrow) + SegmentInfo(this, pos, size, element_start, element_size); + + if (m_pInfo == NULL) + return -1; + + const long status = m_pInfo->Parse(); + + if (status) + return status; + } else if (id == 0x0654AE6B) { // Tracks ID + if (m_pTracks) + return E_FILE_FORMAT_INVALID; + + m_pTracks = new (std::nothrow) + Tracks(this, pos, size, element_start, element_size); + + if (m_pTracks == NULL) + return -1; + + const long status = m_pTracks->Parse(); + + if (status) + return status; + } else if (id == 0x0C53BB6B) { // Cues ID + if (m_pCues == NULL) { + m_pCues = new (std::nothrow) + Cues(this, pos, size, element_start, element_size); + + if (m_pCues == NULL) + return -1; + } + } else if (id == 0x014D9B74) { // SeekHead ID + if (m_pSeekHead == NULL) { + m_pSeekHead = new (std::nothrow) + SeekHead(this, pos, size, element_start, element_size); + + if (m_pSeekHead == NULL) + return -1; + + const long status = m_pSeekHead->Parse(); + + if (status) + return status; + } + } else if (id == 0x0043A770) { // Chapters ID + if (m_pChapters == NULL) { + m_pChapters = new (std::nothrow) + Chapters(this, pos, size, element_start, element_size); + + if (m_pChapters == NULL) + return -1; + + const long status = m_pChapters->Parse(); + + if (status) + return status; + } + } + + m_pos = pos + size; // consume payload + } + + assert((segment_stop < 0) || (m_pos <= segment_stop)); + + if (m_pInfo == NULL) // TODO: liberalize this behavior + return E_FILE_FORMAT_INVALID; + + if (m_pTracks == NULL) + return E_FILE_FORMAT_INVALID; + + return 0; // success +} + +long Segment::LoadCluster(long long& pos, long& len) { + for (;;) { + const long result = DoLoadCluster(pos, len); + + if (result <= 1) + return result; + } +} + +long Segment::DoLoadCluster(long long& pos, long& len) { + if (m_pos < 0) + return DoLoadClusterUnknownSize(pos, len); + + long long total, avail; + + long status = m_pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + assert((total < 0) || (avail <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + long long cluster_off = -1; // offset relative to start of segment + long long cluster_size = -1; // size of cluster payload + + for (;;) { + if ((total >= 0) && (m_pos >= total)) + return 1; // no more clusters + + if ((segment_stop >= 0) && (m_pos >= segment_stop)) + return 1; // no more clusters + + pos = m_pos; + + // Read ID + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id < 0) // error (or underflow) + return static_cast(id); + + pos += len; // consume ID + + // Read Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) // error + return static_cast(size); + + pos += len; // consume length of size of element + + // pos now points to start of payload + + if (size == 0) { // weird + m_pos = pos; + continue; + } + + const long long unknown_size = (1LL << (7 * len)) - 1; + +#if 0 // we must handle this to support live webm + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //TODO: allow this +#endif + + if ((segment_stop >= 0) && (size != unknown_size) && + ((pos + size) > segment_stop)) { + return E_FILE_FORMAT_INVALID; + } + +#if 0 // commented-out, to support incremental cluster parsing + len = static_cast(size); + + if ((pos + size) > avail) + return E_BUFFER_NOT_FULL; +#endif + + if (id == 0x0C53BB6B) { // Cues ID + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; // TODO: liberalize + + if (m_pCues == NULL) { + const long long element_size = (pos - idpos) + size; + + m_pCues = new Cues(this, pos, size, idpos, element_size); + assert(m_pCues); // TODO + } + + m_pos = pos + size; // consume payload + continue; + } + + if (id != 0x0F43B675) { // Cluster ID + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; // TODO: liberalize + + m_pos = pos + size; // consume payload + continue; + } + + // We have a cluster. + + cluster_off = idpos - m_start; // relative pos + + if (size != unknown_size) + cluster_size = size; + + break; + } + + assert(cluster_off >= 0); // have cluster + + long long pos_; + long len_; + + status = Cluster::HasBlockEntries(this, cluster_off, pos_, len_); + + if (status < 0) { // error, or underflow + pos = pos_; + len = len_; + + return status; + } + + // status == 0 means "no block entries found" + // status > 0 means "found at least one block entry" + + // TODO: + // The issue here is that the segment increments its own + // pos ptr past the most recent cluster parsed, and then + // starts from there to parse the next cluster. If we + // don't know the size of the current cluster, then we + // must either parse its payload (as we do below), looking + // for the cluster (or cues) ID to terminate the parse. + // This isn't really what we want: rather, we really need + // a way to create the curr cluster object immediately. + // The pity is that cluster::parse can determine its own + // boundary, and we largely duplicate that same logic here. + // + // Maybe we need to get rid of our look-ahead preloading + // in source::parse??? + // + // As we're parsing the blocks in the curr cluster + //(in cluster::parse), we should have some way to signal + // to the segment that we have determined the boundary, + // so it can adjust its own segment::m_pos member. + // + // The problem is that we're asserting in asyncreadinit, + // because we adjust the pos down to the curr seek pos, + // and the resulting adjusted len is > 2GB. I'm suspicious + // that this is even correct, but even if it is, we can't + // be loading that much data in the cache anyway. + + const long idx = m_clusterCount; + + if (m_clusterPreloadCount > 0) { + assert(idx < m_clusterSize); + + Cluster* const pCluster = m_clusters[idx]; + assert(pCluster); + assert(pCluster->m_index < 0); + + const long long off = pCluster->GetPosition(); + assert(off >= 0); + + if (off == cluster_off) { // preloaded already + if (status == 0) // no entries found + return E_FILE_FORMAT_INVALID; + + if (cluster_size >= 0) + pos += cluster_size; + else { + const long long element_size = pCluster->GetElementSize(); + + if (element_size <= 0) + return E_FILE_FORMAT_INVALID; // TODO: handle this case + + pos = pCluster->m_element_start + element_size; + } + + pCluster->m_index = idx; // move from preloaded to loaded + ++m_clusterCount; + --m_clusterPreloadCount; + + m_pos = pos; // consume payload + assert((segment_stop < 0) || (m_pos <= segment_stop)); + + return 0; // success + } + } + + if (status == 0) { // no entries found + if (cluster_size >= 0) + pos += cluster_size; + + if ((total >= 0) && (pos >= total)) { + m_pos = total; + return 1; // no more clusters + } + + if ((segment_stop >= 0) && (pos >= segment_stop)) { + m_pos = segment_stop; + return 1; // no more clusters + } + + m_pos = pos; + return 2; // try again + } + + // status > 0 means we have an entry + + Cluster* const pCluster = Cluster::Create(this, idx, cluster_off); + // element_size); + assert(pCluster); + + AppendCluster(pCluster); + assert(m_clusters); + assert(idx < m_clusterSize); + assert(m_clusters[idx] == pCluster); + + if (cluster_size >= 0) { + pos += cluster_size; + + m_pos = pos; + assert((segment_stop < 0) || (m_pos <= segment_stop)); + + return 0; + } + + m_pUnknownSize = pCluster; + m_pos = -pos; + + return 0; // partial success, since we have a new cluster + +// status == 0 means "no block entries found" + +// pos designates start of payload +// m_pos has NOT been adjusted yet (in case we need to come back here) + +#if 0 + + if (cluster_size < 0) { //unknown size + const long long payload_pos = pos; //absolute pos of cluster payload + + for (;;) { //determine cluster size + if ((total >= 0) && (pos >= total)) + break; + + if ((segment_stop >= 0) && (pos >= segment_stop)) + break; //no more clusters + + //Read ID + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id < 0) //error (or underflow) + return static_cast(id); + + //This is the distinguished set of ID's we use to determine + //that we have exhausted the sub-element's inside the cluster + //whose ID we parsed earlier. + + if (id == 0x0F43B675) //Cluster ID + break; + + if (id == 0x0C53BB6B) //Cues ID + break; + + switch (id) + { + case 0x20: //BlockGroup + case 0x23: //Simple Block + case 0x67: //TimeCode + case 0x2B: //PrevSize + break; + + default: + assert(false); + break; + } + + pos += len; //consume ID (of sub-element) + + //Read Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + pos += len; //consume size field of element + + //pos now points to start of sub-element's payload + + if (size == 0) //weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //not allowed for sub-elements + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) //weird + return E_FILE_FORMAT_INVALID; + + pos += size; //consume payload of sub-element + assert((segment_stop < 0) || (pos <= segment_stop)); + } //determine cluster size + + cluster_size = pos - payload_pos; + assert(cluster_size >= 0); + + pos = payload_pos; //reset and re-parse original cluster + } + + if (m_clusterPreloadCount > 0) + { + assert(idx < m_clusterSize); + + Cluster* const pCluster = m_clusters[idx]; + assert(pCluster); + assert(pCluster->m_index < 0); + + const long long off = pCluster->GetPosition(); + assert(off >= 0); + + if (off == cluster_off) //preloaded already + return E_FILE_FORMAT_INVALID; //subtle + } + + m_pos = pos + cluster_size; //consume payload + assert((segment_stop < 0) || (m_pos <= segment_stop)); + + return 2; //try to find another cluster + +#endif +} + +long Segment::DoLoadClusterUnknownSize(long long& pos, long& len) { + assert(m_pos < 0); + assert(m_pUnknownSize); + +#if 0 + assert(m_pUnknownSize->GetElementSize() < 0); //TODO: verify this + + const long long element_start = m_pUnknownSize->m_element_start; + + pos = -m_pos; + assert(pos > element_start); + + //We have already consumed the (cluster) ID and size fields. + //We just need to consume the blocks and other sub-elements + //of this cluster, until we discover the boundary. + + long long total, avail; + + long status = m_pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + long long element_size = -1; + + for (;;) { //determine cluster size + if ((total >= 0) && (pos >= total)) + { + element_size = total - element_start; + assert(element_size > 0); + + break; + } + + if ((segment_stop >= 0) && (pos >= segment_stop)) + { + element_size = segment_stop - element_start; + assert(element_size > 0); + + break; + } + + //Read ID + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id < 0) //error (or underflow) + return static_cast(id); + + //This is the distinguished set of ID's we use to determine + //that we have exhausted the sub-element's inside the cluster + //whose ID we parsed earlier. + + if ((id == 0x0F43B675) || (id == 0x0C53BB6B)) { //Cluster ID or Cues ID + element_size = pos - element_start; + assert(element_size > 0); + + break; + } + +#ifdef _DEBUG + switch (id) + { + case 0x20: //BlockGroup + case 0x23: //Simple Block + case 0x67: //TimeCode + case 0x2B: //PrevSize + break; + + default: + assert(false); + break; + } +#endif + + pos += len; //consume ID (of sub-element) + + //Read Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + pos += len; //consume size field of element + + //pos now points to start of sub-element's payload + + if (size == 0) //weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //not allowed for sub-elements + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) //weird + return E_FILE_FORMAT_INVALID; + + pos += size; //consume payload of sub-element + assert((segment_stop < 0) || (pos <= segment_stop)); + } //determine cluster size + + assert(element_size >= 0); + + m_pos = element_start + element_size; + m_pUnknownSize = 0; + + return 2; //continue parsing +#else + const long status = m_pUnknownSize->Parse(pos, len); + + if (status < 0) // error or underflow + return status; + + if (status == 0) // parsed a block + return 2; // continue parsing + + assert(status > 0); // nothing left to parse of this cluster + + const long long start = m_pUnknownSize->m_element_start; + + const long long size = m_pUnknownSize->GetElementSize(); + assert(size >= 0); + + pos = start + size; + m_pos = pos; + + m_pUnknownSize = 0; + + return 2; // continue parsing +#endif +} + +void Segment::AppendCluster(Cluster* pCluster) { + assert(pCluster); + assert(pCluster->m_index >= 0); + + const long count = m_clusterCount + m_clusterPreloadCount; + + long& size = m_clusterSize; + assert(size >= count); + + const long idx = pCluster->m_index; + assert(idx == m_clusterCount); + + if (count >= size) { + const long n = (size <= 0) ? 2048 : 2 * size; + + Cluster** const qq = new Cluster* [n]; + Cluster** q = qq; + + Cluster** p = m_clusters; + Cluster** const pp = p + count; + + while (p != pp) + *q++ = *p++; + + delete[] m_clusters; + + m_clusters = qq; + size = n; + } + + if (m_clusterPreloadCount > 0) { + assert(m_clusters); + + Cluster** const p = m_clusters + m_clusterCount; + assert(*p); + assert((*p)->m_index < 0); + + Cluster** q = p + m_clusterPreloadCount; + assert(q < (m_clusters + size)); + + for (;;) { + Cluster** const qq = q - 1; + assert((*qq)->m_index < 0); + + *q = *qq; + q = qq; + + if (q == p) + break; + } + } + + m_clusters[idx] = pCluster; + ++m_clusterCount; +} + +void Segment::PreloadCluster(Cluster* pCluster, ptrdiff_t idx) { + assert(pCluster); + assert(pCluster->m_index < 0); + assert(idx >= m_clusterCount); + + const long count = m_clusterCount + m_clusterPreloadCount; + + long& size = m_clusterSize; + assert(size >= count); + + if (count >= size) { + const long n = (size <= 0) ? 2048 : 2 * size; + + Cluster** const qq = new Cluster* [n]; + Cluster** q = qq; + + Cluster** p = m_clusters; + Cluster** const pp = p + count; + + while (p != pp) + *q++ = *p++; + + delete[] m_clusters; + + m_clusters = qq; + size = n; + } + + assert(m_clusters); + + Cluster** const p = m_clusters + idx; + + Cluster** q = m_clusters + count; + assert(q >= p); + assert(q < (m_clusters + size)); + + while (q > p) { + Cluster** const qq = q - 1; + assert((*qq)->m_index < 0); + + *q = *qq; + q = qq; + } + + m_clusters[idx] = pCluster; + ++m_clusterPreloadCount; +} + +long Segment::Load() { + assert(m_clusters == NULL); + assert(m_clusterSize == 0); + assert(m_clusterCount == 0); + // assert(m_size >= 0); + + // Outermost (level 0) segment object has been constructed, + // and pos designates start of payload. We need to find the + // inner (level 1) elements. + + const long long header_status = ParseHeaders(); + + if (header_status < 0) // error + return static_cast(header_status); + + if (header_status > 0) // underflow + return E_BUFFER_NOT_FULL; + + assert(m_pInfo); + assert(m_pTracks); + + for (;;) { + const int status = LoadCluster(); + + if (status < 0) // error + return status; + + if (status >= 1) // no more clusters + return 0; + } +} + +SeekHead::SeekHead(Segment* pSegment, long long start, long long size_, + long long element_start, long long element_size) + : m_pSegment(pSegment), + m_start(start), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_entries(0), + m_entry_count(0), + m_void_elements(0), + m_void_element_count(0) {} + +SeekHead::~SeekHead() { + delete[] m_entries; + delete[] m_void_elements; +} + +long SeekHead::Parse() { + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = m_start; + const long long stop = m_start + m_size; + + // first count the seek head entries + + int entry_count = 0; + int void_element_count = 0; + + while (pos < stop) { + long long id, size; + + const long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (id == 0x0DBB) // SeekEntry ID + ++entry_count; + else if (id == 0x6C) // Void ID + ++void_element_count; + + pos += size; // consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + m_entries = new (std::nothrow) Entry[entry_count]; + + if (m_entries == NULL) + return -1; + + m_void_elements = new (std::nothrow) VoidElement[void_element_count]; + + if (m_void_elements == NULL) + return -1; + + // now parse the entries and void elements + + Entry* pEntry = m_entries; + VoidElement* pVoidElement = m_void_elements; + + pos = m_start; + + while (pos < stop) { + const long long idpos = pos; + + long long id, size; + + const long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (id == 0x0DBB) { // SeekEntry ID + if (ParseEntry(pReader, pos, size, pEntry)) { + Entry& e = *pEntry++; + + e.element_start = idpos; + e.element_size = (pos + size) - idpos; + } + } else if (id == 0x6C) { // Void ID + VoidElement& e = *pVoidElement++; + + e.element_start = idpos; + e.element_size = (pos + size) - idpos; + } + + pos += size; // consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + ptrdiff_t count_ = ptrdiff_t(pEntry - m_entries); + assert(count_ >= 0); + assert(count_ <= entry_count); + + m_entry_count = static_cast(count_); + + count_ = ptrdiff_t(pVoidElement - m_void_elements); + assert(count_ >= 0); + assert(count_ <= void_element_count); + + m_void_element_count = static_cast(count_); + + return 0; +} + +int SeekHead::GetCount() const { return m_entry_count; } + +const SeekHead::Entry* SeekHead::GetEntry(int idx) const { + if (idx < 0) + return 0; + + if (idx >= m_entry_count) + return 0; + + return m_entries + idx; +} + +int SeekHead::GetVoidElementCount() const { return m_void_element_count; } + +const SeekHead::VoidElement* SeekHead::GetVoidElement(int idx) const { + if (idx < 0) + return 0; + + if (idx >= m_void_element_count) + return 0; + + return m_void_elements + idx; +} + +#if 0 +void Segment::ParseCues(long long off) +{ + if (m_pCues) + return; + + //odbgstream os; + //os << "Segment::ParseCues (begin)" << endl; + + long long pos = m_start + off; + const long long element_start = pos; + const long long stop = m_start + m_size; + + long len; + + long long result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); + assert((pos + len) <= stop); + + const long long idpos = pos; + + const long long id = ReadUInt(m_pReader, idpos, len); + assert(id == 0x0C53BB6B); //Cues ID + + pos += len; //consume ID + assert(pos < stop); + + //Read Size + + result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); + assert((pos + len) <= stop); + + const long long size = ReadUInt(m_pReader, pos, len); + assert(size >= 0); + + pos += len; //consume length of size of element + assert((pos + size) <= stop); + + const long long element_size = size + pos - element_start; + + //Pos now points to start of payload + + m_pCues = new Cues(this, pos, size, element_start, element_size); + assert(m_pCues); //TODO + + //os << "Segment::ParseCues (end)" << endl; +} +#else +long Segment::ParseCues(long long off, long long& pos, long& len) { + if (m_pCues) + return 0; // success + + if (off < 0) + return -1; + + long long total, avail; + + const int status = m_pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + assert((total < 0) || (avail <= total)); + + pos = m_start + off; + + if ((total < 0) || (pos >= total)) + return 1; // don't bother parsing cues + + const long long element_start = pos; + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // underflow (weird) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id != 0x0C53BB6B) // Cues ID + return E_FILE_FORMAT_INVALID; + + pos += len; // consume ID + assert((segment_stop < 0) || (pos <= segment_stop)); + + // Read Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // underflow (weird) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) // error + return static_cast(size); + + if (size == 0) // weird, although technically not illegal + return 1; // done + + pos += len; // consume length of size of element + assert((segment_stop < 0) || (pos <= segment_stop)); + + // Pos now points to start of payload + + const long long element_stop = pos + size; + + if ((segment_stop >= 0) && (element_stop > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && (element_stop > total)) + return 1; // don't bother parsing anymore + + len = static_cast(size); + + if (element_stop > avail) + return E_BUFFER_NOT_FULL; + + const long long element_size = element_stop - element_start; + + m_pCues = + new (std::nothrow) Cues(this, pos, size, element_start, element_size); + assert(m_pCues); // TODO + + return 0; // success +} +#endif + +#if 0 +void Segment::ParseSeekEntry( + long long start, + long long size_) +{ + long long pos = start; + + const long long stop = start + size_; + + long len; + + const long long seekIdId = ReadUInt(m_pReader, pos, len); + //seekIdId; + assert(seekIdId == 0x13AB); //SeekID ID + assert((pos + len) <= stop); + + pos += len; //consume id + + const long long seekIdSize = ReadUInt(m_pReader, pos, len); + assert(seekIdSize >= 0); + assert((pos + len) <= stop); + + pos += len; //consume size + + const long long seekId = ReadUInt(m_pReader, pos, len); //payload + assert(seekId >= 0); + assert(len == seekIdSize); + assert((pos + len) <= stop); + + pos += seekIdSize; //consume payload + + const long long seekPosId = ReadUInt(m_pReader, pos, len); + //seekPosId; + assert(seekPosId == 0x13AC); //SeekPos ID + assert((pos + len) <= stop); + + pos += len; //consume id + + const long long seekPosSize = ReadUInt(m_pReader, pos, len); + assert(seekPosSize >= 0); + assert((pos + len) <= stop); + + pos += len; //consume size + assert((pos + seekPosSize) <= stop); + + const long long seekOff = UnserializeUInt(m_pReader, pos, seekPosSize); + assert(seekOff >= 0); + assert(seekOff < m_size); + + pos += seekPosSize; //consume payload + assert(pos == stop); + + const long long seekPos = m_start + seekOff; + assert(seekPos < (m_start + m_size)); + + if (seekId == 0x0C53BB6B) //Cues ID + ParseCues(seekOff); +} +#else +bool SeekHead::ParseEntry(IMkvReader* pReader, long long start, long long size_, + Entry* pEntry) { + if (size_ <= 0) + return false; + + long long pos = start; + const long long stop = start + size_; + + long len; + + // parse the container for the level-1 element ID + + const long long seekIdId = ReadUInt(pReader, pos, len); + // seekIdId; + + if (seekIdId != 0x13AB) // SeekID ID + return false; + + if ((pos + len) > stop) + return false; + + pos += len; // consume SeekID id + + const long long seekIdSize = ReadUInt(pReader, pos, len); + + if (seekIdSize <= 0) + return false; + + if ((pos + len) > stop) + return false; + + pos += len; // consume size of field + + if ((pos + seekIdSize) > stop) + return false; + + // Note that the SeekId payload really is serialized + // as a "Matroska integer", not as a plain binary value. + // In fact, Matroska requires that ID values in the + // stream exactly match the binary representation as listed + // in the Matroska specification. + // + // This parser is more liberal, and permits IDs to have + // any width. (This could make the representation in the stream + // different from what's in the spec, but it doesn't matter here, + // since we always normalize "Matroska integer" values.) + + pEntry->id = ReadUInt(pReader, pos, len); // payload + + if (pEntry->id <= 0) + return false; + + if (len != seekIdSize) + return false; + + pos += seekIdSize; // consume SeekID payload + + const long long seekPosId = ReadUInt(pReader, pos, len); + + if (seekPosId != 0x13AC) // SeekPos ID + return false; + + if ((pos + len) > stop) + return false; + + pos += len; // consume id + + const long long seekPosSize = ReadUInt(pReader, pos, len); + + if (seekPosSize <= 0) + return false; + + if ((pos + len) > stop) + return false; + + pos += len; // consume size + + if ((pos + seekPosSize) > stop) + return false; + + pEntry->pos = UnserializeUInt(pReader, pos, seekPosSize); + + if (pEntry->pos < 0) + return false; + + pos += seekPosSize; // consume payload + + if (pos != stop) + return false; + + return true; +} +#endif + +Cues::Cues(Segment* pSegment, long long start_, long long size_, + long long element_start, long long element_size) + : m_pSegment(pSegment), + m_start(start_), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_cue_points(NULL), + m_count(0), + m_preload_count(0), + m_pos(start_) {} + +Cues::~Cues() { + const long n = m_count + m_preload_count; + + CuePoint** p = m_cue_points; + CuePoint** const q = p + n; + + while (p != q) { + CuePoint* const pCP = *p++; + assert(pCP); + + delete pCP; + } + + delete[] m_cue_points; +} + +long Cues::GetCount() const { + if (m_cue_points == NULL) + return -1; + + return m_count; // TODO: really ignore preload count? +} + +bool Cues::DoneParsing() const { + const long long stop = m_start + m_size; + return (m_pos >= stop); +} + +void Cues::Init() const { + if (m_cue_points) + return; + + assert(m_count == 0); + assert(m_preload_count == 0); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + const long long stop = m_start + m_size; + long long pos = m_start; + + long cue_points_size = 0; + + while (pos < stop) { + const long long idpos = pos; + + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); // TODO + assert((pos + len) <= stop); + + pos += len; // consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert((pos + len) <= stop); + + pos += len; // consume Size field + assert((pos + size) <= stop); + + if (id == 0x3B) // CuePoint ID + PreloadCuePoint(cue_points_size, idpos); + + pos += size; // consume payload + assert(pos <= stop); + } +} + +void Cues::PreloadCuePoint(long& cue_points_size, long long pos) const { + assert(m_count == 0); + + if (m_preload_count >= cue_points_size) { + const long n = (cue_points_size <= 0) ? 2048 : 2 * cue_points_size; + + CuePoint** const qq = new CuePoint* [n]; + CuePoint** q = qq; // beginning of target + + CuePoint** p = m_cue_points; // beginning of source + CuePoint** const pp = p + m_preload_count; // end of source + + while (p != pp) + *q++ = *p++; + + delete[] m_cue_points; + + m_cue_points = qq; + cue_points_size = n; + } + + CuePoint* const pCP = new CuePoint(m_preload_count, pos); + m_cue_points[m_preload_count++] = pCP; +} + +bool Cues::LoadCuePoint() const { + // odbgstream os; + // os << "Cues::LoadCuePoint" << endl; + + const long long stop = m_start + m_size; + + if (m_pos >= stop) + return false; // nothing else to do + + Init(); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + while (m_pos < stop) { + const long long idpos = m_pos; + + long len; + + const long long id = ReadUInt(pReader, m_pos, len); + assert(id >= 0); // TODO + assert((m_pos + len) <= stop); + + m_pos += len; // consume ID + + const long long size = ReadUInt(pReader, m_pos, len); + assert(size >= 0); + assert((m_pos + len) <= stop); + + m_pos += len; // consume Size field + assert((m_pos + size) <= stop); + + if (id != 0x3B) { // CuePoint ID + m_pos += size; // consume payload + assert(m_pos <= stop); + + continue; + } + + assert(m_preload_count > 0); + + CuePoint* const pCP = m_cue_points[m_count]; + assert(pCP); + assert((pCP->GetTimeCode() >= 0) || (-pCP->GetTimeCode() == idpos)); + if (pCP->GetTimeCode() < 0 && (-pCP->GetTimeCode() != idpos)) + return false; + + pCP->Load(pReader); + ++m_count; + --m_preload_count; + + m_pos += size; // consume payload + assert(m_pos <= stop); + + return true; // yes, we loaded a cue point + } + + // return (m_pos < stop); + return false; // no, we did not load a cue point +} + +bool Cues::Find(long long time_ns, const Track* pTrack, const CuePoint*& pCP, + const CuePoint::TrackPosition*& pTP) const { + assert(time_ns >= 0); + assert(pTrack); + +#if 0 + LoadCuePoint(); //establish invariant + + assert(m_cue_points); + assert(m_count > 0); + + CuePoint** const ii = m_cue_points; + CuePoint** i = ii; + + CuePoint** const jj = ii + m_count + m_preload_count; + CuePoint** j = jj; + + pCP = *i; + assert(pCP); + + if (time_ns <= pCP->GetTime(m_pSegment)) + { + pTP = pCP->Find(pTrack); + return (pTP != NULL); + } + + IMkvReader* const pReader = m_pSegment->m_pReader; + + while (i < j) + { + //INVARIANT: + //[ii, i) <= time_ns + //[i, j) ? + //[j, jj) > time_ns + + CuePoint** const k = i + (j - i) / 2; + assert(k < jj); + + CuePoint* const pCP = *k; + assert(pCP); + + pCP->Load(pReader); + + const long long t = pCP->GetTime(m_pSegment); + + if (t <= time_ns) + i = k + 1; + else + j = k; + + assert(i <= j); + } + + assert(i == j); + assert(i <= jj); + assert(i > ii); + + pCP = *--i; + assert(pCP); + assert(pCP->GetTime(m_pSegment) <= time_ns); +#else + if (m_cue_points == NULL) + return false; + + if (m_count == 0) + return false; + + CuePoint** const ii = m_cue_points; + CuePoint** i = ii; + + CuePoint** const jj = ii + m_count; + CuePoint** j = jj; + + pCP = *i; + assert(pCP); + + if (time_ns <= pCP->GetTime(m_pSegment)) { + pTP = pCP->Find(pTrack); + return (pTP != NULL); + } + + while (i < j) { + // INVARIANT: + //[ii, i) <= time_ns + //[i, j) ? + //[j, jj) > time_ns + + CuePoint** const k = i + (j - i) / 2; + assert(k < jj); + + CuePoint* const pCP = *k; + assert(pCP); + + const long long t = pCP->GetTime(m_pSegment); + + if (t <= time_ns) + i = k + 1; + else + j = k; + + assert(i <= j); + } + + assert(i == j); + assert(i <= jj); + assert(i > ii); + + pCP = *--i; + assert(pCP); + assert(pCP->GetTime(m_pSegment) <= time_ns); +#endif + + // TODO: here and elsewhere, it's probably not correct to search + // for the cue point with this time, and then search for a matching + // track. In principle, the matching track could be on some earlier + // cue point, and with our current algorithm, we'd miss it. To make + // this bullet-proof, we'd need to create a secondary structure, + // with a list of cue points that apply to a track, and then search + // that track-based structure for a matching cue point. + + pTP = pCP->Find(pTrack); + return (pTP != NULL); +} + +#if 0 +bool Cues::FindNext( + long long time_ns, + const Track* pTrack, + const CuePoint*& pCP, + const CuePoint::TrackPosition*& pTP) const +{ + pCP = 0; + pTP = 0; + + if (m_count == 0) + return false; + + assert(m_cue_points); + + const CuePoint* const* const ii = m_cue_points; + const CuePoint* const* i = ii; + + const CuePoint* const* const jj = ii + m_count; + const CuePoint* const* j = jj; + + while (i < j) + { + //INVARIANT: + //[ii, i) <= time_ns + //[i, j) ? + //[j, jj) > time_ns + + const CuePoint* const* const k = i + (j - i) / 2; + assert(k < jj); + + pCP = *k; + assert(pCP); + + const long long t = pCP->GetTime(m_pSegment); + + if (t <= time_ns) + i = k + 1; + else + j = k; + + assert(i <= j); + } + + assert(i == j); + assert(i <= jj); + + if (i >= jj) //time_ns is greater than max cue point + return false; + + pCP = *i; + assert(pCP); + assert(pCP->GetTime(m_pSegment) > time_ns); + + pTP = pCP->Find(pTrack); + return (pTP != NULL); +} +#endif + +const CuePoint* Cues::GetFirst() const { + if (m_cue_points == NULL) + return NULL; + + if (m_count == 0) + return NULL; + +#if 0 + LoadCuePoint(); //init cues + + const size_t count = m_count + m_preload_count; + + if (count == 0) //weird + return NULL; +#endif + + CuePoint* const* const pp = m_cue_points; + assert(pp); + + CuePoint* const pCP = pp[0]; + assert(pCP); + assert(pCP->GetTimeCode() >= 0); + + return pCP; +} + +const CuePoint* Cues::GetLast() const { + if (m_cue_points == NULL) + return NULL; + + if (m_count <= 0) + return NULL; + +#if 0 + LoadCuePoint(); //init cues + + const size_t count = m_count + m_preload_count; + + if (count == 0) //weird + return NULL; + + const size_t index = count - 1; + + CuePoint* const* const pp = m_cue_points; + assert(pp); + + CuePoint* const pCP = pp[index]; + assert(pCP); + + pCP->Load(m_pSegment->m_pReader); + assert(pCP->GetTimeCode() >= 0); +#else + const long index = m_count - 1; + + CuePoint* const* const pp = m_cue_points; + assert(pp); + + CuePoint* const pCP = pp[index]; + assert(pCP); + assert(pCP->GetTimeCode() >= 0); +#endif + + return pCP; +} + +const CuePoint* Cues::GetNext(const CuePoint* pCurr) const { + if (pCurr == NULL) + return NULL; + + assert(pCurr->GetTimeCode() >= 0); + assert(m_cue_points); + assert(m_count >= 1); + +#if 0 + const size_t count = m_count + m_preload_count; + + size_t index = pCurr->m_index; + assert(index < count); + + CuePoint* const* const pp = m_cue_points; + assert(pp); + assert(pp[index] == pCurr); + + ++index; + + if (index >= count) + return NULL; + + CuePoint* const pNext = pp[index]; + assert(pNext); + + pNext->Load(m_pSegment->m_pReader); +#else + long index = pCurr->m_index; + assert(index < m_count); + + CuePoint* const* const pp = m_cue_points; + assert(pp); + assert(pp[index] == pCurr); + + ++index; + + if (index >= m_count) + return NULL; + + CuePoint* const pNext = pp[index]; + assert(pNext); + assert(pNext->GetTimeCode() >= 0); +#endif + + return pNext; +} + +const BlockEntry* Cues::GetBlock(const CuePoint* pCP, + const CuePoint::TrackPosition* pTP) const { + if (pCP == NULL) + return NULL; + + if (pTP == NULL) + return NULL; + + return m_pSegment->GetBlock(*pCP, *pTP); +} + +const BlockEntry* Segment::GetBlock(const CuePoint& cp, + const CuePoint::TrackPosition& tp) { + Cluster** const ii = m_clusters; + Cluster** i = ii; + + const long count = m_clusterCount + m_clusterPreloadCount; + + Cluster** const jj = ii + count; + Cluster** j = jj; + + while (i < j) { + // INVARIANT: + //[ii, i) < pTP->m_pos + //[i, j) ? + //[j, jj) > pTP->m_pos + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + Cluster* const pCluster = *k; + assert(pCluster); + + // const long long pos_ = pCluster->m_pos; + // assert(pos_); + // const long long pos = pos_ * ((pos_ < 0) ? -1 : 1); + + const long long pos = pCluster->GetPosition(); + assert(pos >= 0); + + if (pos < tp.m_pos) + i = k + 1; + else if (pos > tp.m_pos) + j = k; + else + return pCluster->GetEntry(cp, tp); + } + + assert(i == j); + // assert(Cluster::HasBlockEntries(this, tp.m_pos)); + + Cluster* const pCluster = Cluster::Create(this, -1, tp.m_pos); //, -1); + assert(pCluster); + + const ptrdiff_t idx = i - m_clusters; + + PreloadCluster(pCluster, idx); + assert(m_clusters); + assert(m_clusterPreloadCount > 0); + assert(m_clusters[idx] == pCluster); + + return pCluster->GetEntry(cp, tp); +} + +const Cluster* Segment::FindOrPreloadCluster(long long requested_pos) { + if (requested_pos < 0) + return 0; + + Cluster** const ii = m_clusters; + Cluster** i = ii; + + const long count = m_clusterCount + m_clusterPreloadCount; + + Cluster** const jj = ii + count; + Cluster** j = jj; + + while (i < j) { + // INVARIANT: + //[ii, i) < pTP->m_pos + //[i, j) ? + //[j, jj) > pTP->m_pos + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + Cluster* const pCluster = *k; + assert(pCluster); + + // const long long pos_ = pCluster->m_pos; + // assert(pos_); + // const long long pos = pos_ * ((pos_ < 0) ? -1 : 1); + + const long long pos = pCluster->GetPosition(); + assert(pos >= 0); + + if (pos < requested_pos) + i = k + 1; + else if (pos > requested_pos) + j = k; + else + return pCluster; + } + + assert(i == j); + // assert(Cluster::HasBlockEntries(this, tp.m_pos)); + + Cluster* const pCluster = Cluster::Create(this, -1, requested_pos); + //-1); + assert(pCluster); + + const ptrdiff_t idx = i - m_clusters; + + PreloadCluster(pCluster, idx); + assert(m_clusters); + assert(m_clusterPreloadCount > 0); + assert(m_clusters[idx] == pCluster); + + return pCluster; +} + +CuePoint::CuePoint(long idx, long long pos) + : m_element_start(0), + m_element_size(0), + m_index(idx), + m_timecode(-1 * pos), + m_track_positions(NULL), + m_track_positions_count(0) { + assert(pos > 0); +} + +CuePoint::~CuePoint() { delete[] m_track_positions; } + +void CuePoint::Load(IMkvReader* pReader) { + // odbgstream os; + // os << "CuePoint::Load(begin): timecode=" << m_timecode << endl; + + if (m_timecode >= 0) // already loaded + return; + + assert(m_track_positions == NULL); + assert(m_track_positions_count == 0); + + long long pos_ = -m_timecode; + const long long element_start = pos_; + + long long stop; + + { + long len; + + const long long id = ReadUInt(pReader, pos_, len); + assert(id == 0x3B); // CuePoint ID + if (id != 0x3B) + return; + + pos_ += len; // consume ID + + const long long size = ReadUInt(pReader, pos_, len); + assert(size >= 0); + + pos_ += len; // consume Size field + // pos_ now points to start of payload + + stop = pos_ + size; + } + + const long long element_size = stop - element_start; + + long long pos = pos_; + + // First count number of track positions + + while (pos < stop) { + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); // TODO + assert((pos + len) <= stop); + + pos += len; // consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert((pos + len) <= stop); + + pos += len; // consume Size field + assert((pos + size) <= stop); + + if (id == 0x33) // CueTime ID + m_timecode = UnserializeUInt(pReader, pos, size); + + else if (id == 0x37) // CueTrackPosition(s) ID + ++m_track_positions_count; + + pos += size; // consume payload + assert(pos <= stop); + } + + assert(m_timecode >= 0); + assert(m_track_positions_count > 0); + + // os << "CuePoint::Load(cont'd): idpos=" << idpos + // << " timecode=" << m_timecode + // << endl; + + m_track_positions = new TrackPosition[m_track_positions_count]; + + // Now parse track positions + + TrackPosition* p = m_track_positions; + pos = pos_; + + while (pos < stop) { + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); // TODO + assert((pos + len) <= stop); + + pos += len; // consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert((pos + len) <= stop); + + pos += len; // consume Size field + assert((pos + size) <= stop); + + if (id == 0x37) { // CueTrackPosition(s) ID + TrackPosition& tp = *p++; + tp.Parse(pReader, pos, size); + } + + pos += size; // consume payload + assert(pos <= stop); + } + + assert(size_t(p - m_track_positions) == m_track_positions_count); + + m_element_start = element_start; + m_element_size = element_size; +} + +void CuePoint::TrackPosition::Parse(IMkvReader* pReader, long long start_, + long long size_) { + const long long stop = start_ + size_; + long long pos = start_; + + m_track = -1; + m_pos = -1; + m_block = 1; // default + + while (pos < stop) { + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); // TODO + assert((pos + len) <= stop); + + pos += len; // consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert((pos + len) <= stop); + + pos += len; // consume Size field + assert((pos + size) <= stop); + + if (id == 0x77) // CueTrack ID + m_track = UnserializeUInt(pReader, pos, size); + + else if (id == 0x71) // CueClusterPos ID + m_pos = UnserializeUInt(pReader, pos, size); + + else if (id == 0x1378) // CueBlockNumber + m_block = UnserializeUInt(pReader, pos, size); + + pos += size; // consume payload + assert(pos <= stop); + } + + assert(m_pos >= 0); + assert(m_track > 0); + // assert(m_block > 0); +} + +const CuePoint::TrackPosition* CuePoint::Find(const Track* pTrack) const { + assert(pTrack); + + const long long n = pTrack->GetNumber(); + + const TrackPosition* i = m_track_positions; + const TrackPosition* const j = i + m_track_positions_count; + + while (i != j) { + const TrackPosition& p = *i++; + + if (p.m_track == n) + return &p; + } + + return NULL; // no matching track number found +} + +long long CuePoint::GetTimeCode() const { return m_timecode; } + +long long CuePoint::GetTime(const Segment* pSegment) const { + assert(pSegment); + assert(m_timecode >= 0); + + const SegmentInfo* const pInfo = pSegment->GetInfo(); + assert(pInfo); + + const long long scale = pInfo->GetTimeCodeScale(); + assert(scale >= 1); + + const long long time = scale * m_timecode; + + return time; +} + +#if 0 +long long Segment::Unparsed() const +{ + if (m_size < 0) + return LLONG_MAX; + + const long long stop = m_start + m_size; + + const long long result = stop - m_pos; + assert(result >= 0); + + return result; +} +#else +bool Segment::DoneParsing() const { + if (m_size < 0) { + long long total, avail; + + const int status = m_pReader->Length(&total, &avail); + + if (status < 0) // error + return true; // must assume done + + if (total < 0) + return false; // assume live stream + + return (m_pos >= total); + } + + const long long stop = m_start + m_size; + + return (m_pos >= stop); +} +#endif + +const Cluster* Segment::GetFirst() const { + if ((m_clusters == NULL) || (m_clusterCount <= 0)) + return &m_eos; + + Cluster* const pCluster = m_clusters[0]; + assert(pCluster); + + return pCluster; +} + +const Cluster* Segment::GetLast() const { + if ((m_clusters == NULL) || (m_clusterCount <= 0)) + return &m_eos; + + const long idx = m_clusterCount - 1; + + Cluster* const pCluster = m_clusters[idx]; + assert(pCluster); + + return pCluster; +} + +unsigned long Segment::GetCount() const { return m_clusterCount; } + +const Cluster* Segment::GetNext(const Cluster* pCurr) { + assert(pCurr); + assert(pCurr != &m_eos); + assert(m_clusters); + + long idx = pCurr->m_index; + + if (idx >= 0) { + assert(m_clusterCount > 0); + assert(idx < m_clusterCount); + assert(pCurr == m_clusters[idx]); + + ++idx; + + if (idx >= m_clusterCount) + return &m_eos; // caller will LoadCluster as desired + + Cluster* const pNext = m_clusters[idx]; + assert(pNext); + assert(pNext->m_index >= 0); + assert(pNext->m_index == idx); + + return pNext; + } + + assert(m_clusterPreloadCount > 0); + + long long pos = pCurr->m_element_start; + + assert(m_size >= 0); // TODO + const long long stop = m_start + m_size; // end of segment + + { + long len; + + long long result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); + assert((pos + len) <= stop); // TODO + if (result != 0) + return NULL; + + const long long id = ReadUInt(m_pReader, pos, len); + assert(id == 0x0F43B675); // Cluster ID + if (id != 0x0F43B675) + return NULL; + + pos += len; // consume ID + + // Read Size + result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); // TODO + assert((pos + len) <= stop); // TODO + + const long long size = ReadUInt(m_pReader, pos, len); + assert(size > 0); // TODO + // assert((pCurr->m_size <= 0) || (pCurr->m_size == size)); + + pos += len; // consume length of size of element + assert((pos + size) <= stop); // TODO + + // Pos now points to start of payload + + pos += size; // consume payload + } + + long long off_next = 0; + + while (pos < stop) { + long len; + + long long result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); + assert((pos + len) <= stop); // TODO + if (result != 0) + return NULL; + + const long long idpos = pos; // pos of next (potential) cluster + + const long long id = ReadUInt(m_pReader, idpos, len); + assert(id > 0); // TODO + + pos += len; // consume ID + + // Read Size + result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); // TODO + assert((pos + len) <= stop); // TODO + + const long long size = ReadUInt(m_pReader, pos, len); + assert(size >= 0); // TODO + + pos += len; // consume length of size of element + assert((pos + size) <= stop); // TODO + + // Pos now points to start of payload + + if (size == 0) // weird + continue; + + if (id == 0x0F43B675) { // Cluster ID + const long long off_next_ = idpos - m_start; + + long long pos_; + long len_; + + const long status = Cluster::HasBlockEntries(this, off_next_, pos_, len_); + + assert(status >= 0); + + if (status > 0) { + off_next = off_next_; + break; + } + } + + pos += size; // consume payload + } + + if (off_next <= 0) + return 0; + + Cluster** const ii = m_clusters + m_clusterCount; + Cluster** i = ii; + + Cluster** const jj = ii + m_clusterPreloadCount; + Cluster** j = jj; + + while (i < j) { + // INVARIANT: + //[0, i) < pos_next + //[i, j) ? + //[j, jj) > pos_next + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + Cluster* const pNext = *k; + assert(pNext); + assert(pNext->m_index < 0); + + // const long long pos_ = pNext->m_pos; + // assert(pos_); + // pos = pos_ * ((pos_ < 0) ? -1 : 1); + + pos = pNext->GetPosition(); + + if (pos < off_next) + i = k + 1; + else if (pos > off_next) + j = k; + else + return pNext; + } + + assert(i == j); + + Cluster* const pNext = Cluster::Create(this, -1, off_next); + assert(pNext); + + const ptrdiff_t idx_next = i - m_clusters; // insertion position + + PreloadCluster(pNext, idx_next); + assert(m_clusters); + assert(idx_next < m_clusterSize); + assert(m_clusters[idx_next] == pNext); + + return pNext; +} + +long Segment::ParseNext(const Cluster* pCurr, const Cluster*& pResult, + long long& pos, long& len) { + assert(pCurr); + assert(!pCurr->EOS()); + assert(m_clusters); + + pResult = 0; + + if (pCurr->m_index >= 0) { // loaded (not merely preloaded) + assert(m_clusters[pCurr->m_index] == pCurr); + + const long next_idx = pCurr->m_index + 1; + + if (next_idx < m_clusterCount) { + pResult = m_clusters[next_idx]; + return 0; // success + } + + // curr cluster is last among loaded + + const long result = LoadCluster(pos, len); + + if (result < 0) // error or underflow + return result; + + if (result > 0) // no more clusters + { + // pResult = &m_eos; + return 1; + } + + pResult = GetLast(); + return 0; // success + } + + assert(m_pos > 0); + + long long total, avail; + + long status = m_pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + assert((total < 0) || (avail <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + // interrogate curr cluster + + pos = pCurr->m_element_start; + + if (pCurr->m_element_size >= 0) + pos += pCurr->m_element_size; + else { + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(m_pReader, pos, len); + + if (id != 0x0F43B675) // weird: not Cluster ID + return -1; + + pos += len; // consume ID + + // Read Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) // error + return static_cast(size); + + pos += len; // consume size field + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) // TODO: should never happen + return E_FILE_FORMAT_INVALID; // TODO: resolve this + + // assert((pCurr->m_size <= 0) || (pCurr->m_size == size)); + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + // Pos now points to start of payload + + pos += size; // consume payload (that is, the current cluster) + assert((segment_stop < 0) || (pos <= segment_stop)); + + // By consuming the payload, we are assuming that the curr + // cluster isn't interesting. That is, we don't bother checking + // whether the payload of the curr cluster is less than what + // happens to be available (obtained via IMkvReader::Length). + // Presumably the caller has already dispensed with the current + // cluster, and really does want the next cluster. + } + + // pos now points to just beyond the last fully-loaded cluster + + for (;;) { + const long status = DoParseNext(pResult, pos, len); + + if (status <= 1) + return status; + } +} + +long Segment::DoParseNext(const Cluster*& pResult, long long& pos, long& len) { + long long total, avail; + + long status = m_pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + assert((total < 0) || (avail <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + // Parse next cluster. This is strictly a parsing activity. + // Creation of a new cluster object happens later, after the + // parsing is done. + + long long off_next = 0; + long long cluster_size = -1; + + for (;;) { + if ((total >= 0) && (pos >= total)) + return 1; // EOF + + if ((segment_stop >= 0) && (pos >= segment_stop)) + return 1; // EOF + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; // absolute + const long long idoff = pos - m_start; // relative + + const long long id = ReadUInt(m_pReader, idpos, len); // absolute + + if (id < 0) // error + return static_cast(id); + + if (id == 0) // weird + return -1; // generic error + + pos += len; // consume ID + + // Read Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) // error + return static_cast(size); + + pos += len; // consume length of size of element + + // Pos now points to start of payload + + if (size == 0) // weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if ((segment_stop >= 0) && (size != unknown_size) && + ((pos + size) > segment_stop)) { + return E_FILE_FORMAT_INVALID; + } + + if (id == 0x0C53BB6B) { // Cues ID + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + const long long element_stop = pos + size; + + if ((segment_stop >= 0) && (element_stop > segment_stop)) + return E_FILE_FORMAT_INVALID; + + const long long element_start = idpos; + const long long element_size = element_stop - element_start; + + if (m_pCues == NULL) { + m_pCues = new Cues(this, pos, size, element_start, element_size); + assert(m_pCues); // TODO + } + + pos += size; // consume payload + assert((segment_stop < 0) || (pos <= segment_stop)); + + continue; + } + + if (id != 0x0F43B675) { // not a Cluster ID + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + pos += size; // consume payload + assert((segment_stop < 0) || (pos <= segment_stop)); + + continue; + } + +#if 0 // this is commented-out to support incremental cluster parsing + len = static_cast(size); + + if (element_stop > avail) + return E_BUFFER_NOT_FULL; +#endif + + // We have a cluster. + + off_next = idoff; + + if (size != unknown_size) + cluster_size = size; + + break; + } + + assert(off_next > 0); // have cluster + + // We have parsed the next cluster. + // We have not created a cluster object yet. What we need + // to do now is determine whether it has already be preloaded + //(in which case, an object for this cluster has already been + // created), and if not, create a new cluster object. + + Cluster** const ii = m_clusters + m_clusterCount; + Cluster** i = ii; + + Cluster** const jj = ii + m_clusterPreloadCount; + Cluster** j = jj; + + while (i < j) { + // INVARIANT: + //[0, i) < pos_next + //[i, j) ? + //[j, jj) > pos_next + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + const Cluster* const pNext = *k; + assert(pNext); + assert(pNext->m_index < 0); + + pos = pNext->GetPosition(); + assert(pos >= 0); + + if (pos < off_next) + i = k + 1; + else if (pos > off_next) + j = k; + else { + pResult = pNext; + return 0; // success + } + } + + assert(i == j); + + long long pos_; + long len_; + + status = Cluster::HasBlockEntries(this, off_next, pos_, len_); + + if (status < 0) { // error or underflow + pos = pos_; + len = len_; + + return status; + } + + if (status > 0) { // means "found at least one block entry" + Cluster* const pNext = Cluster::Create(this, + -1, // preloaded + off_next); + // element_size); + assert(pNext); + + const ptrdiff_t idx_next = i - m_clusters; // insertion position + + PreloadCluster(pNext, idx_next); + assert(m_clusters); + assert(idx_next < m_clusterSize); + assert(m_clusters[idx_next] == pNext); + + pResult = pNext; + return 0; // success + } + + // status == 0 means "no block entries found" + + if (cluster_size < 0) { // unknown size + const long long payload_pos = pos; // absolute pos of cluster payload + + for (;;) { // determine cluster size + if ((total >= 0) && (pos >= total)) + break; + + if ((segment_stop >= 0) && (pos >= segment_stop)) + break; // no more clusters + + // Read ID + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id < 0) // error (or underflow) + return static_cast(id); + + // This is the distinguished set of ID's we use to determine + // that we have exhausted the sub-element's inside the cluster + // whose ID we parsed earlier. + + if (id == 0x0F43B675) // Cluster ID + break; + + if (id == 0x0C53BB6B) // Cues ID + break; + + pos += len; // consume ID (of sub-element) + + // Read Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) // error + return static_cast(size); + + pos += len; // consume size field of element + + // pos now points to start of sub-element's payload + + if (size == 0) // weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; // not allowed for sub-elements + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) // weird + return E_FILE_FORMAT_INVALID; + + pos += size; // consume payload of sub-element + assert((segment_stop < 0) || (pos <= segment_stop)); + } // determine cluster size + + cluster_size = pos - payload_pos; + assert(cluster_size >= 0); // TODO: handle cluster_size = 0 + + pos = payload_pos; // reset and re-parse original cluster + } + + pos += cluster_size; // consume payload + assert((segment_stop < 0) || (pos <= segment_stop)); + + return 2; // try to find a cluster that follows next +} + +const Cluster* Segment::FindCluster(long long time_ns) const { + if ((m_clusters == NULL) || (m_clusterCount <= 0)) + return &m_eos; + + { + Cluster* const pCluster = m_clusters[0]; + assert(pCluster); + assert(pCluster->m_index == 0); + + if (time_ns <= pCluster->GetTime()) + return pCluster; + } + + // Binary search of cluster array + + long i = 0; + long j = m_clusterCount; + + while (i < j) { + // INVARIANT: + //[0, i) <= time_ns + //[i, j) ? + //[j, m_clusterCount) > time_ns + + const long k = i + (j - i) / 2; + assert(k < m_clusterCount); + + Cluster* const pCluster = m_clusters[k]; + assert(pCluster); + assert(pCluster->m_index == k); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + i = k + 1; + else + j = k; + + assert(i <= j); + } + + assert(i == j); + assert(i > 0); + assert(i <= m_clusterCount); + + const long k = i - 1; + + Cluster* const pCluster = m_clusters[k]; + assert(pCluster); + assert(pCluster->m_index == k); + assert(pCluster->GetTime() <= time_ns); + + return pCluster; +} + +#if 0 +const BlockEntry* Segment::Seek( + long long time_ns, + const Track* pTrack) const +{ + assert(pTrack); + + if ((m_clusters == NULL) || (m_clusterCount <= 0)) + return pTrack->GetEOS(); + + Cluster** const i = m_clusters; + assert(i); + + { + Cluster* const pCluster = *i; + assert(pCluster); + assert(pCluster->m_index == 0); //m_clusterCount > 0 + assert(pCluster->m_pSegment == this); + + if (time_ns <= pCluster->GetTime()) + return pCluster->GetEntry(pTrack); + } + + Cluster** const j = i + m_clusterCount; + + if (pTrack->GetType() == 2) { //audio + //TODO: we could decide to use cues for this, as we do for video. + //But we only use it for video because looking around for a keyframe + //can get expensive. Audio doesn't require anything special so a + //straight cluster search is good enough (we assume). + + Cluster** lo = i; + Cluster** hi = j; + + while (lo < hi) + { + //INVARIANT: + //[i, lo) <= time_ns + //[lo, hi) ? + //[hi, j) > time_ns + + Cluster** const mid = lo + (hi - lo) / 2; + assert(mid < hi); + + Cluster* const pCluster = *mid; + assert(pCluster); + assert(pCluster->m_index == long(mid - m_clusters)); + assert(pCluster->m_pSegment == this); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + lo = mid + 1; + else + hi = mid; + + assert(lo <= hi); + } + + assert(lo == hi); + assert(lo > i); + assert(lo <= j); + + while (lo > i) + { + Cluster* const pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + const BlockEntry* const pBE = pCluster->GetEntry(pTrack); + + if ((pBE != 0) && !pBE->EOS()) + return pBE; + + //landed on empty cluster (no entries) + } + + return pTrack->GetEOS(); //weird + } + + assert(pTrack->GetType() == 1); //video + + Cluster** lo = i; + Cluster** hi = j; + + while (lo < hi) + { + //INVARIANT: + //[i, lo) <= time_ns + //[lo, hi) ? + //[hi, j) > time_ns + + Cluster** const mid = lo + (hi - lo) / 2; + assert(mid < hi); + + Cluster* const pCluster = *mid; + assert(pCluster); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + lo = mid + 1; + else + hi = mid; + + assert(lo <= hi); + } + + assert(lo == hi); + assert(lo > i); + assert(lo <= j); + + Cluster* pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + { + const BlockEntry* const pBE = pCluster->GetEntry(pTrack, time_ns); + + if ((pBE != 0) && !pBE->EOS()) //found a keyframe + return pBE; + } + + const VideoTrack* const pVideo = static_cast(pTrack); + + while (lo != i) + { + pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + const BlockEntry* const pBlockEntry = pCluster->GetMaxKey(pVideo); + + if ((pBlockEntry != 0) && !pBlockEntry->EOS()) + return pBlockEntry; + } + + //weird: we're on the first cluster, but no keyframe found + //should never happen but we must return something anyway + + return pTrack->GetEOS(); +} +#endif + +#if 0 +bool Segment::SearchCues( + long long time_ns, + Track* pTrack, + Cluster*& pCluster, + const BlockEntry*& pBlockEntry, + const CuePoint*& pCP, + const CuePoint::TrackPosition*& pTP) +{ + if (pTrack->GetType() != 1) //not video + return false; //TODO: for now, just handle video stream + + if (m_pCues == NULL) + return false; + + if (!m_pCues->Find(time_ns, pTrack, pCP, pTP)) + return false; //weird + + assert(pCP); + assert(pTP); + assert(pTP->m_track == pTrack->GetNumber()); + + //We have the cue point and track position we want, + //so we now need to search for the cluster having + //the indicated position. + + return GetCluster(pCP, pTP, pCluster, pBlockEntry); +} +#endif + +const Tracks* Segment::GetTracks() const { return m_pTracks; } + +const SegmentInfo* Segment::GetInfo() const { return m_pInfo; } + +const Cues* Segment::GetCues() const { return m_pCues; } + +const Chapters* Segment::GetChapters() const { return m_pChapters; } + +const SeekHead* Segment::GetSeekHead() const { return m_pSeekHead; } + +long long Segment::GetDuration() const { + assert(m_pInfo); + return m_pInfo->GetDuration(); +} + +Chapters::Chapters(Segment* pSegment, long long payload_start, + long long payload_size, long long element_start, + long long element_size) + : m_pSegment(pSegment), + m_start(payload_start), + m_size(payload_size), + m_element_start(element_start), + m_element_size(element_size), + m_editions(NULL), + m_editions_size(0), + m_editions_count(0) {} + +Chapters::~Chapters() { + while (m_editions_count > 0) { + Edition& e = m_editions[--m_editions_count]; + e.Clear(); + } +} + +long Chapters::Parse() { + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = m_start; // payload start + const long long stop = pos + m_size; // payload stop + + while (pos < stop) { + long long id, size; + + long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == 0x05B9) { // EditionEntry ID + status = ParseEdition(pos, size); + + if (status < 0) // error + return status; + } + + pos += size; + assert(pos <= stop); + } + + assert(pos == stop); + return 0; +} + +int Chapters::GetEditionCount() const { return m_editions_count; } + +const Chapters::Edition* Chapters::GetEdition(int idx) const { + if (idx < 0) + return NULL; + + if (idx >= m_editions_count) + return NULL; + + return m_editions + idx; +} + +bool Chapters::ExpandEditionsArray() { + if (m_editions_size > m_editions_count) + return true; // nothing else to do + + const int size = (m_editions_size == 0) ? 1 : 2 * m_editions_size; + + Edition* const editions = new (std::nothrow) Edition[size]; + + if (editions == NULL) + return false; + + for (int idx = 0; idx < m_editions_count; ++idx) { + m_editions[idx].ShallowCopy(editions[idx]); + } + + delete[] m_editions; + m_editions = editions; + + m_editions_size = size; + return true; +} + +long Chapters::ParseEdition(long long pos, long long size) { + if (!ExpandEditionsArray()) + return -1; + + Edition& e = m_editions[m_editions_count++]; + e.Init(); + + return e.Parse(m_pSegment->m_pReader, pos, size); +} + +Chapters::Edition::Edition() {} + +Chapters::Edition::~Edition() {} + +int Chapters::Edition::GetAtomCount() const { return m_atoms_count; } + +const Chapters::Atom* Chapters::Edition::GetAtom(int index) const { + if (index < 0) + return NULL; + + if (index >= m_atoms_count) + return NULL; + + return m_atoms + index; +} + +void Chapters::Edition::Init() { + m_atoms = NULL; + m_atoms_size = 0; + m_atoms_count = 0; +} + +void Chapters::Edition::ShallowCopy(Edition& rhs) const { + rhs.m_atoms = m_atoms; + rhs.m_atoms_size = m_atoms_size; + rhs.m_atoms_count = m_atoms_count; +} + +void Chapters::Edition::Clear() { + while (m_atoms_count > 0) { + Atom& a = m_atoms[--m_atoms_count]; + a.Clear(); + } + + delete[] m_atoms; + m_atoms = NULL; + + m_atoms_size = 0; +} + +long Chapters::Edition::Parse(IMkvReader* pReader, long long pos, + long long size) { + const long long stop = pos + size; + + while (pos < stop) { + long long id, size; + + long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == 0x36) { // Atom ID + status = ParseAtom(pReader, pos, size); + + if (status < 0) // error + return status; + } + + pos += size; + assert(pos <= stop); + } + + assert(pos == stop); + return 0; +} + +long Chapters::Edition::ParseAtom(IMkvReader* pReader, long long pos, + long long size) { + if (!ExpandAtomsArray()) + return -1; + + Atom& a = m_atoms[m_atoms_count++]; + a.Init(); + + return a.Parse(pReader, pos, size); +} + +bool Chapters::Edition::ExpandAtomsArray() { + if (m_atoms_size > m_atoms_count) + return true; // nothing else to do + + const int size = (m_atoms_size == 0) ? 1 : 2 * m_atoms_size; + + Atom* const atoms = new (std::nothrow) Atom[size]; + + if (atoms == NULL) + return false; + + for (int idx = 0; idx < m_atoms_count; ++idx) { + m_atoms[idx].ShallowCopy(atoms[idx]); + } + + delete[] m_atoms; + m_atoms = atoms; + + m_atoms_size = size; + return true; +} + +Chapters::Atom::Atom() {} + +Chapters::Atom::~Atom() {} + +unsigned long long Chapters::Atom::GetUID() const { return m_uid; } + +const char* Chapters::Atom::GetStringUID() const { return m_string_uid; } + +long long Chapters::Atom::GetStartTimecode() const { return m_start_timecode; } + +long long Chapters::Atom::GetStopTimecode() const { return m_stop_timecode; } + +long long Chapters::Atom::GetStartTime(const Chapters* pChapters) const { + return GetTime(pChapters, m_start_timecode); +} + +long long Chapters::Atom::GetStopTime(const Chapters* pChapters) const { + return GetTime(pChapters, m_stop_timecode); +} + +int Chapters::Atom::GetDisplayCount() const { return m_displays_count; } + +const Chapters::Display* Chapters::Atom::GetDisplay(int index) const { + if (index < 0) + return NULL; + + if (index >= m_displays_count) + return NULL; + + return m_displays + index; +} + +void Chapters::Atom::Init() { + m_string_uid = NULL; + m_uid = 0; + m_start_timecode = -1; + m_stop_timecode = -1; + + m_displays = NULL; + m_displays_size = 0; + m_displays_count = 0; +} + +void Chapters::Atom::ShallowCopy(Atom& rhs) const { + rhs.m_string_uid = m_string_uid; + rhs.m_uid = m_uid; + rhs.m_start_timecode = m_start_timecode; + rhs.m_stop_timecode = m_stop_timecode; + + rhs.m_displays = m_displays; + rhs.m_displays_size = m_displays_size; + rhs.m_displays_count = m_displays_count; +} + +void Chapters::Atom::Clear() { + delete[] m_string_uid; + m_string_uid = NULL; + + while (m_displays_count > 0) { + Display& d = m_displays[--m_displays_count]; + d.Clear(); + } + + delete[] m_displays; + m_displays = NULL; + + m_displays_size = 0; +} + +long Chapters::Atom::Parse(IMkvReader* pReader, long long pos, long long size) { + const long long stop = pos + size; + + while (pos < stop) { + long long id, size; + + long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == 0x00) { // Display ID + status = ParseDisplay(pReader, pos, size); + + if (status < 0) // error + return status; + } else if (id == 0x1654) { // StringUID ID + status = UnserializeString(pReader, pos, size, m_string_uid); + + if (status < 0) // error + return status; + } else if (id == 0x33C4) { // UID ID + long long val; + status = UnserializeInt(pReader, pos, size, val); + + if (val < 0) // error + return status; + + m_uid = static_cast(val); + } else if (id == 0x11) { // TimeStart ID + const long long val = UnserializeUInt(pReader, pos, size); + + if (val < 0) // error + return static_cast(val); + + m_start_timecode = val; + } else if (id == 0x12) { // TimeEnd ID + const long long val = UnserializeUInt(pReader, pos, size); + + if (val < 0) // error + return static_cast(val); + + m_stop_timecode = val; + } + + pos += size; + assert(pos <= stop); + } + + assert(pos == stop); + return 0; +} + +long long Chapters::Atom::GetTime(const Chapters* pChapters, + long long timecode) { + if (pChapters == NULL) + return -1; + + Segment* const pSegment = pChapters->m_pSegment; + + if (pSegment == NULL) // weird + return -1; + + const SegmentInfo* const pInfo = pSegment->GetInfo(); + + if (pInfo == NULL) + return -1; + + const long long timecode_scale = pInfo->GetTimeCodeScale(); + + if (timecode_scale < 1) // weird + return -1; + + if (timecode < 0) + return -1; + + const long long result = timecode_scale * timecode; + + return result; +} + +long Chapters::Atom::ParseDisplay(IMkvReader* pReader, long long pos, + long long size) { + if (!ExpandDisplaysArray()) + return -1; + + Display& d = m_displays[m_displays_count++]; + d.Init(); + + return d.Parse(pReader, pos, size); +} + +bool Chapters::Atom::ExpandDisplaysArray() { + if (m_displays_size > m_displays_count) + return true; // nothing else to do + + const int size = (m_displays_size == 0) ? 1 : 2 * m_displays_size; + + Display* const displays = new (std::nothrow) Display[size]; + + if (displays == NULL) + return false; + + for (int idx = 0; idx < m_displays_count; ++idx) { + m_displays[idx].ShallowCopy(displays[idx]); + } + + delete[] m_displays; + m_displays = displays; + + m_displays_size = size; + return true; +} + +Chapters::Display::Display() {} + +Chapters::Display::~Display() {} + +const char* Chapters::Display::GetString() const { return m_string; } + +const char* Chapters::Display::GetLanguage() const { return m_language; } + +const char* Chapters::Display::GetCountry() const { return m_country; } + +void Chapters::Display::Init() { + m_string = NULL; + m_language = NULL; + m_country = NULL; +} + +void Chapters::Display::ShallowCopy(Display& rhs) const { + rhs.m_string = m_string; + rhs.m_language = m_language; + rhs.m_country = m_country; +} + +void Chapters::Display::Clear() { + delete[] m_string; + m_string = NULL; + + delete[] m_language; + m_language = NULL; + + delete[] m_country; + m_country = NULL; +} + +long Chapters::Display::Parse(IMkvReader* pReader, long long pos, + long long size) { + const long long stop = pos + size; + + while (pos < stop) { + long long id, size; + + long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == 0x05) { // ChapterString ID + status = UnserializeString(pReader, pos, size, m_string); + + if (status) + return status; + } else if (id == 0x037C) { // ChapterLanguage ID + status = UnserializeString(pReader, pos, size, m_language); + + if (status) + return status; + } else if (id == 0x037E) { // ChapterCountry ID + status = UnserializeString(pReader, pos, size, m_country); + + if (status) + return status; + } + + pos += size; + assert(pos <= stop); + } + + assert(pos == stop); + return 0; +} + +SegmentInfo::SegmentInfo(Segment* pSegment, long long start, long long size_, + long long element_start, long long element_size) + : m_pSegment(pSegment), + m_start(start), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_pMuxingAppAsUTF8(NULL), + m_pWritingAppAsUTF8(NULL), + m_pTitleAsUTF8(NULL) {} + +SegmentInfo::~SegmentInfo() { + delete[] m_pMuxingAppAsUTF8; + m_pMuxingAppAsUTF8 = NULL; + + delete[] m_pWritingAppAsUTF8; + m_pWritingAppAsUTF8 = NULL; + + delete[] m_pTitleAsUTF8; + m_pTitleAsUTF8 = NULL; +} + +long SegmentInfo::Parse() { + assert(m_pMuxingAppAsUTF8 == NULL); + assert(m_pWritingAppAsUTF8 == NULL); + assert(m_pTitleAsUTF8 == NULL); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = m_start; + const long long stop = m_start + m_size; + + m_timecodeScale = 1000000; + m_duration = -1; + + while (pos < stop) { + long long id, size; + + const long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (id == 0x0AD7B1) { // Timecode Scale + m_timecodeScale = UnserializeUInt(pReader, pos, size); + + if (m_timecodeScale <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == 0x0489) { // Segment duration + const long status = UnserializeFloat(pReader, pos, size, m_duration); + + if (status < 0) + return status; + + if (m_duration < 0) + return E_FILE_FORMAT_INVALID; + } else if (id == 0x0D80) { // MuxingApp + const long status = + UnserializeString(pReader, pos, size, m_pMuxingAppAsUTF8); + + if (status) + return status; + } else if (id == 0x1741) { // WritingApp + const long status = + UnserializeString(pReader, pos, size, m_pWritingAppAsUTF8); + + if (status) + return status; + } else if (id == 0x3BA9) { // Title + const long status = UnserializeString(pReader, pos, size, m_pTitleAsUTF8); + + if (status) + return status; + } + + pos += size; + assert(pos <= stop); + } + + assert(pos == stop); + + return 0; +} + +long long SegmentInfo::GetTimeCodeScale() const { return m_timecodeScale; } + +long long SegmentInfo::GetDuration() const { + if (m_duration < 0) + return -1; + + assert(m_timecodeScale >= 1); + + const double dd = double(m_duration) * double(m_timecodeScale); + const long long d = static_cast(dd); + + return d; +} + +const char* SegmentInfo::GetMuxingAppAsUTF8() const { + return m_pMuxingAppAsUTF8; +} + +const char* SegmentInfo::GetWritingAppAsUTF8() const { + return m_pWritingAppAsUTF8; +} + +const char* SegmentInfo::GetTitleAsUTF8() const { return m_pTitleAsUTF8; } + +/////////////////////////////////////////////////////////////// +// ContentEncoding element +ContentEncoding::ContentCompression::ContentCompression() + : algo(0), settings(NULL), settings_len(0) {} + +ContentEncoding::ContentCompression::~ContentCompression() { + delete[] settings; +} + +ContentEncoding::ContentEncryption::ContentEncryption() + : algo(0), + key_id(NULL), + key_id_len(0), + signature(NULL), + signature_len(0), + sig_key_id(NULL), + sig_key_id_len(0), + sig_algo(0), + sig_hash_algo(0) {} + +ContentEncoding::ContentEncryption::~ContentEncryption() { + delete[] key_id; + delete[] signature; + delete[] sig_key_id; +} + +ContentEncoding::ContentEncoding() + : compression_entries_(NULL), + compression_entries_end_(NULL), + encryption_entries_(NULL), + encryption_entries_end_(NULL), + encoding_order_(0), + encoding_scope_(1), + encoding_type_(0) {} + +ContentEncoding::~ContentEncoding() { + ContentCompression** comp_i = compression_entries_; + ContentCompression** const comp_j = compression_entries_end_; + + while (comp_i != comp_j) { + ContentCompression* const comp = *comp_i++; + delete comp; + } + + delete[] compression_entries_; + + ContentEncryption** enc_i = encryption_entries_; + ContentEncryption** const enc_j = encryption_entries_end_; + + while (enc_i != enc_j) { + ContentEncryption* const enc = *enc_i++; + delete enc; + } + + delete[] encryption_entries_; +} + +const ContentEncoding::ContentCompression* +ContentEncoding::GetCompressionByIndex(unsigned long idx) const { + const ptrdiff_t count = compression_entries_end_ - compression_entries_; + assert(count >= 0); + + if (idx >= static_cast(count)) + return NULL; + + return compression_entries_[idx]; +} + +unsigned long ContentEncoding::GetCompressionCount() const { + const ptrdiff_t count = compression_entries_end_ - compression_entries_; + assert(count >= 0); + + return static_cast(count); +} + +const ContentEncoding::ContentEncryption* ContentEncoding::GetEncryptionByIndex( + unsigned long idx) const { + const ptrdiff_t count = encryption_entries_end_ - encryption_entries_; + assert(count >= 0); + + if (idx >= static_cast(count)) + return NULL; + + return encryption_entries_[idx]; +} + +unsigned long ContentEncoding::GetEncryptionCount() const { + const ptrdiff_t count = encryption_entries_end_ - encryption_entries_; + assert(count >= 0); + + return static_cast(count); +} + +long ContentEncoding::ParseContentEncAESSettingsEntry( + long long start, long long size, IMkvReader* pReader, + ContentEncAESSettings* aes) { + assert(pReader); + assert(aes); + + long long pos = start; + const long long stop = start + size; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, pos, stop, id, size); + if (status < 0) // error + return status; + + if (id == 0x7E8) { + // AESSettingsCipherMode + aes->cipher_mode = UnserializeUInt(pReader, pos, size); + if (aes->cipher_mode != 1) + return E_FILE_FORMAT_INVALID; + } + + pos += size; // consume payload + assert(pos <= stop); + } + + return 0; +} + +long ContentEncoding::ParseContentEncodingEntry(long long start, long long size, + IMkvReader* pReader) { + assert(pReader); + + long long pos = start; + const long long stop = start + size; + + // Count ContentCompression and ContentEncryption elements. + int compression_count = 0; + int encryption_count = 0; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, pos, stop, id, size); + if (status < 0) // error + return status; + + if (id == 0x1034) // ContentCompression ID + ++compression_count; + + if (id == 0x1035) // ContentEncryption ID + ++encryption_count; + + pos += size; // consume payload + assert(pos <= stop); + } + + if (compression_count <= 0 && encryption_count <= 0) + return -1; + + if (compression_count > 0) { + compression_entries_ = + new (std::nothrow) ContentCompression* [compression_count]; + if (!compression_entries_) + return -1; + compression_entries_end_ = compression_entries_; + } + + if (encryption_count > 0) { + encryption_entries_ = + new (std::nothrow) ContentEncryption* [encryption_count]; + if (!encryption_entries_) { + delete[] compression_entries_; + return -1; + } + encryption_entries_end_ = encryption_entries_; + } + + pos = start; + while (pos < stop) { + long long id, size; + long status = ParseElementHeader(pReader, pos, stop, id, size); + if (status < 0) // error + return status; + + if (id == 0x1031) { + // ContentEncodingOrder + encoding_order_ = UnserializeUInt(pReader, pos, size); + } else if (id == 0x1032) { + // ContentEncodingScope + encoding_scope_ = UnserializeUInt(pReader, pos, size); + if (encoding_scope_ < 1) + return -1; + } else if (id == 0x1033) { + // ContentEncodingType + encoding_type_ = UnserializeUInt(pReader, pos, size); + } else if (id == 0x1034) { + // ContentCompression ID + ContentCompression* const compression = + new (std::nothrow) ContentCompression(); + if (!compression) + return -1; + + status = ParseCompressionEntry(pos, size, pReader, compression); + if (status) { + delete compression; + return status; + } + *compression_entries_end_++ = compression; + } else if (id == 0x1035) { + // ContentEncryption ID + ContentEncryption* const encryption = + new (std::nothrow) ContentEncryption(); + if (!encryption) + return -1; + + status = ParseEncryptionEntry(pos, size, pReader, encryption); + if (status) { + delete encryption; + return status; + } + *encryption_entries_end_++ = encryption; + } + + pos += size; // consume payload + assert(pos <= stop); + } + + assert(pos == stop); + return 0; +} + +long ContentEncoding::ParseCompressionEntry(long long start, long long size, + IMkvReader* pReader, + ContentCompression* compression) { + assert(pReader); + assert(compression); + + long long pos = start; + const long long stop = start + size; + + bool valid = false; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, pos, stop, id, size); + if (status < 0) // error + return status; + + if (id == 0x254) { + // ContentCompAlgo + long long algo = UnserializeUInt(pReader, pos, size); + if (algo < 0) + return E_FILE_FORMAT_INVALID; + compression->algo = algo; + valid = true; + } else if (id == 0x255) { + // ContentCompSettings + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast(size); + typedef unsigned char* buf_t; + const buf_t buf = new (std::nothrow) unsigned char[buflen]; + if (buf == NULL) + return -1; + + const int read_status = + pReader->Read(pos, static_cast(buflen), buf); + if (read_status) { + delete[] buf; + return status; + } + + compression->settings = buf; + compression->settings_len = buflen; + } + + pos += size; // consume payload + assert(pos <= stop); + } + + // ContentCompAlgo is mandatory + if (!valid) + return E_FILE_FORMAT_INVALID; + + return 0; +} + +long ContentEncoding::ParseEncryptionEntry(long long start, long long size, + IMkvReader* pReader, + ContentEncryption* encryption) { + assert(pReader); + assert(encryption); + + long long pos = start; + const long long stop = start + size; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, pos, stop, id, size); + if (status < 0) // error + return status; + + if (id == 0x7E1) { + // ContentEncAlgo + encryption->algo = UnserializeUInt(pReader, pos, size); + if (encryption->algo != 5) + return E_FILE_FORMAT_INVALID; + } else if (id == 0x7E2) { + // ContentEncKeyID + delete[] encryption -> key_id; + encryption->key_id = NULL; + encryption->key_id_len = 0; + + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast(size); + typedef unsigned char* buf_t; + const buf_t buf = new (std::nothrow) unsigned char[buflen]; + if (buf == NULL) + return -1; + + const int read_status = + pReader->Read(pos, static_cast(buflen), buf); + if (read_status) { + delete[] buf; + return status; + } + + encryption->key_id = buf; + encryption->key_id_len = buflen; + } else if (id == 0x7E3) { + // ContentSignature + delete[] encryption -> signature; + encryption->signature = NULL; + encryption->signature_len = 0; + + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast(size); + typedef unsigned char* buf_t; + const buf_t buf = new (std::nothrow) unsigned char[buflen]; + if (buf == NULL) + return -1; + + const int read_status = + pReader->Read(pos, static_cast(buflen), buf); + if (read_status) { + delete[] buf; + return status; + } + + encryption->signature = buf; + encryption->signature_len = buflen; + } else if (id == 0x7E4) { + // ContentSigKeyID + delete[] encryption -> sig_key_id; + encryption->sig_key_id = NULL; + encryption->sig_key_id_len = 0; + + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast(size); + typedef unsigned char* buf_t; + const buf_t buf = new (std::nothrow) unsigned char[buflen]; + if (buf == NULL) + return -1; + + const int read_status = + pReader->Read(pos, static_cast(buflen), buf); + if (read_status) { + delete[] buf; + return status; + } + + encryption->sig_key_id = buf; + encryption->sig_key_id_len = buflen; + } else if (id == 0x7E5) { + // ContentSigAlgo + encryption->sig_algo = UnserializeUInt(pReader, pos, size); + } else if (id == 0x7E6) { + // ContentSigHashAlgo + encryption->sig_hash_algo = UnserializeUInt(pReader, pos, size); + } else if (id == 0x7E7) { + // ContentEncAESSettings + const long status = ParseContentEncAESSettingsEntry( + pos, size, pReader, &encryption->aes_settings); + if (status) + return status; + } + + pos += size; // consume payload + assert(pos <= stop); + } + + return 0; +} + +Track::Track(Segment* pSegment, long long element_start, long long element_size) + : m_pSegment(pSegment), + m_element_start(element_start), + m_element_size(element_size), + content_encoding_entries_(NULL), + content_encoding_entries_end_(NULL) {} + +Track::~Track() { + Info& info = const_cast(m_info); + info.Clear(); + + ContentEncoding** i = content_encoding_entries_; + ContentEncoding** const j = content_encoding_entries_end_; + + while (i != j) { + ContentEncoding* const encoding = *i++; + delete encoding; + } + + delete[] content_encoding_entries_; +} + +long Track::Create(Segment* pSegment, const Info& info, long long element_start, + long long element_size, Track*& pResult) { + if (pResult) + return -1; + + Track* const pTrack = + new (std::nothrow) Track(pSegment, element_start, element_size); + + if (pTrack == NULL) + return -1; // generic error + + const int status = info.Copy(pTrack->m_info); + + if (status) { // error + delete pTrack; + return status; + } + + pResult = pTrack; + return 0; // success +} + +Track::Info::Info() + : uid(0), + defaultDuration(0), + codecDelay(0), + seekPreRoll(0), + nameAsUTF8(NULL), + language(NULL), + codecId(NULL), + codecNameAsUTF8(NULL), + codecPrivate(NULL), + codecPrivateSize(0), + lacing(false) {} + +Track::Info::~Info() { Clear(); } + +void Track::Info::Clear() { + delete[] nameAsUTF8; + nameAsUTF8 = NULL; + + delete[] language; + language = NULL; + + delete[] codecId; + codecId = NULL; + + delete[] codecPrivate; + codecPrivate = NULL; + codecPrivateSize = 0; + + delete[] codecNameAsUTF8; + codecNameAsUTF8 = NULL; +} + +int Track::Info::CopyStr(char* Info::*str, Info& dst_) const { + if (str == static_cast(NULL)) + return -1; + + char*& dst = dst_.*str; + + if (dst) // should be NULL already + return -1; + + const char* const src = this->*str; + + if (src == NULL) + return 0; + + const size_t len = strlen(src); + + dst = new (std::nothrow) char[len + 1]; + + if (dst == NULL) + return -1; + + strcpy(dst, src); + + return 0; +} + +int Track::Info::Copy(Info& dst) const { + if (&dst == this) + return 0; + + dst.type = type; + dst.number = number; + dst.defaultDuration = defaultDuration; + dst.codecDelay = codecDelay; + dst.seekPreRoll = seekPreRoll; + dst.uid = uid; + dst.lacing = lacing; + dst.settings = settings; + + // We now copy the string member variables from src to dst. + // This involves memory allocation so in principle the operation + // can fail (indeed, that's why we have Info::Copy), so we must + // report this to the caller. An error return from this function + // therefore implies that the copy was only partially successful. + + if (int status = CopyStr(&Info::nameAsUTF8, dst)) + return status; + + if (int status = CopyStr(&Info::language, dst)) + return status; + + if (int status = CopyStr(&Info::codecId, dst)) + return status; + + if (int status = CopyStr(&Info::codecNameAsUTF8, dst)) + return status; + + if (codecPrivateSize > 0) { + if (codecPrivate == NULL) + return -1; + + if (dst.codecPrivate) + return -1; + + if (dst.codecPrivateSize != 0) + return -1; + + dst.codecPrivate = new (std::nothrow) unsigned char[codecPrivateSize]; + + if (dst.codecPrivate == NULL) + return -1; + + memcpy(dst.codecPrivate, codecPrivate, codecPrivateSize); + dst.codecPrivateSize = codecPrivateSize; + } + + return 0; +} + +const BlockEntry* Track::GetEOS() const { return &m_eos; } + +long Track::GetType() const { return m_info.type; } + +long Track::GetNumber() const { return m_info.number; } + +unsigned long long Track::GetUid() const { return m_info.uid; } + +const char* Track::GetNameAsUTF8() const { return m_info.nameAsUTF8; } + +const char* Track::GetLanguage() const { return m_info.language; } + +const char* Track::GetCodecNameAsUTF8() const { return m_info.codecNameAsUTF8; } + +const char* Track::GetCodecId() const { return m_info.codecId; } + +const unsigned char* Track::GetCodecPrivate(size_t& size) const { + size = m_info.codecPrivateSize; + return m_info.codecPrivate; +} + +bool Track::GetLacing() const { return m_info.lacing; } + +unsigned long long Track::GetDefaultDuration() const { + return m_info.defaultDuration; +} + +unsigned long long Track::GetCodecDelay() const { return m_info.codecDelay; } + +unsigned long long Track::GetSeekPreRoll() const { return m_info.seekPreRoll; } + +long Track::GetFirst(const BlockEntry*& pBlockEntry) const { + const Cluster* pCluster = m_pSegment->GetFirst(); + + for (int i = 0;;) { + if (pCluster == NULL) { + pBlockEntry = GetEOS(); + return 1; + } + + if (pCluster->EOS()) { +#if 0 + if (m_pSegment->Unparsed() <= 0) { //all clusters have been loaded + pBlockEntry = GetEOS(); + return 1; + } +#else + if (m_pSegment->DoneParsing()) { + pBlockEntry = GetEOS(); + return 1; + } +#endif + + pBlockEntry = 0; + return E_BUFFER_NOT_FULL; + } + + long status = pCluster->GetFirst(pBlockEntry); + + if (status < 0) // error + return status; + + if (pBlockEntry == 0) { // empty cluster + pCluster = m_pSegment->GetNext(pCluster); + continue; + } + + for (;;) { + const Block* const pBlock = pBlockEntry->GetBlock(); + assert(pBlock); + + const long long tn = pBlock->GetTrackNumber(); + + if ((tn == m_info.number) && VetEntry(pBlockEntry)) + return 0; + + const BlockEntry* pNextEntry; + + status = pCluster->GetNext(pBlockEntry, pNextEntry); + + if (status < 0) // error + return status; + + if (pNextEntry == 0) + break; + + pBlockEntry = pNextEntry; + } + + ++i; + + if (i >= 100) + break; + + pCluster = m_pSegment->GetNext(pCluster); + } + + // NOTE: if we get here, it means that we didn't find a block with + // a matching track number. We interpret that as an error (which + // might be too conservative). + + pBlockEntry = GetEOS(); // so we can return a non-NULL value + return 1; +} + +long Track::GetNext(const BlockEntry* pCurrEntry, + const BlockEntry*& pNextEntry) const { + assert(pCurrEntry); + assert(!pCurrEntry->EOS()); //? + + const Block* const pCurrBlock = pCurrEntry->GetBlock(); + assert(pCurrBlock && pCurrBlock->GetTrackNumber() == m_info.number); + if (!pCurrBlock || pCurrBlock->GetTrackNumber() != m_info.number) + return -1; + + const Cluster* pCluster = pCurrEntry->GetCluster(); + assert(pCluster); + assert(!pCluster->EOS()); + + long status = pCluster->GetNext(pCurrEntry, pNextEntry); + + if (status < 0) // error + return status; + + for (int i = 0;;) { + while (pNextEntry) { + const Block* const pNextBlock = pNextEntry->GetBlock(); + assert(pNextBlock); + + if (pNextBlock->GetTrackNumber() == m_info.number) + return 0; + + pCurrEntry = pNextEntry; + + status = pCluster->GetNext(pCurrEntry, pNextEntry); + + if (status < 0) // error + return status; + } + + pCluster = m_pSegment->GetNext(pCluster); + + if (pCluster == NULL) { + pNextEntry = GetEOS(); + return 1; + } + + if (pCluster->EOS()) { +#if 0 + if (m_pSegment->Unparsed() <= 0) //all clusters have been loaded + { + pNextEntry = GetEOS(); + return 1; + } +#else + if (m_pSegment->DoneParsing()) { + pNextEntry = GetEOS(); + return 1; + } +#endif + + // TODO: there is a potential O(n^2) problem here: we tell the + // caller to (pre)load another cluster, which he does, but then he + // calls GetNext again, which repeats the same search. This is + // a pathological case, since the only way it can happen is if + // there exists a long sequence of clusters none of which contain a + // block from this track. One way around this problem is for the + // caller to be smarter when he loads another cluster: don't call + // us back until you have a cluster that contains a block from this + // track. (Of course, that's not cheap either, since our caller + // would have to scan the each cluster as it's loaded, so that + // would just push back the problem.) + + pNextEntry = NULL; + return E_BUFFER_NOT_FULL; + } + + status = pCluster->GetFirst(pNextEntry); + + if (status < 0) // error + return status; + + if (pNextEntry == NULL) // empty cluster + continue; + + ++i; + + if (i >= 100) + break; + } + + // NOTE: if we get here, it means that we didn't find a block with + // a matching track number after lots of searching, so we give + // up trying. + + pNextEntry = GetEOS(); // so we can return a non-NULL value + return 1; +} + +bool Track::VetEntry(const BlockEntry* pBlockEntry) const { + assert(pBlockEntry); + const Block* const pBlock = pBlockEntry->GetBlock(); + assert(pBlock); + assert(pBlock->GetTrackNumber() == m_info.number); + if (!pBlock || pBlock->GetTrackNumber() != m_info.number) + return false; + + // This function is used during a seek to determine whether the + // frame is a valid seek target. This default function simply + // returns true, which means all frames are valid seek targets. + // It gets overridden by the VideoTrack class, because only video + // keyframes can be used as seek target. + + return true; +} + +long Track::Seek(long long time_ns, const BlockEntry*& pResult) const { + const long status = GetFirst(pResult); + + if (status < 0) // buffer underflow, etc + return status; + + assert(pResult); + + if (pResult->EOS()) + return 0; + + const Cluster* pCluster = pResult->GetCluster(); + assert(pCluster); + assert(pCluster->GetIndex() >= 0); + + if (time_ns <= pResult->GetBlock()->GetTime(pCluster)) + return 0; + + Cluster** const clusters = m_pSegment->m_clusters; + assert(clusters); + + const long count = m_pSegment->GetCount(); // loaded only, not preloaded + assert(count > 0); + + Cluster** const i = clusters + pCluster->GetIndex(); + assert(i); + assert(*i == pCluster); + assert(pCluster->GetTime() <= time_ns); + + Cluster** const j = clusters + count; + + Cluster** lo = i; + Cluster** hi = j; + + while (lo < hi) { + // INVARIANT: + //[i, lo) <= time_ns + //[lo, hi) ? + //[hi, j) > time_ns + + Cluster** const mid = lo + (hi - lo) / 2; + assert(mid < hi); + + pCluster = *mid; + assert(pCluster); + assert(pCluster->GetIndex() >= 0); + assert(pCluster->GetIndex() == long(mid - m_pSegment->m_clusters)); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + lo = mid + 1; + else + hi = mid; + + assert(lo <= hi); + } + + assert(lo == hi); + assert(lo > i); + assert(lo <= j); + + while (lo > i) { + pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + pResult = pCluster->GetEntry(this); + + if ((pResult != 0) && !pResult->EOS()) + return 0; + + // landed on empty cluster (no entries) + } + + pResult = GetEOS(); // weird + return 0; +} + +const ContentEncoding* Track::GetContentEncodingByIndex( + unsigned long idx) const { + const ptrdiff_t count = + content_encoding_entries_end_ - content_encoding_entries_; + assert(count >= 0); + + if (idx >= static_cast(count)) + return NULL; + + return content_encoding_entries_[idx]; +} + +unsigned long Track::GetContentEncodingCount() const { + const ptrdiff_t count = + content_encoding_entries_end_ - content_encoding_entries_; + assert(count >= 0); + + return static_cast(count); +} + +long Track::ParseContentEncodingsEntry(long long start, long long size) { + IMkvReader* const pReader = m_pSegment->m_pReader; + assert(pReader); + + long long pos = start; + const long long stop = start + size; + + // Count ContentEncoding elements. + int count = 0; + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, pos, stop, id, size); + if (status < 0) // error + return status; + + // pos now designates start of element + if (id == 0x2240) // ContentEncoding ID + ++count; + + pos += size; // consume payload + assert(pos <= stop); + } + + if (count <= 0) + return -1; + + content_encoding_entries_ = new (std::nothrow) ContentEncoding* [count]; + if (!content_encoding_entries_) + return -1; + + content_encoding_entries_end_ = content_encoding_entries_; + + pos = start; + while (pos < stop) { + long long id, size; + long status = ParseElementHeader(pReader, pos, stop, id, size); + if (status < 0) // error + return status; + + // pos now designates start of element + if (id == 0x2240) { // ContentEncoding ID + ContentEncoding* const content_encoding = + new (std::nothrow) ContentEncoding(); + if (!content_encoding) + return -1; + + status = content_encoding->ParseContentEncodingEntry(pos, size, pReader); + if (status) { + delete content_encoding; + return status; + } + + *content_encoding_entries_end_++ = content_encoding; + } + + pos += size; // consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + return 0; +} + +Track::EOSBlock::EOSBlock() : BlockEntry(NULL, LONG_MIN) {} + +BlockEntry::Kind Track::EOSBlock::GetKind() const { return kBlockEOS; } + +const Block* Track::EOSBlock::GetBlock() const { return NULL; } + +VideoTrack::VideoTrack(Segment* pSegment, long long element_start, + long long element_size) + : Track(pSegment, element_start, element_size) {} + +long VideoTrack::Parse(Segment* pSegment, const Info& info, + long long element_start, long long element_size, + VideoTrack*& pResult) { + if (pResult) + return -1; + + if (info.type != Track::kVideo) + return -1; + + long long width = 0; + long long height = 0; + double rate = 0.0; + + IMkvReader* const pReader = pSegment->m_pReader; + + const Settings& s = info.settings; + assert(s.start >= 0); + assert(s.size >= 0); + + long long pos = s.start; + assert(pos >= 0); + + const long long stop = pos + s.size; + + while (pos < stop) { + long long id, size; + + const long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (id == 0x30) { // pixel width + width = UnserializeUInt(pReader, pos, size); + + if (width <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == 0x3A) { // pixel height + height = UnserializeUInt(pReader, pos, size); + + if (height <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == 0x0383E3) { // frame rate + const long status = UnserializeFloat(pReader, pos, size, rate); + + if (status < 0) + return status; + + if (rate <= 0) + return E_FILE_FORMAT_INVALID; + } + + pos += size; // consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + VideoTrack* const pTrack = + new (std::nothrow) VideoTrack(pSegment, element_start, element_size); + + if (pTrack == NULL) + return -1; // generic error + + const int status = info.Copy(pTrack->m_info); + + if (status) { // error + delete pTrack; + return status; + } + + pTrack->m_width = width; + pTrack->m_height = height; + pTrack->m_rate = rate; + + pResult = pTrack; + return 0; // success +} + +bool VideoTrack::VetEntry(const BlockEntry* pBlockEntry) const { + return Track::VetEntry(pBlockEntry) && pBlockEntry->GetBlock()->IsKey(); +} + +long VideoTrack::Seek(long long time_ns, const BlockEntry*& pResult) const { + const long status = GetFirst(pResult); + + if (status < 0) // buffer underflow, etc + return status; + + assert(pResult); + + if (pResult->EOS()) + return 0; + + const Cluster* pCluster = pResult->GetCluster(); + assert(pCluster); + assert(pCluster->GetIndex() >= 0); + + if (time_ns <= pResult->GetBlock()->GetTime(pCluster)) + return 0; + + Cluster** const clusters = m_pSegment->m_clusters; + assert(clusters); + + const long count = m_pSegment->GetCount(); // loaded only, not pre-loaded + assert(count > 0); + + Cluster** const i = clusters + pCluster->GetIndex(); + assert(i); + assert(*i == pCluster); + assert(pCluster->GetTime() <= time_ns); + + Cluster** const j = clusters + count; + + Cluster** lo = i; + Cluster** hi = j; + + while (lo < hi) { + // INVARIANT: + //[i, lo) <= time_ns + //[lo, hi) ? + //[hi, j) > time_ns + + Cluster** const mid = lo + (hi - lo) / 2; + assert(mid < hi); + + pCluster = *mid; + assert(pCluster); + assert(pCluster->GetIndex() >= 0); + assert(pCluster->GetIndex() == long(mid - m_pSegment->m_clusters)); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + lo = mid + 1; + else + hi = mid; + + assert(lo <= hi); + } + + assert(lo == hi); + assert(lo > i); + assert(lo <= j); + + pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + pResult = pCluster->GetEntry(this, time_ns); + + if ((pResult != 0) && !pResult->EOS()) // found a keyframe + return 0; + + while (lo != i) { + pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + +#if 0 + //TODO: + //We need to handle the case when a cluster + //contains multiple keyframes. Simply returning + //the largest keyframe on the cluster isn't + //good enough. + pResult = pCluster->GetMaxKey(this); +#else + pResult = pCluster->GetEntry(this, time_ns); +#endif + + if ((pResult != 0) && !pResult->EOS()) + return 0; + } + + // weird: we're on the first cluster, but no keyframe found + // should never happen but we must return something anyway + + pResult = GetEOS(); + return 0; +} + +long long VideoTrack::GetWidth() const { return m_width; } + +long long VideoTrack::GetHeight() const { return m_height; } + +double VideoTrack::GetFrameRate() const { return m_rate; } + +AudioTrack::AudioTrack(Segment* pSegment, long long element_start, + long long element_size) + : Track(pSegment, element_start, element_size) {} + +long AudioTrack::Parse(Segment* pSegment, const Info& info, + long long element_start, long long element_size, + AudioTrack*& pResult) { + if (pResult) + return -1; + + if (info.type != Track::kAudio) + return -1; + + IMkvReader* const pReader = pSegment->m_pReader; + + const Settings& s = info.settings; + assert(s.start >= 0); + assert(s.size >= 0); + + long long pos = s.start; + assert(pos >= 0); + + const long long stop = pos + s.size; + + double rate = 8000.0; // MKV default + long long channels = 1; + long long bit_depth = 0; + + while (pos < stop) { + long long id, size; + + long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (id == 0x35) { // Sample Rate + status = UnserializeFloat(pReader, pos, size, rate); + + if (status < 0) + return status; + + if (rate <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == 0x1F) { // Channel Count + channels = UnserializeUInt(pReader, pos, size); + + if (channels <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == 0x2264) { // Bit Depth + bit_depth = UnserializeUInt(pReader, pos, size); + + if (bit_depth <= 0) + return E_FILE_FORMAT_INVALID; + } + + pos += size; // consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + AudioTrack* const pTrack = + new (std::nothrow) AudioTrack(pSegment, element_start, element_size); + + if (pTrack == NULL) + return -1; // generic error + + const int status = info.Copy(pTrack->m_info); + + if (status) { + delete pTrack; + return status; + } + + pTrack->m_rate = rate; + pTrack->m_channels = channels; + pTrack->m_bitDepth = bit_depth; + + pResult = pTrack; + return 0; // success +} + +double AudioTrack::GetSamplingRate() const { return m_rate; } + +long long AudioTrack::GetChannels() const { return m_channels; } + +long long AudioTrack::GetBitDepth() const { return m_bitDepth; } + +Tracks::Tracks(Segment* pSegment, long long start, long long size_, + long long element_start, long long element_size) + : m_pSegment(pSegment), + m_start(start), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_trackEntries(NULL), + m_trackEntriesEnd(NULL) {} + +long Tracks::Parse() { + assert(m_trackEntries == NULL); + assert(m_trackEntriesEnd == NULL); + + const long long stop = m_start + m_size; + IMkvReader* const pReader = m_pSegment->m_pReader; + + int count = 0; + long long pos = m_start; + + while (pos < stop) { + long long id, size; + + const long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == 0x2E) // TrackEntry ID + ++count; + + pos += size; // consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + if (count <= 0) + return 0; // success + + m_trackEntries = new (std::nothrow) Track* [count]; + + if (m_trackEntries == NULL) + return -1; + + m_trackEntriesEnd = m_trackEntries; + + pos = m_start; + + while (pos < stop) { + const long long element_start = pos; + + long long id, payload_size; + + const long status = + ParseElementHeader(pReader, pos, stop, id, payload_size); + + if (status < 0) // error + return status; + + if (payload_size == 0) // weird + continue; + + const long long payload_stop = pos + payload_size; + assert(payload_stop <= stop); // checked in ParseElement + + const long long element_size = payload_stop - element_start; + + if (id == 0x2E) { // TrackEntry ID + Track*& pTrack = *m_trackEntriesEnd; + pTrack = NULL; + + const long status = ParseTrackEntry(pos, payload_size, element_start, + element_size, pTrack); + + if (status) + return status; + + if (pTrack) + ++m_trackEntriesEnd; + } + + pos = payload_stop; + assert(pos <= stop); + } + + assert(pos == stop); + + return 0; // success +} + +unsigned long Tracks::GetTracksCount() const { + const ptrdiff_t result = m_trackEntriesEnd - m_trackEntries; + assert(result >= 0); + + return static_cast(result); +} + +long Tracks::ParseTrackEntry(long long track_start, long long track_size, + long long element_start, long long element_size, + Track*& pResult) const { + if (pResult) + return -1; + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = track_start; + const long long track_stop = track_start + track_size; + + Track::Info info; + + info.type = 0; + info.number = 0; + info.uid = 0; + info.defaultDuration = 0; + + Track::Settings v; + v.start = -1; + v.size = -1; + + Track::Settings a; + a.start = -1; + a.size = -1; + + Track::Settings e; // content_encodings_settings; + e.start = -1; + e.size = -1; + + long long lacing = 1; // default is true + + while (pos < track_stop) { + long long id, size; + + const long status = ParseElementHeader(pReader, pos, track_stop, id, size); + + if (status < 0) // error + return status; + + if (size < 0) + return E_FILE_FORMAT_INVALID; + + const long long start = pos; + + if (id == 0x60) { // VideoSettings ID + v.start = start; + v.size = size; + } else if (id == 0x61) { // AudioSettings ID + a.start = start; + a.size = size; + } else if (id == 0x2D80) { // ContentEncodings ID + e.start = start; + e.size = size; + } else if (id == 0x33C5) { // Track UID + if (size > 8) + return E_FILE_FORMAT_INVALID; + + info.uid = 0; + + long long pos_ = start; + const long long pos_end = start + size; + + while (pos_ != pos_end) { + unsigned char b; + + const int status = pReader->Read(pos_, 1, &b); + + if (status) + return status; + + info.uid <<= 8; + info.uid |= b; + + ++pos_; + } + } else if (id == 0x57) { // Track Number + const long long num = UnserializeUInt(pReader, pos, size); + + if ((num <= 0) || (num > 127)) + return E_FILE_FORMAT_INVALID; + + info.number = static_cast(num); + } else if (id == 0x03) { // Track Type + const long long type = UnserializeUInt(pReader, pos, size); + + if ((type <= 0) || (type > 254)) + return E_FILE_FORMAT_INVALID; + + info.type = static_cast(type); + } else if (id == 0x136E) { // Track Name + const long status = + UnserializeString(pReader, pos, size, info.nameAsUTF8); + + if (status) + return status; + } else if (id == 0x02B59C) { // Track Language + const long status = UnserializeString(pReader, pos, size, info.language); + + if (status) + return status; + } else if (id == 0x03E383) { // Default Duration + const long long duration = UnserializeUInt(pReader, pos, size); + + if (duration < 0) + return E_FILE_FORMAT_INVALID; + + info.defaultDuration = static_cast(duration); + } else if (id == 0x06) { // CodecID + const long status = UnserializeString(pReader, pos, size, info.codecId); + + if (status) + return status; + } else if (id == 0x1C) { // lacing + lacing = UnserializeUInt(pReader, pos, size); + + if ((lacing < 0) || (lacing > 1)) + return E_FILE_FORMAT_INVALID; + } else if (id == 0x23A2) { // Codec Private + delete[] info.codecPrivate; + info.codecPrivate = NULL; + info.codecPrivateSize = 0; + + const size_t buflen = static_cast(size); + + if (buflen) { + typedef unsigned char* buf_t; + + const buf_t buf = new (std::nothrow) unsigned char[buflen]; + + if (buf == NULL) + return -1; + + const int status = pReader->Read(pos, static_cast(buflen), buf); + + if (status) { + delete[] buf; + return status; + } + + info.codecPrivate = buf; + info.codecPrivateSize = buflen; + } + } else if (id == 0x058688) { // Codec Name + const long status = + UnserializeString(pReader, pos, size, info.codecNameAsUTF8); + + if (status) + return status; + } else if (id == 0x16AA) { // Codec Delay + info.codecDelay = UnserializeUInt(pReader, pos, size); + } else if (id == 0x16BB) { // Seek Pre Roll + info.seekPreRoll = UnserializeUInt(pReader, pos, size); + } + + pos += size; // consume payload + assert(pos <= track_stop); + } + + assert(pos == track_stop); + + if (info.number <= 0) // not specified + return E_FILE_FORMAT_INVALID; + + if (GetTrackByNumber(info.number)) + return E_FILE_FORMAT_INVALID; + + if (info.type <= 0) // not specified + return E_FILE_FORMAT_INVALID; + + info.lacing = (lacing > 0) ? true : false; + + if (info.type == Track::kVideo) { + if (v.start < 0) + return E_FILE_FORMAT_INVALID; + + if (a.start >= 0) + return E_FILE_FORMAT_INVALID; + + info.settings = v; + + VideoTrack* pTrack = NULL; + + const long status = VideoTrack::Parse(m_pSegment, info, element_start, + element_size, pTrack); + + if (status) + return status; + + pResult = pTrack; + assert(pResult); + + if (e.start >= 0) + pResult->ParseContentEncodingsEntry(e.start, e.size); + } else if (info.type == Track::kAudio) { + if (a.start < 0) + return E_FILE_FORMAT_INVALID; + + if (v.start >= 0) + return E_FILE_FORMAT_INVALID; + + info.settings = a; + + AudioTrack* pTrack = NULL; + + const long status = AudioTrack::Parse(m_pSegment, info, element_start, + element_size, pTrack); + + if (status) + return status; + + pResult = pTrack; + assert(pResult); + + if (e.start >= 0) + pResult->ParseContentEncodingsEntry(e.start, e.size); + } else { + // neither video nor audio - probably metadata or subtitles + + if (a.start >= 0) + return E_FILE_FORMAT_INVALID; + + if (v.start >= 0) + return E_FILE_FORMAT_INVALID; + + if (e.start >= 0) + return E_FILE_FORMAT_INVALID; + + info.settings.start = -1; + info.settings.size = 0; + + Track* pTrack = NULL; + + const long status = + Track::Create(m_pSegment, info, element_start, element_size, pTrack); + + if (status) + return status; + + pResult = pTrack; + assert(pResult); + } + + return 0; // success +} + +Tracks::~Tracks() { + Track** i = m_trackEntries; + Track** const j = m_trackEntriesEnd; + + while (i != j) { + Track* const pTrack = *i++; + delete pTrack; + } + + delete[] m_trackEntries; +} + +const Track* Tracks::GetTrackByNumber(long tn) const { + if (tn < 0) + return NULL; + + Track** i = m_trackEntries; + Track** const j = m_trackEntriesEnd; + + while (i != j) { + Track* const pTrack = *i++; + + if (pTrack == NULL) + continue; + + if (tn == pTrack->GetNumber()) + return pTrack; + } + + return NULL; // not found +} + +const Track* Tracks::GetTrackByIndex(unsigned long idx) const { + const ptrdiff_t count = m_trackEntriesEnd - m_trackEntries; + + if (idx >= static_cast(count)) + return NULL; + + return m_trackEntries[idx]; +} + +#if 0 +long long Cluster::Unparsed() const +{ + if (m_timecode < 0) //not even partially loaded + return LLONG_MAX; + + assert(m_pos >= m_element_start); + //assert(m_element_size > m_size); + + const long long element_stop = m_element_start + m_element_size; + assert(m_pos <= element_stop); + + const long long result = element_stop - m_pos; + assert(result >= 0); + + return result; +} +#endif + +long Cluster::Load(long long& pos, long& len) const { + assert(m_pSegment); + assert(m_pos >= m_element_start); + + if (m_timecode >= 0) // at least partially loaded + return 0; + + assert(m_pos == m_element_start); + assert(m_element_size < 0); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long total, avail; + + const int status = pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + assert((total < 0) || (avail <= total)); + assert((total < 0) || (m_pos <= total)); // TODO: verify this + + pos = m_pos; + + long long cluster_size = -1; + + { + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error or underflow + return static_cast(result); + + if (result > 0) // underflow (weird) + return E_BUFFER_NOT_FULL; + + // if ((pos + len) > segment_stop) + // return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id_ = ReadUInt(pReader, pos, len); + + if (id_ < 0) // error + return static_cast(id_); + + if (id_ != 0x0F43B675) // Cluster ID + return E_FILE_FORMAT_INVALID; + + pos += len; // consume id + + // read cluster size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + // if ((pos + len) > segment_stop) + // return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) // error + return static_cast(cluster_size); + + if (size == 0) + return E_FILE_FORMAT_INVALID; // TODO: verify this + + pos += len; // consume length of size of element + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size != unknown_size) + cluster_size = size; + } + +// pos points to start of payload + +#if 0 + len = static_cast(size_); + + if (cluster_stop > avail) + return E_BUFFER_NOT_FULL; +#endif + + long long timecode = -1; + long long new_pos = -1; + bool bBlock = false; + + long long cluster_stop = (cluster_size < 0) ? -1 : pos + cluster_size; + + for (;;) { + if ((cluster_stop >= 0) && (pos >= cluster_stop)) + break; + + // Parse ID + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) // error + return static_cast(id); + + if (id == 0) + return E_FILE_FORMAT_INVALID; + + // This is the distinguished set of ID's we use to determine + // that we have exhausted the sub-element's inside the cluster + // whose ID we parsed earlier. + + if (id == 0x0F43B675) // Cluster ID + break; + + if (id == 0x0C53BB6B) // Cues ID + break; + + pos += len; // consume ID field + + // Parse Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) // error + return static_cast(size); + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume size field + + if ((cluster_stop >= 0) && (pos > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + // pos now points to start of payload + + if (size == 0) // weird + continue; + + if ((cluster_stop >= 0) && ((pos + size) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if (id == 0x67) { // TimeCode ID + len = static_cast(size); + + if ((pos + size) > avail) + return E_BUFFER_NOT_FULL; + + timecode = UnserializeUInt(pReader, pos, size); + + if (timecode < 0) // error (or underflow) + return static_cast(timecode); + + new_pos = pos + size; + + if (bBlock) + break; + } else if (id == 0x20) { // BlockGroup ID + bBlock = true; + break; + } else if (id == 0x23) { // SimpleBlock ID + bBlock = true; + break; + } + + pos += size; // consume payload + assert((cluster_stop < 0) || (pos <= cluster_stop)); + } + + assert((cluster_stop < 0) || (pos <= cluster_stop)); + + if (timecode < 0) // no timecode found + return E_FILE_FORMAT_INVALID; + + if (!bBlock) + return E_FILE_FORMAT_INVALID; + + m_pos = new_pos; // designates position just beyond timecode payload + m_timecode = timecode; // m_timecode >= 0 means we're partially loaded + + if (cluster_size >= 0) + m_element_size = cluster_stop - m_element_start; + + return 0; +} + +long Cluster::Parse(long long& pos, long& len) const { + long status = Load(pos, len); + + if (status < 0) + return status; + + assert(m_pos >= m_element_start); + assert(m_timecode >= 0); + // assert(m_size > 0); + // assert(m_element_size > m_size); + + const long long cluster_stop = + (m_element_size < 0) ? -1 : m_element_start + m_element_size; + + if ((cluster_stop >= 0) && (m_pos >= cluster_stop)) + return 1; // nothing else to do + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long total, avail; + + status = pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + assert((total < 0) || (avail <= total)); + + pos = m_pos; + + for (;;) { + if ((cluster_stop >= 0) && (pos >= cluster_stop)) + break; + + if ((total >= 0) && (pos >= total)) { + if (m_element_size < 0) + m_element_size = pos - m_element_start; + + break; + } + + // Parse ID + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) // error + return static_cast(id); + + if (id == 0) // weird + return E_FILE_FORMAT_INVALID; + + // This is the distinguished set of ID's we use to determine + // that we have exhausted the sub-element's inside the cluster + // whose ID we parsed earlier. + + if ((id == 0x0F43B675) || (id == 0x0C53BB6B)) { // Cluster or Cues ID + if (m_element_size < 0) + m_element_size = pos - m_element_start; + + break; + } + + pos += len; // consume ID field + + // Parse Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) // error + return static_cast(size); + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume size field + + if ((cluster_stop >= 0) && (pos > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + // pos now points to start of payload + + if (size == 0) // weird + continue; + + // const long long block_start = pos; + const long long block_stop = pos + size; + + if (cluster_stop >= 0) { + if (block_stop > cluster_stop) { + if ((id == 0x20) || (id == 0x23)) + return E_FILE_FORMAT_INVALID; + + pos = cluster_stop; + break; + } + } else if ((total >= 0) && (block_stop > total)) { + m_element_size = total - m_element_start; + pos = total; + break; + } else if (block_stop > avail) { + len = static_cast(size); + return E_BUFFER_NOT_FULL; + } + + Cluster* const this_ = const_cast(this); + + if (id == 0x20) // BlockGroup + return this_->ParseBlockGroup(size, pos, len); + + if (id == 0x23) // SimpleBlock + return this_->ParseSimpleBlock(size, pos, len); + + pos += size; // consume payload + assert((cluster_stop < 0) || (pos <= cluster_stop)); + } + + assert(m_element_size > 0); + + m_pos = pos; + assert((cluster_stop < 0) || (m_pos <= cluster_stop)); + + if (m_entries_count > 0) { + const long idx = m_entries_count - 1; + + const BlockEntry* const pLast = m_entries[idx]; + assert(pLast); + + const Block* const pBlock = pLast->GetBlock(); + assert(pBlock); + + const long long start = pBlock->m_start; + + if ((total >= 0) && (start > total)) + return -1; // defend against trucated stream + + const long long size = pBlock->m_size; + + const long long stop = start + size; + assert((cluster_stop < 0) || (stop <= cluster_stop)); + + if ((total >= 0) && (stop > total)) + return -1; // defend against trucated stream + } + + return 1; // no more entries +} + +long Cluster::ParseSimpleBlock(long long block_size, long long& pos, + long& len) { + const long long block_start = pos; + const long long block_stop = pos + block_size; + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long total, avail; + + long status = pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + assert((total < 0) || (avail <= total)); + + // parse track number + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((pos + len) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long track = ReadUInt(pReader, pos, len); + + if (track < 0) // error + return static_cast(track); + + if (track == 0) + return E_FILE_FORMAT_INVALID; + +#if 0 + //TODO(matthewjheaney) + //This turned out to be too conservative. The problem is that + //if we see a track header in the tracks element with an unsupported + //track type, we throw that track header away, so it is not present + //in the track map. But even though we don't understand the track + //header, there are still blocks in the cluster with that track + //number. It was our decision to ignore that track header, so it's + //up to us to deal with blocks associated with that track -- we + //cannot simply report an error since technically there's nothing + //wrong with the file. + // + //For now we go ahead and finish the parse, creating a block entry + //for this block. This is somewhat wasteful, because without a + //track header there's nothing you can do with the block. What + //we really need here is a special return value that indicates to + //the caller that he should ignore this particular block, and + //continue parsing. + + const Tracks* const pTracks = m_pSegment->GetTracks(); + assert(pTracks); + + const long tn = static_cast(track); + + const Track* const pTrack = pTracks->GetTrackByNumber(tn); + + if (pTrack == NULL) + return E_FILE_FORMAT_INVALID; +#endif + + pos += len; // consume track number + + if ((pos + 2) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 2) > avail) { + len = 2; + return E_BUFFER_NOT_FULL; + } + + pos += 2; // consume timecode + + if ((pos + 1) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + unsigned char flags; + + status = pReader->Read(pos, 1, &flags); + + if (status < 0) { // error or underflow + len = 1; + return status; + } + + ++pos; // consume flags byte + assert(pos <= avail); + + if (pos >= block_stop) + return E_FILE_FORMAT_INVALID; + + const int lacing = int(flags & 0x06) >> 1; + + if ((lacing != 0) && (block_stop > avail)) { + len = static_cast(block_stop - pos); + return E_BUFFER_NOT_FULL; + } + + status = CreateBlock(0x23, // simple block id + block_start, block_size, + 0); // DiscardPadding + + if (status != 0) + return status; + + m_pos = block_stop; + + return 0; // success +} + +long Cluster::ParseBlockGroup(long long payload_size, long long& pos, + long& len) { + const long long payload_start = pos; + const long long payload_stop = pos + payload_size; + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long total, avail; + + long status = pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + assert((total < 0) || (avail <= total)); + + if ((total >= 0) && (payload_stop > total)) + return E_FILE_FORMAT_INVALID; + + if (payload_stop > avail) { + len = static_cast(payload_size); + return E_BUFFER_NOT_FULL; + } + + long long discard_padding = 0; + + while (pos < payload_stop) { + // parse sub-block element ID + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((pos + len) > payload_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) // error + return static_cast(id); + + if (id == 0) // not a value ID + return E_FILE_FORMAT_INVALID; + + pos += len; // consume ID field + + // Parse Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((pos + len) > payload_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) // error + return static_cast(size); + + pos += len; // consume size field + + // pos now points to start of sub-block group payload + + if (pos > payload_stop) + return E_FILE_FORMAT_INVALID; + + if (size == 0) // weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + if (id == 0x35A2) { // DiscardPadding + status = UnserializeInt(pReader, pos, size, discard_padding); + + if (status < 0) // error + return status; + } + + if (id != 0x21) { // sub-part of BlockGroup is not a Block + pos += size; // consume sub-part of block group + + if (pos > payload_stop) + return E_FILE_FORMAT_INVALID; + + continue; + } + + const long long block_stop = pos + size; + + if (block_stop > payload_stop) + return E_FILE_FORMAT_INVALID; + + // parse track number + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((pos + len) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long track = ReadUInt(pReader, pos, len); + + if (track < 0) // error + return static_cast(track); + + if (track == 0) + return E_FILE_FORMAT_INVALID; + +#if 0 + //TODO(matthewjheaney) + //This turned out to be too conservative. The problem is that + //if we see a track header in the tracks element with an unsupported + //track type, we throw that track header away, so it is not present + //in the track map. But even though we don't understand the track + //header, there are still blocks in the cluster with that track + //number. It was our decision to ignore that track header, so it's + //up to us to deal with blocks associated with that track -- we + //cannot simply report an error since technically there's nothing + //wrong with the file. + // + //For now we go ahead and finish the parse, creating a block entry + //for this block. This is somewhat wasteful, because without a + //track header there's nothing you can do with the block. What + //we really need here is a special return value that indicates to + //the caller that he should ignore this particular block, and + //continue parsing. + + const Tracks* const pTracks = m_pSegment->GetTracks(); + assert(pTracks); + + const long tn = static_cast(track); + + const Track* const pTrack = pTracks->GetTrackByNumber(tn); + + if (pTrack == NULL) + return E_FILE_FORMAT_INVALID; +#endif + + pos += len; // consume track number + + if ((pos + 2) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 2) > avail) { + len = 2; + return E_BUFFER_NOT_FULL; + } + + pos += 2; // consume timecode + + if ((pos + 1) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + unsigned char flags; + + status = pReader->Read(pos, 1, &flags); + + if (status < 0) { // error or underflow + len = 1; + return status; + } + + ++pos; // consume flags byte + assert(pos <= avail); + + if (pos >= block_stop) + return E_FILE_FORMAT_INVALID; + + const int lacing = int(flags & 0x06) >> 1; + + if ((lacing != 0) && (block_stop > avail)) { + len = static_cast(block_stop - pos); + return E_BUFFER_NOT_FULL; + } + + pos = block_stop; // consume block-part of block group + assert(pos <= payload_stop); + } + + assert(pos == payload_stop); + + status = CreateBlock(0x20, // BlockGroup ID + payload_start, payload_size, discard_padding); + if (status != 0) + return status; + + m_pos = payload_stop; + + return 0; // success +} + +long Cluster::GetEntry(long index, const mkvparser::BlockEntry*& pEntry) const { + assert(m_pos >= m_element_start); + + pEntry = NULL; + + if (index < 0) + return -1; // generic error + + if (m_entries_count < 0) + return E_BUFFER_NOT_FULL; + + assert(m_entries); + assert(m_entries_size > 0); + assert(m_entries_count <= m_entries_size); + + if (index < m_entries_count) { + pEntry = m_entries[index]; + assert(pEntry); + + return 1; // found entry + } + + if (m_element_size < 0) // we don't know cluster end yet + return E_BUFFER_NOT_FULL; // underflow + + const long long element_stop = m_element_start + m_element_size; + + if (m_pos >= element_stop) + return 0; // nothing left to parse + + return E_BUFFER_NOT_FULL; // underflow, since more remains to be parsed +} + +Cluster* Cluster::Create(Segment* pSegment, long idx, long long off) +// long long element_size) +{ + assert(pSegment); + assert(off >= 0); + + const long long element_start = pSegment->m_start + off; + + Cluster* const pCluster = new Cluster(pSegment, idx, element_start); + // element_size); + assert(pCluster); + + return pCluster; +} + +Cluster::Cluster() + : m_pSegment(NULL), + m_element_start(0), + m_index(0), + m_pos(0), + m_element_size(0), + m_timecode(0), + m_entries(NULL), + m_entries_size(0), + m_entries_count(0) // means "no entries" +{} + +Cluster::Cluster(Segment* pSegment, long idx, long long element_start + /* long long element_size */) + : m_pSegment(pSegment), + m_element_start(element_start), + m_index(idx), + m_pos(element_start), + m_element_size(-1 /* element_size */), + m_timecode(-1), + m_entries(NULL), + m_entries_size(0), + m_entries_count(-1) // means "has not been parsed yet" +{} + +Cluster::~Cluster() { + if (m_entries_count <= 0) + return; + + BlockEntry** i = m_entries; + BlockEntry** const j = m_entries + m_entries_count; + + while (i != j) { + BlockEntry* p = *i++; + assert(p); + + delete p; + } + + delete[] m_entries; +} + +bool Cluster::EOS() const { return (m_pSegment == NULL); } + +long Cluster::GetIndex() const { return m_index; } + +long long Cluster::GetPosition() const { + const long long pos = m_element_start - m_pSegment->m_start; + assert(pos >= 0); + + return pos; +} + +long long Cluster::GetElementSize() const { return m_element_size; } + +#if 0 +bool Cluster::HasBlockEntries( + const Segment* pSegment, + long long off) { + assert(pSegment); + assert(off >= 0); //relative to start of segment payload + + IMkvReader* const pReader = pSegment->m_pReader; + + long long pos = pSegment->m_start + off; //absolute + long long size; + + { + long len; + + const long long id = ReadUInt(pReader, pos, len); + (void)id; + assert(id >= 0); + assert(id == 0x0F43B675); //Cluster ID + + pos += len; //consume id + + size = ReadUInt(pReader, pos, len); + assert(size > 0); + + pos += len; //consume size + + //pos now points to start of payload + } + + const long long stop = pos + size; + + while (pos < stop) + { + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume id + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume size + + if (id == 0x20) //BlockGroup ID + return true; + + if (id == 0x23) //SimpleBlock ID + return true; + + pos += size; //consume payload + assert(pos <= stop); + } + + return false; +} +#endif + +long Cluster::HasBlockEntries( + const Segment* pSegment, + long long off, // relative to start of segment payload + long long& pos, long& len) { + assert(pSegment); + assert(off >= 0); // relative to segment + + IMkvReader* const pReader = pSegment->m_pReader; + + long long total, avail; + + long status = pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + assert((total < 0) || (avail <= total)); + + pos = pSegment->m_start + off; // absolute + + if ((total >= 0) && (pos >= total)) + return 0; // we don't even have a complete cluster + + const long long segment_stop = + (pSegment->m_size < 0) ? -1 : pSegment->m_start + pSegment->m_size; + + long long cluster_stop = -1; // interpreted later to mean "unknown size" + + { + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // need more data + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && ((pos + len) > total)) + return 0; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) // error + return static_cast(id); + + if (id != 0x0F43B675) // weird: not cluster ID + return -1; // generic error + + pos += len; // consume Cluster ID field + + // read size field + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && ((pos + len) > total)) + return 0; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) // error + return static_cast(size); + + if (size == 0) + return 0; // cluster does not have entries + + pos += len; // consume size field + + // pos now points to start of payload + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size != unknown_size) { + cluster_stop = pos + size; + assert(cluster_stop >= 0); + + if ((segment_stop >= 0) && (cluster_stop > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && (cluster_stop > total)) + // return E_FILE_FORMAT_INVALID; //too conservative + return 0; // cluster does not have any entries + } + } + + for (;;) { + if ((cluster_stop >= 0) && (pos >= cluster_stop)) + return 0; // no entries detected + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // need more data + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) // error + return static_cast(id); + + // This is the distinguished set of ID's we use to determine + // that we have exhausted the sub-element's inside the cluster + // whose ID we parsed earlier. + + if (id == 0x0F43B675) // Cluster ID + return 0; // no entries found + + if (id == 0x0C53BB6B) // Cues ID + return 0; // no entries found + + pos += len; // consume id field + + if ((cluster_stop >= 0) && (pos >= cluster_stop)) + return E_FILE_FORMAT_INVALID; + + // read size field + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast(result); + + if (result > 0) // underflow + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) // error + return static_cast(size); + + pos += len; // consume size field + + // pos now points to start of payload + + if ((cluster_stop >= 0) && (pos > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if (size == 0) // weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; // not supported inside cluster + + if ((cluster_stop >= 0) && ((pos + size) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if (id == 0x20) // BlockGroup ID + return 1; // have at least one entry + + if (id == 0x23) // SimpleBlock ID + return 1; // have at least one entry + + pos += size; // consume payload + assert((cluster_stop < 0) || (pos <= cluster_stop)); + } +} + +long long Cluster::GetTimeCode() const { + long long pos; + long len; + + const long status = Load(pos, len); + + if (status < 0) // error + return status; + + return m_timecode; +} + +long long Cluster::GetTime() const { + const long long tc = GetTimeCode(); + + if (tc < 0) + return tc; + + const SegmentInfo* const pInfo = m_pSegment->GetInfo(); + assert(pInfo); + + const long long scale = pInfo->GetTimeCodeScale(); + assert(scale >= 1); + + const long long t = m_timecode * scale; + + return t; +} + +long long Cluster::GetFirstTime() const { + const BlockEntry* pEntry; + + const long status = GetFirst(pEntry); + + if (status < 0) // error + return status; + + if (pEntry == NULL) // empty cluster + return GetTime(); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + return pBlock->GetTime(this); +} + +long long Cluster::GetLastTime() const { + const BlockEntry* pEntry; + + const long status = GetLast(pEntry); + + if (status < 0) // error + return status; + + if (pEntry == NULL) // empty cluster + return GetTime(); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + return pBlock->GetTime(this); +} + +long Cluster::CreateBlock(long long id, + long long pos, // absolute pos of payload + long long size, long long discard_padding) { + assert((id == 0x20) || (id == 0x23)); // BlockGroup or SimpleBlock + + if (m_entries_count < 0) { // haven't parsed anything yet + assert(m_entries == NULL); + assert(m_entries_size == 0); + + m_entries_size = 1024; + m_entries = new BlockEntry* [m_entries_size]; + + m_entries_count = 0; + } else { + assert(m_entries); + assert(m_entries_size > 0); + assert(m_entries_count <= m_entries_size); + + if (m_entries_count >= m_entries_size) { + const long entries_size = 2 * m_entries_size; + + BlockEntry** const entries = new BlockEntry* [entries_size]; + assert(entries); + + BlockEntry** src = m_entries; + BlockEntry** const src_end = src + m_entries_count; + + BlockEntry** dst = entries; + + while (src != src_end) + *dst++ = *src++; + + delete[] m_entries; + + m_entries = entries; + m_entries_size = entries_size; + } + } + + if (id == 0x20) // BlockGroup ID + return CreateBlockGroup(pos, size, discard_padding); + else // SimpleBlock ID + return CreateSimpleBlock(pos, size); +} + +long Cluster::CreateBlockGroup(long long start_offset, long long size, + long long discard_padding) { + assert(m_entries); + assert(m_entries_size > 0); + assert(m_entries_count >= 0); + assert(m_entries_count < m_entries_size); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = start_offset; + const long long stop = start_offset + size; + + // For WebM files, there is a bias towards previous reference times + //(in order to support alt-ref frames, which refer back to the previous + // keyframe). Normally a 0 value is not possible, but here we tenatively + // allow 0 as the value of a reference frame, with the interpretation + // that this is a "previous" reference time. + + long long prev = 1; // nonce + long long next = 0; // nonce + long long duration = -1; // really, this is unsigned + + long long bpos = -1; + long long bsize = -1; + + while (pos < stop) { + long len; + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); // TODO + assert((pos + len) <= stop); + + pos += len; // consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); // TODO + assert((pos + len) <= stop); + + pos += len; // consume size + + if (id == 0x21) { // Block ID + if (bpos < 0) { // Block ID + bpos = pos; + bsize = size; + } + } else if (id == 0x1B) { // Duration ID + assert(size <= 8); + + duration = UnserializeUInt(pReader, pos, size); + assert(duration >= 0); // TODO + } else if (id == 0x7B) { // ReferenceBlock + assert(size <= 8); + const long size_ = static_cast(size); + + long long time; + + long status = UnserializeInt(pReader, pos, size_, time); + assert(status == 0); + if (status != 0) + return -1; + + if (time <= 0) // see note above + prev = time; + else // weird + next = time; + } + + pos += size; // consume payload + assert(pos <= stop); + } + + assert(pos == stop); + assert(bpos >= 0); + assert(bsize >= 0); + + const long idx = m_entries_count; + + BlockEntry** const ppEntry = m_entries + idx; + BlockEntry*& pEntry = *ppEntry; + + pEntry = new (std::nothrow) + BlockGroup(this, idx, bpos, bsize, prev, next, duration, discard_padding); + + if (pEntry == NULL) + return -1; // generic error + + BlockGroup* const p = static_cast(pEntry); + + const long status = p->Parse(); + + if (status == 0) { // success + ++m_entries_count; + return 0; + } + + delete pEntry; + pEntry = 0; + + return status; +} + +long Cluster::CreateSimpleBlock(long long st, long long sz) { + assert(m_entries); + assert(m_entries_size > 0); + assert(m_entries_count >= 0); + assert(m_entries_count < m_entries_size); + + const long idx = m_entries_count; + + BlockEntry** const ppEntry = m_entries + idx; + BlockEntry*& pEntry = *ppEntry; + + pEntry = new (std::nothrow) SimpleBlock(this, idx, st, sz); + + if (pEntry == NULL) + return -1; // generic error + + SimpleBlock* const p = static_cast(pEntry); + + const long status = p->Parse(); + + if (status == 0) { + ++m_entries_count; + return 0; + } + + delete pEntry; + pEntry = 0; + + return status; +} + +long Cluster::GetFirst(const BlockEntry*& pFirst) const { + if (m_entries_count <= 0) { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) { // error + pFirst = NULL; + return status; + } + + if (m_entries_count <= 0) { // empty cluster + pFirst = NULL; + return 0; + } + } + + assert(m_entries); + + pFirst = m_entries[0]; + assert(pFirst); + + return 0; // success +} + +long Cluster::GetLast(const BlockEntry*& pLast) const { + for (;;) { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) { // error + pLast = NULL; + return status; + } + + if (status > 0) // no new block + break; + } + + if (m_entries_count <= 0) { + pLast = NULL; + return 0; + } + + assert(m_entries); + + const long idx = m_entries_count - 1; + + pLast = m_entries[idx]; + assert(pLast); + + return 0; +} + +long Cluster::GetNext(const BlockEntry* pCurr, const BlockEntry*& pNext) const { + assert(pCurr); + assert(m_entries); + assert(m_entries_count > 0); + + size_t idx = pCurr->GetIndex(); + assert(idx < size_t(m_entries_count)); + assert(m_entries[idx] == pCurr); + + ++idx; + + if (idx >= size_t(m_entries_count)) { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) { // error + pNext = NULL; + return status; + } + + if (status > 0) { + pNext = NULL; + return 0; + } + + assert(m_entries); + assert(m_entries_count > 0); + assert(idx < size_t(m_entries_count)); + } + + pNext = m_entries[idx]; + assert(pNext); + + return 0; +} + +long Cluster::GetEntryCount() const { return m_entries_count; } + +const BlockEntry* Cluster::GetEntry(const Track* pTrack, + long long time_ns) const { + assert(pTrack); + + if (m_pSegment == NULL) // this is the special EOS cluster + return pTrack->GetEOS(); + +#if 0 + + LoadBlockEntries(); + + if ((m_entries == NULL) || (m_entries_count <= 0)) + return NULL; //return EOS here? + + const BlockEntry* pResult = pTrack->GetEOS(); + + BlockEntry** i = m_entries; + assert(i); + + BlockEntry** const j = i + m_entries_count; + + while (i != j) + { + const BlockEntry* const pEntry = *i++; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != pTrack->GetNumber()) + continue; + + if (pTrack->VetEntry(pEntry)) + { + if (time_ns < 0) //just want first candidate block + return pEntry; + + const long long ns = pBlock->GetTime(this); + + if (ns > time_ns) + break; + + pResult = pEntry; + } + else if (time_ns >= 0) + { + const long long ns = pBlock->GetTime(this); + + if (ns > time_ns) + break; + } + } + + return pResult; + +#else + + const BlockEntry* pResult = pTrack->GetEOS(); + + long index = 0; + + for (;;) { + if (index >= m_entries_count) { + long long pos; + long len; + + const long status = Parse(pos, len); + assert(status >= 0); + + if (status > 0) // completely parsed, and no more entries + return pResult; + + if (status < 0) // should never happen + return 0; + + assert(m_entries); + assert(index < m_entries_count); + } + + const BlockEntry* const pEntry = m_entries[index]; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != pTrack->GetNumber()) { + ++index; + continue; + } + + if (pTrack->VetEntry(pEntry)) { + if (time_ns < 0) // just want first candidate block + return pEntry; + + const long long ns = pBlock->GetTime(this); + + if (ns > time_ns) + return pResult; + + pResult = pEntry; // have a candidate + } else if (time_ns >= 0) { + const long long ns = pBlock->GetTime(this); + + if (ns > time_ns) + return pResult; + } + + ++index; + } + +#endif +} + +const BlockEntry* Cluster::GetEntry(const CuePoint& cp, + const CuePoint::TrackPosition& tp) const { + assert(m_pSegment); + +#if 0 + + LoadBlockEntries(); + + if (m_entries == NULL) + return NULL; + + const long long count = m_entries_count; + + if (count <= 0) + return NULL; + + const long long tc = cp.GetTimeCode(); + + if ((tp.m_block > 0) && (tp.m_block <= count)) + { + const size_t block = static_cast(tp.m_block); + const size_t index = block - 1; + + const BlockEntry* const pEntry = m_entries[index]; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if ((pBlock->GetTrackNumber() == tp.m_track) && + (pBlock->GetTimeCode(this) == tc)) + { + return pEntry; + } + } + + const BlockEntry* const* i = m_entries; + const BlockEntry* const* const j = i + count; + + while (i != j) + { +#ifdef _DEBUG + const ptrdiff_t idx = i - m_entries; + idx; +#endif + + const BlockEntry* const pEntry = *i++; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != tp.m_track) + continue; + + const long long tc_ = pBlock->GetTimeCode(this); + assert(tc_ >= 0); + + if (tc_ < tc) + continue; + + if (tc_ > tc) + return NULL; + + const Tracks* const pTracks = m_pSegment->GetTracks(); + assert(pTracks); + + const long tn = static_cast(tp.m_track); + const Track* const pTrack = pTracks->GetTrackByNumber(tn); + + if (pTrack == NULL) + return NULL; + + const long long type = pTrack->GetType(); + + if (type == 2) //audio + return pEntry; + + if (type != 1) //not video + return NULL; + + if (!pBlock->IsKey()) + return NULL; + + return pEntry; + } + + return NULL; + +#else + + const long long tc = cp.GetTimeCode(); + + if (tp.m_block > 0) { + const long block = static_cast(tp.m_block); + const long index = block - 1; + + while (index >= m_entries_count) { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) // TODO: can this happen? + return NULL; + + if (status > 0) // nothing remains to be parsed + return NULL; + } + + const BlockEntry* const pEntry = m_entries[index]; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if ((pBlock->GetTrackNumber() == tp.m_track) && + (pBlock->GetTimeCode(this) == tc)) { + return pEntry; + } + } + + long index = 0; + + for (;;) { + if (index >= m_entries_count) { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) // TODO: can this happen? + return NULL; + + if (status > 0) // nothing remains to be parsed + return NULL; + + assert(m_entries); + assert(index < m_entries_count); + } + + const BlockEntry* const pEntry = m_entries[index]; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != tp.m_track) { + ++index; + continue; + } + + const long long tc_ = pBlock->GetTimeCode(this); + + if (tc_ < tc) { + ++index; + continue; + } + + if (tc_ > tc) + return NULL; + + const Tracks* const pTracks = m_pSegment->GetTracks(); + assert(pTracks); + + const long tn = static_cast(tp.m_track); + const Track* const pTrack = pTracks->GetTrackByNumber(tn); + + if (pTrack == NULL) + return NULL; + + const long long type = pTrack->GetType(); + + if (type == 2) // audio + return pEntry; + + if (type != 1) // not video + return NULL; + + if (!pBlock->IsKey()) + return NULL; + + return pEntry; + } + +#endif +} + +#if 0 +const BlockEntry* Cluster::GetMaxKey(const VideoTrack* pTrack) const +{ + assert(pTrack); + + if (m_pSegment == NULL) //EOS + return pTrack->GetEOS(); + + LoadBlockEntries(); + + if ((m_entries == NULL) || (m_entries_count <= 0)) + return pTrack->GetEOS(); + + BlockEntry** i = m_entries + m_entries_count; + BlockEntry** const j = m_entries; + + while (i != j) + { + const BlockEntry* const pEntry = *--i; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != pTrack->GetNumber()) + continue; + + if (pBlock->IsKey()) + return pEntry; + } + + return pTrack->GetEOS(); //no satisfactory block found +} +#endif + +BlockEntry::BlockEntry(Cluster* p, long idx) : m_pCluster(p), m_index(idx) {} + +BlockEntry::~BlockEntry() {} + +bool BlockEntry::EOS() const { return (GetKind() == kBlockEOS); } + +const Cluster* BlockEntry::GetCluster() const { return m_pCluster; } + +long BlockEntry::GetIndex() const { return m_index; } + +SimpleBlock::SimpleBlock(Cluster* pCluster, long idx, long long start, + long long size) + : BlockEntry(pCluster, idx), m_block(start, size, 0) {} + +long SimpleBlock::Parse() { return m_block.Parse(m_pCluster); } + +BlockEntry::Kind SimpleBlock::GetKind() const { return kBlockSimple; } + +const Block* SimpleBlock::GetBlock() const { return &m_block; } + +BlockGroup::BlockGroup(Cluster* pCluster, long idx, long long block_start, + long long block_size, long long prev, long long next, + long long duration, long long discard_padding) + : BlockEntry(pCluster, idx), + m_block(block_start, block_size, discard_padding), + m_prev(prev), + m_next(next), + m_duration(duration) {} + +long BlockGroup::Parse() { + const long status = m_block.Parse(m_pCluster); + + if (status) + return status; + + m_block.SetKey((m_prev > 0) && (m_next <= 0)); + + return 0; +} + +#if 0 +void BlockGroup::ParseBlock(long long start, long long size) +{ + IMkvReader* const pReader = m_pCluster->m_pSegment->m_pReader; + + Block* const pBlock = new Block(start, size, pReader); + assert(pBlock); //TODO + + //TODO: the Matroska spec says you have multiple blocks within the + //same block group, with blocks ranked by priority (the flag bits). + + assert(m_pBlock == NULL); + m_pBlock = pBlock; +} +#endif + +BlockEntry::Kind BlockGroup::GetKind() const { return kBlockGroup; } + +const Block* BlockGroup::GetBlock() const { return &m_block; } + +long long BlockGroup::GetPrevTimeCode() const { return m_prev; } + +long long BlockGroup::GetNextTimeCode() const { return m_next; } + +long long BlockGroup::GetDurationTimeCode() const { return m_duration; } + +Block::Block(long long start, long long size_, long long discard_padding) + : m_start(start), + m_size(size_), + m_track(0), + m_timecode(-1), + m_flags(0), + m_frames(NULL), + m_frame_count(-1), + m_discard_padding(discard_padding) {} + +Block::~Block() { delete[] m_frames; } + +long Block::Parse(const Cluster* pCluster) { + if (pCluster == NULL) + return -1; + + if (pCluster->m_pSegment == NULL) + return -1; + + assert(m_start >= 0); + assert(m_size >= 0); + assert(m_track <= 0); + assert(m_frames == NULL); + assert(m_frame_count <= 0); + + long long pos = m_start; + const long long stop = m_start + m_size; + + long len; + + IMkvReader* const pReader = pCluster->m_pSegment->m_pReader; + + m_track = ReadUInt(pReader, pos, len); + + if (m_track <= 0) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > stop) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume track number + + if ((stop - pos) < 2) + return E_FILE_FORMAT_INVALID; + + long status; + long long value; + + status = UnserializeInt(pReader, pos, 2, value); + + if (status) + return E_FILE_FORMAT_INVALID; + + if (value < SHRT_MIN) + return E_FILE_FORMAT_INVALID; + + if (value > SHRT_MAX) + return E_FILE_FORMAT_INVALID; + + m_timecode = static_cast(value); + + pos += 2; + + if ((stop - pos) <= 0) + return E_FILE_FORMAT_INVALID; + + status = pReader->Read(pos, 1, &m_flags); + + if (status) + return E_FILE_FORMAT_INVALID; + + const int lacing = int(m_flags & 0x06) >> 1; + + ++pos; // consume flags byte + + if (lacing == 0) { // no lacing + if (pos > stop) + return E_FILE_FORMAT_INVALID; + + m_frame_count = 1; + m_frames = new Frame[m_frame_count]; + + Frame& f = m_frames[0]; + f.pos = pos; + + const long long frame_size = stop - pos; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + f.len = static_cast(frame_size); + + return 0; // success + } + + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + unsigned char biased_count; + + status = pReader->Read(pos, 1, &biased_count); + + if (status) + return E_FILE_FORMAT_INVALID; + + ++pos; // consume frame count + assert(pos <= stop); + + m_frame_count = int(biased_count) + 1; + + m_frames = new Frame[m_frame_count]; + assert(m_frames); + + if (lacing == 1) { // Xiph + Frame* pf = m_frames; + Frame* const pf_end = pf + m_frame_count; + + long size = 0; + int frame_count = m_frame_count; + + while (frame_count > 1) { + long frame_size = 0; + + for (;;) { + unsigned char val; + + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + status = pReader->Read(pos, 1, &val); + + if (status) + return E_FILE_FORMAT_INVALID; + + ++pos; // consume xiph size byte + + frame_size += val; + + if (val < 255) + break; + } + + Frame& f = *pf++; + assert(pf < pf_end); + + f.pos = 0; // patch later + + f.len = frame_size; + size += frame_size; // contribution of this frame + + --frame_count; + } + + assert(pf < pf_end); + assert(pos <= stop); + + { + Frame& f = *pf++; + + if (pf != pf_end) + return E_FILE_FORMAT_INVALID; + + f.pos = 0; // patch later + + const long long total_size = stop - pos; + + if (total_size < size) + return E_FILE_FORMAT_INVALID; + + const long long frame_size = total_size - size; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + f.len = static_cast(frame_size); + } + + pf = m_frames; + while (pf != pf_end) { + Frame& f = *pf++; + assert((pos + f.len) <= stop); + + f.pos = pos; + pos += f.len; + } + + assert(pos == stop); + } else if (lacing == 2) { // fixed-size lacing + const long long total_size = stop - pos; + + if ((total_size % m_frame_count) != 0) + return E_FILE_FORMAT_INVALID; + + const long long frame_size = total_size / m_frame_count; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + Frame* pf = m_frames; + Frame* const pf_end = pf + m_frame_count; + + while (pf != pf_end) { + assert((pos + frame_size) <= stop); + + Frame& f = *pf++; + + f.pos = pos; + f.len = static_cast(frame_size); + + pos += frame_size; + } + + assert(pos == stop); + } else { + assert(lacing == 3); // EBML lacing + + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + long size = 0; + int frame_count = m_frame_count; + + long long frame_size = ReadUInt(pReader, pos, len); + + if (frame_size < 0) + return E_FILE_FORMAT_INVALID; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > stop) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume length of size of first frame + + if ((pos + frame_size) > stop) + return E_FILE_FORMAT_INVALID; + + Frame* pf = m_frames; + Frame* const pf_end = pf + m_frame_count; + + { + Frame& curr = *pf; + + curr.pos = 0; // patch later + + curr.len = static_cast(frame_size); + size += curr.len; // contribution of this frame + } + + --frame_count; + + while (frame_count > 1) { + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + assert(pf < pf_end); + + const Frame& prev = *pf++; + assert(prev.len == frame_size); + if (prev.len != frame_size) + return E_FILE_FORMAT_INVALID; + + assert(pf < pf_end); + + Frame& curr = *pf; + + curr.pos = 0; // patch later + + const long long delta_size_ = ReadUInt(pReader, pos, len); + + if (delta_size_ < 0) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > stop) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume length of (delta) size + assert(pos <= stop); + + const int exp = 7 * len - 1; + const long long bias = (1LL << exp) - 1LL; + const long long delta_size = delta_size_ - bias; + + frame_size += delta_size; + + if (frame_size < 0) + return E_FILE_FORMAT_INVALID; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + curr.len = static_cast(frame_size); + size += curr.len; // contribution of this frame + + --frame_count; + } + + { + assert(pos <= stop); + assert(pf < pf_end); + + const Frame& prev = *pf++; + assert(prev.len == frame_size); + if (prev.len != frame_size) + return E_FILE_FORMAT_INVALID; + + assert(pf < pf_end); + + Frame& curr = *pf++; + assert(pf == pf_end); + + curr.pos = 0; // patch later + + const long long total_size = stop - pos; + + if (total_size < size) + return E_FILE_FORMAT_INVALID; + + frame_size = total_size - size; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + curr.len = static_cast(frame_size); + } + + pf = m_frames; + while (pf != pf_end) { + Frame& f = *pf++; + assert((pos + f.len) <= stop); + + f.pos = pos; + pos += f.len; + } + + assert(pos == stop); + } + + return 0; // success +} + +long long Block::GetTimeCode(const Cluster* pCluster) const { + if (pCluster == 0) + return m_timecode; + + const long long tc0 = pCluster->GetTimeCode(); + assert(tc0 >= 0); + + const long long tc = tc0 + m_timecode; + + return tc; // unscaled timecode units +} + +long long Block::GetTime(const Cluster* pCluster) const { + assert(pCluster); + + const long long tc = GetTimeCode(pCluster); + + const Segment* const pSegment = pCluster->m_pSegment; + const SegmentInfo* const pInfo = pSegment->GetInfo(); + assert(pInfo); + + const long long scale = pInfo->GetTimeCodeScale(); + assert(scale >= 1); + + const long long ns = tc * scale; + + return ns; +} + +long long Block::GetTrackNumber() const { return m_track; } + +bool Block::IsKey() const { + return ((m_flags & static_cast(1 << 7)) != 0); +} + +void Block::SetKey(bool bKey) { + if (bKey) + m_flags |= static_cast(1 << 7); + else + m_flags &= 0x7F; +} + +bool Block::IsInvisible() const { return bool(int(m_flags & 0x08) != 0); } + +Block::Lacing Block::GetLacing() const { + const int value = int(m_flags & 0x06) >> 1; + return static_cast(value); +} + +int Block::GetFrameCount() const { return m_frame_count; } + +const Block::Frame& Block::GetFrame(int idx) const { + assert(idx >= 0); + assert(idx < m_frame_count); + + const Frame& f = m_frames[idx]; + assert(f.pos > 0); + assert(f.len > 0); + + return f; +} + +long Block::Frame::Read(IMkvReader* pReader, unsigned char* buf) const { + assert(pReader); + assert(buf); + + const long status = pReader->Read(pos, len, buf); + return status; +} + +long long Block::GetDiscardPadding() const { return m_discard_padding; } + +} // end namespace mkvparser diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/webm/mkvparser.hpp b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/webm/mkvparser.hpp new file mode 100644 index 00000000..3e17d07c --- /dev/null +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/webm/mkvparser.hpp @@ -0,0 +1,945 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#ifndef MKVPARSER_HPP +#define MKVPARSER_HPP + +#include +#include +#include + +namespace mkvparser { + +const int E_FILE_FORMAT_INVALID = -2; +const int E_BUFFER_NOT_FULL = -3; + +class IMkvReader { + public: + virtual int Read(long long pos, long len, unsigned char* buf) = 0; + virtual int Length(long long* total, long long* available) = 0; + + protected: + virtual ~IMkvReader(); +}; + +long long GetUIntLength(IMkvReader*, long long, long&); +long long ReadUInt(IMkvReader*, long long, long&); +long long UnserializeUInt(IMkvReader*, long long pos, long long size); + +long UnserializeFloat(IMkvReader*, long long pos, long long size, double&); +long UnserializeInt(IMkvReader*, long long pos, long len, long long& result); + +long UnserializeString(IMkvReader*, long long pos, long long size, char*& str); + +long ParseElementHeader(IMkvReader* pReader, + long long& pos, // consume id and size fields + long long stop, // if you know size of element's parent + long long& id, long long& size); + +bool Match(IMkvReader*, long long&, unsigned long, long long&); +bool Match(IMkvReader*, long long&, unsigned long, unsigned char*&, size_t&); + +void GetVersion(int& major, int& minor, int& build, int& revision); + +struct EBMLHeader { + EBMLHeader(); + ~EBMLHeader(); + long long m_version; + long long m_readVersion; + long long m_maxIdLength; + long long m_maxSizeLength; + char* m_docType; + long long m_docTypeVersion; + long long m_docTypeReadVersion; + + long long Parse(IMkvReader*, long long&); + void Init(); +}; + +class Segment; +class Track; +class Cluster; + +class Block { + Block(const Block&); + Block& operator=(const Block&); + + public: + const long long m_start; + const long long m_size; + + Block(long long start, long long size, long long discard_padding); + ~Block(); + + long Parse(const Cluster*); + + long long GetTrackNumber() const; + long long GetTimeCode(const Cluster*) const; // absolute, but not scaled + long long GetTime(const Cluster*) const; // absolute, and scaled (ns) + bool IsKey() const; + void SetKey(bool); + bool IsInvisible() const; + + enum Lacing { kLacingNone, kLacingXiph, kLacingFixed, kLacingEbml }; + Lacing GetLacing() const; + + int GetFrameCount() const; // to index frames: [0, count) + + struct Frame { + long long pos; // absolute offset + long len; + + long Read(IMkvReader*, unsigned char*) const; + }; + + const Frame& GetFrame(int frame_index) const; + + long long GetDiscardPadding() const; + + private: + long long m_track; // Track::Number() + short m_timecode; // relative to cluster + unsigned char m_flags; + + Frame* m_frames; + int m_frame_count; + + protected: + const long long m_discard_padding; +}; + +class BlockEntry { + BlockEntry(const BlockEntry&); + BlockEntry& operator=(const BlockEntry&); + + protected: + BlockEntry(Cluster*, long index); + + public: + virtual ~BlockEntry(); + + bool EOS() const; + const Cluster* GetCluster() const; + long GetIndex() const; + virtual const Block* GetBlock() const = 0; + + enum Kind { kBlockEOS, kBlockSimple, kBlockGroup }; + virtual Kind GetKind() const = 0; + + protected: + Cluster* const m_pCluster; + const long m_index; +}; + +class SimpleBlock : public BlockEntry { + SimpleBlock(const SimpleBlock&); + SimpleBlock& operator=(const SimpleBlock&); + + public: + SimpleBlock(Cluster*, long index, long long start, long long size); + long Parse(); + + Kind GetKind() const; + const Block* GetBlock() const; + + protected: + Block m_block; +}; + +class BlockGroup : public BlockEntry { + BlockGroup(const BlockGroup&); + BlockGroup& operator=(const BlockGroup&); + + public: + BlockGroup(Cluster*, long index, + long long block_start, // absolute pos of block's payload + long long block_size, // size of block's payload + long long prev, long long next, long long duration, + long long discard_padding); + + long Parse(); + + Kind GetKind() const; + const Block* GetBlock() const; + + long long GetPrevTimeCode() const; // relative to block's time + long long GetNextTimeCode() const; // as above + long long GetDurationTimeCode() const; + + private: + Block m_block; + const long long m_prev; + const long long m_next; + const long long m_duration; +}; + +/////////////////////////////////////////////////////////////// +// ContentEncoding element +// Elements used to describe if the track data has been encrypted or +// compressed with zlib or header stripping. +class ContentEncoding { + public: + enum { kCTR = 1 }; + + ContentEncoding(); + ~ContentEncoding(); + + // ContentCompression element names + struct ContentCompression { + ContentCompression(); + ~ContentCompression(); + + unsigned long long algo; + unsigned char* settings; + long long settings_len; + }; + + // ContentEncAESSettings element names + struct ContentEncAESSettings { + ContentEncAESSettings() : cipher_mode(kCTR) {} + ~ContentEncAESSettings() {} + + unsigned long long cipher_mode; + }; + + // ContentEncryption element names + struct ContentEncryption { + ContentEncryption(); + ~ContentEncryption(); + + unsigned long long algo; + unsigned char* key_id; + long long key_id_len; + unsigned char* signature; + long long signature_len; + unsigned char* sig_key_id; + long long sig_key_id_len; + unsigned long long sig_algo; + unsigned long long sig_hash_algo; + + ContentEncAESSettings aes_settings; + }; + + // Returns ContentCompression represented by |idx|. Returns NULL if |idx| + // is out of bounds. + const ContentCompression* GetCompressionByIndex(unsigned long idx) const; + + // Returns number of ContentCompression elements in this ContentEncoding + // element. + unsigned long GetCompressionCount() const; + + // Parses the ContentCompression element from |pReader|. |start| is the + // starting offset of the ContentCompression payload. |size| is the size in + // bytes of the ContentCompression payload. |compression| is where the parsed + // values will be stored. + long ParseCompressionEntry(long long start, long long size, + IMkvReader* pReader, + ContentCompression* compression); + + // Returns ContentEncryption represented by |idx|. Returns NULL if |idx| + // is out of bounds. + const ContentEncryption* GetEncryptionByIndex(unsigned long idx) const; + + // Returns number of ContentEncryption elements in this ContentEncoding + // element. + unsigned long GetEncryptionCount() const; + + // Parses the ContentEncAESSettings element from |pReader|. |start| is the + // starting offset of the ContentEncAESSettings payload. |size| is the + // size in bytes of the ContentEncAESSettings payload. |encryption| is + // where the parsed values will be stored. + long ParseContentEncAESSettingsEntry(long long start, long long size, + IMkvReader* pReader, + ContentEncAESSettings* aes); + + // Parses the ContentEncoding element from |pReader|. |start| is the + // starting offset of the ContentEncoding payload. |size| is the size in + // bytes of the ContentEncoding payload. Returns true on success. + long ParseContentEncodingEntry(long long start, long long size, + IMkvReader* pReader); + + // Parses the ContentEncryption element from |pReader|. |start| is the + // starting offset of the ContentEncryption payload. |size| is the size in + // bytes of the ContentEncryption payload. |encryption| is where the parsed + // values will be stored. + long ParseEncryptionEntry(long long start, long long size, + IMkvReader* pReader, ContentEncryption* encryption); + + unsigned long long encoding_order() const { return encoding_order_; } + unsigned long long encoding_scope() const { return encoding_scope_; } + unsigned long long encoding_type() const { return encoding_type_; } + + private: + // Member variables for list of ContentCompression elements. + ContentCompression** compression_entries_; + ContentCompression** compression_entries_end_; + + // Member variables for list of ContentEncryption elements. + ContentEncryption** encryption_entries_; + ContentEncryption** encryption_entries_end_; + + // ContentEncoding element names + unsigned long long encoding_order_; + unsigned long long encoding_scope_; + unsigned long long encoding_type_; + + // LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncoding); + ContentEncoding(const ContentEncoding&); + ContentEncoding& operator=(const ContentEncoding&); +}; + +class Track { + Track(const Track&); + Track& operator=(const Track&); + + public: + class Info; + static long Create(Segment*, const Info&, long long element_start, + long long element_size, Track*&); + + enum Type { kVideo = 1, kAudio = 2, kSubtitle = 0x11, kMetadata = 0x21 }; + + Segment* const m_pSegment; + const long long m_element_start; + const long long m_element_size; + virtual ~Track(); + + long GetType() const; + long GetNumber() const; + unsigned long long GetUid() const; + const char* GetNameAsUTF8() const; + const char* GetLanguage() const; + const char* GetCodecNameAsUTF8() const; + const char* GetCodecId() const; + const unsigned char* GetCodecPrivate(size_t&) const; + bool GetLacing() const; + unsigned long long GetDefaultDuration() const; + unsigned long long GetCodecDelay() const; + unsigned long long GetSeekPreRoll() const; + + const BlockEntry* GetEOS() const; + + struct Settings { + long long start; + long long size; + }; + + class Info { + public: + Info(); + ~Info(); + int Copy(Info&) const; + void Clear(); + long type; + long number; + unsigned long long uid; + unsigned long long defaultDuration; + unsigned long long codecDelay; + unsigned long long seekPreRoll; + char* nameAsUTF8; + char* language; + char* codecId; + char* codecNameAsUTF8; + unsigned char* codecPrivate; + size_t codecPrivateSize; + bool lacing; + Settings settings; + + private: + Info(const Info&); + Info& operator=(const Info&); + int CopyStr(char* Info::*str, Info&) const; + }; + + long GetFirst(const BlockEntry*&) const; + long GetNext(const BlockEntry* pCurr, const BlockEntry*& pNext) const; + virtual bool VetEntry(const BlockEntry*) const; + virtual long Seek(long long time_ns, const BlockEntry*&) const; + + const ContentEncoding* GetContentEncodingByIndex(unsigned long idx) const; + unsigned long GetContentEncodingCount() const; + + long ParseContentEncodingsEntry(long long start, long long size); + + protected: + Track(Segment*, long long element_start, long long element_size); + + Info m_info; + + class EOSBlock : public BlockEntry { + public: + EOSBlock(); + + Kind GetKind() const; + const Block* GetBlock() const; + }; + + EOSBlock m_eos; + + private: + ContentEncoding** content_encoding_entries_; + ContentEncoding** content_encoding_entries_end_; +}; + +class VideoTrack : public Track { + VideoTrack(const VideoTrack&); + VideoTrack& operator=(const VideoTrack&); + + VideoTrack(Segment*, long long element_start, long long element_size); + + public: + static long Parse(Segment*, const Info&, long long element_start, + long long element_size, VideoTrack*&); + + long long GetWidth() const; + long long GetHeight() const; + double GetFrameRate() const; + + bool VetEntry(const BlockEntry*) const; + long Seek(long long time_ns, const BlockEntry*&) const; + + private: + long long m_width; + long long m_height; + double m_rate; +}; + +class AudioTrack : public Track { + AudioTrack(const AudioTrack&); + AudioTrack& operator=(const AudioTrack&); + + AudioTrack(Segment*, long long element_start, long long element_size); + + public: + static long Parse(Segment*, const Info&, long long element_start, + long long element_size, AudioTrack*&); + + double GetSamplingRate() const; + long long GetChannels() const; + long long GetBitDepth() const; + + private: + double m_rate; + long long m_channels; + long long m_bitDepth; +}; + +class Tracks { + Tracks(const Tracks&); + Tracks& operator=(const Tracks&); + + public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + Tracks(Segment*, long long start, long long size, long long element_start, + long long element_size); + + ~Tracks(); + + long Parse(); + + unsigned long GetTracksCount() const; + + const Track* GetTrackByNumber(long tn) const; + const Track* GetTrackByIndex(unsigned long idx) const; + + private: + Track** m_trackEntries; + Track** m_trackEntriesEnd; + + long ParseTrackEntry(long long payload_start, long long payload_size, + long long element_start, long long element_size, + Track*&) const; +}; + +class Chapters { + Chapters(const Chapters&); + Chapters& operator=(const Chapters&); + + public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + Chapters(Segment*, long long payload_start, long long payload_size, + long long element_start, long long element_size); + + ~Chapters(); + + long Parse(); + + class Atom; + class Edition; + + class Display { + friend class Atom; + Display(); + Display(const Display&); + ~Display(); + Display& operator=(const Display&); + + public: + const char* GetString() const; + const char* GetLanguage() const; + const char* GetCountry() const; + + private: + void Init(); + void ShallowCopy(Display&) const; + void Clear(); + long Parse(IMkvReader*, long long pos, long long size); + + char* m_string; + char* m_language; + char* m_country; + }; + + class Atom { + friend class Edition; + Atom(); + Atom(const Atom&); + ~Atom(); + Atom& operator=(const Atom&); + + public: + unsigned long long GetUID() const; + const char* GetStringUID() const; + + long long GetStartTimecode() const; + long long GetStopTimecode() const; + + long long GetStartTime(const Chapters*) const; + long long GetStopTime(const Chapters*) const; + + int GetDisplayCount() const; + const Display* GetDisplay(int index) const; + + private: + void Init(); + void ShallowCopy(Atom&) const; + void Clear(); + long Parse(IMkvReader*, long long pos, long long size); + static long long GetTime(const Chapters*, long long timecode); + + long ParseDisplay(IMkvReader*, long long pos, long long size); + bool ExpandDisplaysArray(); + + char* m_string_uid; + unsigned long long m_uid; + long long m_start_timecode; + long long m_stop_timecode; + + Display* m_displays; + int m_displays_size; + int m_displays_count; + }; + + class Edition { + friend class Chapters; + Edition(); + Edition(const Edition&); + ~Edition(); + Edition& operator=(const Edition&); + + public: + int GetAtomCount() const; + const Atom* GetAtom(int index) const; + + private: + void Init(); + void ShallowCopy(Edition&) const; + void Clear(); + long Parse(IMkvReader*, long long pos, long long size); + + long ParseAtom(IMkvReader*, long long pos, long long size); + bool ExpandAtomsArray(); + + Atom* m_atoms; + int m_atoms_size; + int m_atoms_count; + }; + + int GetEditionCount() const; + const Edition* GetEdition(int index) const; + + private: + long ParseEdition(long long pos, long long size); + bool ExpandEditionsArray(); + + Edition* m_editions; + int m_editions_size; + int m_editions_count; +}; + +class SegmentInfo { + SegmentInfo(const SegmentInfo&); + SegmentInfo& operator=(const SegmentInfo&); + + public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + SegmentInfo(Segment*, long long start, long long size, + long long element_start, long long element_size); + + ~SegmentInfo(); + + long Parse(); + + long long GetTimeCodeScale() const; + long long GetDuration() const; // scaled + const char* GetMuxingAppAsUTF8() const; + const char* GetWritingAppAsUTF8() const; + const char* GetTitleAsUTF8() const; + + private: + long long m_timecodeScale; + double m_duration; + char* m_pMuxingAppAsUTF8; + char* m_pWritingAppAsUTF8; + char* m_pTitleAsUTF8; +}; + +class SeekHead { + SeekHead(const SeekHead&); + SeekHead& operator=(const SeekHead&); + + public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + SeekHead(Segment*, long long start, long long size, long long element_start, + long long element_size); + + ~SeekHead(); + + long Parse(); + + struct Entry { + // the SeekHead entry payload + long long id; + long long pos; + + // absolute pos of SeekEntry ID + long long element_start; + + // SeekEntry ID size + size size + payload + long long element_size; + }; + + int GetCount() const; + const Entry* GetEntry(int idx) const; + + struct VoidElement { + // absolute pos of Void ID + long long element_start; + + // ID size + size size + payload size + long long element_size; + }; + + int GetVoidElementCount() const; + const VoidElement* GetVoidElement(int idx) const; + + private: + Entry* m_entries; + int m_entry_count; + + VoidElement* m_void_elements; + int m_void_element_count; + + static bool ParseEntry(IMkvReader*, + long long pos, // payload + long long size, Entry*); +}; + +class Cues; +class CuePoint { + friend class Cues; + + CuePoint(long, long long); + ~CuePoint(); + + CuePoint(const CuePoint&); + CuePoint& operator=(const CuePoint&); + + public: + long long m_element_start; + long long m_element_size; + + void Load(IMkvReader*); + + long long GetTimeCode() const; // absolute but unscaled + long long GetTime(const Segment*) const; // absolute and scaled (ns units) + + struct TrackPosition { + long long m_track; + long long m_pos; // of cluster + long long m_block; + // codec_state //defaults to 0 + // reference = clusters containing req'd referenced blocks + // reftime = timecode of the referenced block + + void Parse(IMkvReader*, long long, long long); + }; + + const TrackPosition* Find(const Track*) const; + + private: + const long m_index; + long long m_timecode; + TrackPosition* m_track_positions; + size_t m_track_positions_count; +}; + +class Cues { + friend class Segment; + + Cues(Segment*, long long start, long long size, long long element_start, + long long element_size); + ~Cues(); + + Cues(const Cues&); + Cues& operator=(const Cues&); + + public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + bool Find( // lower bound of time_ns + long long time_ns, const Track*, const CuePoint*&, + const CuePoint::TrackPosition*&) const; + +#if 0 + bool FindNext( //upper_bound of time_ns + long long time_ns, + const Track*, + const CuePoint*&, + const CuePoint::TrackPosition*&) const; +#endif + + const CuePoint* GetFirst() const; + const CuePoint* GetLast() const; + const CuePoint* GetNext(const CuePoint*) const; + + const BlockEntry* GetBlock(const CuePoint*, + const CuePoint::TrackPosition*) const; + + bool LoadCuePoint() const; + long GetCount() const; // loaded only + // long GetTotal() const; //loaded + preloaded + bool DoneParsing() const; + + private: + void Init() const; + void PreloadCuePoint(long&, long long) const; + + mutable CuePoint** m_cue_points; + mutable long m_count; + mutable long m_preload_count; + mutable long long m_pos; +}; + +class Cluster { + friend class Segment; + + Cluster(const Cluster&); + Cluster& operator=(const Cluster&); + + public: + Segment* const m_pSegment; + + public: + static Cluster* Create(Segment*, + long index, // index in segment + long long off); // offset relative to segment + // long long element_size); + + Cluster(); // EndOfStream + ~Cluster(); + + bool EOS() const; + + long long GetTimeCode() const; // absolute, but not scaled + long long GetTime() const; // absolute, and scaled (nanosecond units) + long long GetFirstTime() const; // time (ns) of first (earliest) block + long long GetLastTime() const; // time (ns) of last (latest) block + + long GetFirst(const BlockEntry*&) const; + long GetLast(const BlockEntry*&) const; + long GetNext(const BlockEntry* curr, const BlockEntry*& next) const; + + const BlockEntry* GetEntry(const Track*, long long ns = -1) const; + const BlockEntry* GetEntry(const CuePoint&, + const CuePoint::TrackPosition&) const; + // const BlockEntry* GetMaxKey(const VideoTrack*) const; + + // static bool HasBlockEntries(const Segment*, long long); + + static long HasBlockEntries(const Segment*, long long idoff, long long& pos, + long& size); + + long GetEntryCount() const; + + long Load(long long& pos, long& size) const; + + long Parse(long long& pos, long& size) const; + long GetEntry(long index, const mkvparser::BlockEntry*&) const; + + protected: + Cluster(Segment*, long index, long long element_start); + // long long element_size); + + public: + const long long m_element_start; + long long GetPosition() const; // offset relative to segment + + long GetIndex() const; + long long GetElementSize() const; + // long long GetPayloadSize() const; + + // long long Unparsed() const; + + private: + long m_index; + mutable long long m_pos; + // mutable long long m_size; + mutable long long m_element_size; + mutable long long m_timecode; + mutable BlockEntry** m_entries; + mutable long m_entries_size; + mutable long m_entries_count; + + long ParseSimpleBlock(long long, long long&, long&); + long ParseBlockGroup(long long, long long&, long&); + + long CreateBlock(long long id, long long pos, long long size, + long long discard_padding); + long CreateBlockGroup(long long start_offset, long long size, + long long discard_padding); + long CreateSimpleBlock(long long, long long); +}; + +class Segment { + friend class Cues; + friend class Track; + friend class VideoTrack; + + Segment(const Segment&); + Segment& operator=(const Segment&); + + private: + Segment(IMkvReader*, long long elem_start, + // long long elem_size, + long long pos, long long size); + + public: + IMkvReader* const m_pReader; + const long long m_element_start; + // const long long m_element_size; + const long long m_start; // posn of segment payload + const long long m_size; // size of segment payload + Cluster m_eos; // TODO: make private? + + static long long CreateInstance(IMkvReader*, long long, Segment*&); + ~Segment(); + + long Load(); // loads headers and all clusters + + // for incremental loading + // long long Unparsed() const; + bool DoneParsing() const; + long long ParseHeaders(); // stops when first cluster is found + // long FindNextCluster(long long& pos, long& size) const; + long LoadCluster(long long& pos, long& size); // load one cluster + long LoadCluster(); + + long ParseNext(const Cluster* pCurr, const Cluster*& pNext, long long& pos, + long& size); + +#if 0 + //This pair parses one cluster, but only changes the state of the + //segment object when the cluster is actually added to the index. + long ParseCluster(long long& cluster_pos, long long& new_pos) const; + bool AddCluster(long long cluster_pos, long long new_pos); +#endif + + const SeekHead* GetSeekHead() const; + const Tracks* GetTracks() const; + const SegmentInfo* GetInfo() const; + const Cues* GetCues() const; + const Chapters* GetChapters() const; + + long long GetDuration() const; + + unsigned long GetCount() const; + const Cluster* GetFirst() const; + const Cluster* GetLast() const; + const Cluster* GetNext(const Cluster*); + + const Cluster* FindCluster(long long time_nanoseconds) const; + // const BlockEntry* Seek(long long time_nanoseconds, const Track*) const; + + const Cluster* FindOrPreloadCluster(long long pos); + + long ParseCues(long long cues_off, // offset relative to start of segment + long long& parse_pos, long& parse_len); + + private: + long long m_pos; // absolute file posn; what has been consumed so far + Cluster* m_pUnknownSize; + + SeekHead* m_pSeekHead; + SegmentInfo* m_pInfo; + Tracks* m_pTracks; + Cues* m_pCues; + Chapters* m_pChapters; + Cluster** m_clusters; + long m_clusterCount; // number of entries for which m_index >= 0 + long m_clusterPreloadCount; // number of entries for which m_index < 0 + long m_clusterSize; // array size + + long DoLoadCluster(long long&, long&); + long DoLoadClusterUnknownSize(long long&, long&); + long DoParseNext(const Cluster*&, long long&, long&); + + void AppendCluster(Cluster*); + void PreloadCluster(Cluster*, ptrdiff_t); + + // void ParseSeekHead(long long pos, long long size); + // void ParseSeekEntry(long long pos, long long size); + // void ParseCues(long long); + + const BlockEntry* GetBlock(const CuePoint&, const CuePoint::TrackPosition&); +}; + +} // end namespace mkvparser + +inline long mkvparser::Segment::LoadCluster() { + long long pos; + long size; + + return LoadCluster(pos, size); +} + +#endif // MKVPARSER_HPP diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/webm/mkvreader.cpp b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/webm/mkvreader.cpp new file mode 100644 index 00000000..eaf9e0a7 --- /dev/null +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/webm/mkvreader.cpp @@ -0,0 +1,132 @@ +// Copyright (c) 2010 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#include "mkvreader.hpp" + +#include + +namespace mkvparser { + +MkvReader::MkvReader() : m_file(NULL), reader_owns_file_(true) {} + +MkvReader::MkvReader(FILE* fp) : m_file(fp), reader_owns_file_(false) { + GetFileSize(); +} + +MkvReader::~MkvReader() { + if (reader_owns_file_) + Close(); + m_file = NULL; +} + +int MkvReader::Open(const char* fileName) { + if (fileName == NULL) + return -1; + + if (m_file) + return -1; + +#ifdef _MSC_VER + const errno_t e = fopen_s(&m_file, fileName, "rb"); + + if (e) + return -1; // error +#else + m_file = fopen(fileName, "rb"); + + if (m_file == NULL) + return -1; +#endif + return !GetFileSize(); +} + +bool MkvReader::GetFileSize() { + if (m_file == NULL) + return false; +#ifdef _MSC_VER + int status = _fseeki64(m_file, 0L, SEEK_END); + + if (status) + return false; // error + + m_length = _ftelli64(m_file); +#else + fseek(m_file, 0L, SEEK_END); + m_length = ftell(m_file); +#endif + assert(m_length >= 0); + + if (m_length < 0) + return false; + +#ifdef _MSC_VER + status = _fseeki64(m_file, 0L, SEEK_SET); + + if (status) + return false; // error +#else + fseek(m_file, 0L, SEEK_SET); +#endif + + return true; +} + +void MkvReader::Close() { + if (m_file != NULL) { + fclose(m_file); + m_file = NULL; + } +} + +int MkvReader::Length(long long* total, long long* available) { + if (m_file == NULL) + return -1; + + if (total) + *total = m_length; + + if (available) + *available = m_length; + + return 0; +} + +int MkvReader::Read(long long offset, long len, unsigned char* buffer) { + if (m_file == NULL) + return -1; + + if (offset < 0) + return -1; + + if (len < 0) + return -1; + + if (len == 0) + return 0; + + if (offset >= m_length) + return -1; + +#ifdef _MSC_VER + const int status = _fseeki64(m_file, offset, SEEK_SET); + + if (status) + return -1; // error +#else + fseek(m_file, offset, SEEK_SET); +#endif + + const size_t size = fread(buffer, 1, len, m_file); + + if (size < size_t(len)) + return -1; // error + + return 0; // success +} + +} // end namespace mkvparser diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/webm/mkvreader.hpp b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/webm/mkvreader.hpp new file mode 100644 index 00000000..82ebad54 --- /dev/null +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/FeatureLib/pfMoviePlayer/webm/mkvreader.hpp @@ -0,0 +1,45 @@ +// Copyright (c) 2010 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#ifndef MKVREADER_HPP +#define MKVREADER_HPP + +#include "mkvparser.hpp" +#include + +namespace mkvparser { + +class MkvReader : public IMkvReader { + public: + MkvReader(); + explicit MkvReader(FILE* fp); + virtual ~MkvReader(); + + int Open(const char*); + void Close(); + + virtual int Read(long long position, long length, unsigned char* buffer); + virtual int Length(long long* total, long long* available); + + private: + MkvReader(const MkvReader&); + MkvReader& operator=(const MkvReader&); + + // Determines the size of the file. This is called either by the constructor + // or by the Open function depending on file ownership. Returns true on + // success. + bool GetFileSize(); + + long long m_length; + FILE* m_file; + bool reader_owns_file_; +}; + +} // end namespace mkvparser + +#endif // MKVREADER_HPP diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plDSoundBuffer.cpp b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plDSoundBuffer.cpp index 804dc4c3..8f90428a 100644 --- a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plDSoundBuffer.cpp +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plDSoundBuffer.cpp @@ -105,21 +105,10 @@ plDSoundBuffer::~plDSoundBuffer() void plDSoundBuffer::IAllocate( UInt32 size, plWAVHeader &bufferDesc, hsBool enable3D, hsBool tryStatic ) { // Create a DSound buffer description - fBufferDesc = TRACKED_NEW DSBUFFERDESC; - fBufferDesc->dwSize = sizeof( DSBUFFERDESC ); - - fBufferDesc->dwBufferBytes = size; - fBufferDesc->dwReserved = 0; - - fBufferDesc->lpwfxFormat = TRACKED_NEW WAVEFORMATEX; - fBufferDesc->lpwfxFormat->cbSize = 0; - fBufferDesc->lpwfxFormat->nAvgBytesPerSec = bufferDesc.fAvgBytesPerSec; - fBufferDesc->lpwfxFormat->nBlockAlign = bufferDesc.fBlockAlign; - fBufferDesc->lpwfxFormat->nChannels = bufferDesc.fNumChannels; - fBufferDesc->lpwfxFormat->nSamplesPerSec = bufferDesc.fNumSamplesPerSec; - fBufferDesc->lpwfxFormat->wBitsPerSample = bufferDesc.fBitsPerSample; - fBufferDesc->lpwfxFormat->wFormatTag = bufferDesc.fFormatTag; - + fBufferDesc = new plWAVHeader; + *fBufferDesc = bufferDesc; + fBufferSize = size; + // Do we want to try EAX? if( plgAudioSys::UsingEAX() ) fEAXSource.Init( this ); @@ -149,14 +138,9 @@ void plDSoundBuffer::IRelease( void ) alGetError(); memset(streamingBuffers, 0, STREAMING_BUFFERS * sizeof(unsigned)); - if( fBufferDesc != nil ) - { - delete fBufferDesc->lpwfxFormat; - fBufferDesc->lpwfxFormat = nil; - } - delete fBufferDesc; fBufferDesc = nil; + fBufferSize = 0; fValid = false; plProfile_Dec( NumAllocated ); @@ -197,7 +181,7 @@ bool plDSoundBuffer::FillBuffer(void *data, unsigned bytes, plWAVHeader *header) source = 0; buffer = 0; - ALenum format = IGetALFormat(fBufferDesc->lpwfxFormat->wBitsPerSample, fBufferDesc->lpwfxFormat->nChannels); + ALenum format = IGetALFormat(fBufferDesc->fBitsPerSample, fBufferDesc->fNumChannels); ALenum error = alGetError(); alGenBuffers(1, &buffer); error = alGetError(); @@ -280,8 +264,8 @@ bool plDSoundBuffer::SetupStreamingSource(plAudioFileReader *stream) return false; } - ALenum format = IGetALFormat(fBufferDesc->lpwfxFormat->wBitsPerSample, fBufferDesc->lpwfxFormat->nChannels); - alBufferData( streamingBuffers[i], format, data, size, fBufferDesc->lpwfxFormat->nSamplesPerSec ); + ALenum format = IGetALFormat(fBufferDesc->fBitsPerSample, fBufferDesc->fNumChannels); + alBufferData( streamingBuffers[i], format, data, size, fBufferDesc->fNumSamplesPerSec ); if( (error = alGetError()) != AL_NO_ERROR ) plStatusLog::AddLineS("audio.log", "alBufferData"); } @@ -346,8 +330,8 @@ bool plDSoundBuffer::SetupStreamingSource(void *data, unsigned bytes) return false; } - ALenum format = IGetALFormat(fBufferDesc->lpwfxFormat->wBitsPerSample, fBufferDesc->lpwfxFormat->nChannels); - alBufferData( streamingBuffers[i], format, bufferData, size, fBufferDesc->lpwfxFormat->nSamplesPerSec ); + ALenum format = IGetALFormat(fBufferDesc->fBitsPerSample, fBufferDesc->fNumChannels); + alBufferData( streamingBuffers[i], format, bufferData, size, fBufferDesc->fNumSamplesPerSec ); if( (error = alGetError()) != AL_NO_ERROR ) plStatusLog::AddLineS("audio.log", "alBufferData"); } @@ -364,7 +348,7 @@ bool plDSoundBuffer::SetupStreamingSource(void *data, unsigned bytes) SetScalarVolume(0); alSourcef(source, AL_ROLLOFF_FACTOR, 0.3048); - alGetError(); + error = alGetError(); if( error != AL_NO_ERROR ) { return false; @@ -381,7 +365,7 @@ bool plDSoundBuffer::SetupStreamingSource(void *data, unsigned bytes) } //============================================================================ -int plDSoundBuffer::BuffersProcessed() +int plDSoundBuffer::BuffersProcessed( void ) { if(alIsSource(source)==AL_FALSE) { @@ -399,7 +383,7 @@ int plDSoundBuffer::BuffersProcessed() } //============================================================================ -int plDSoundBuffer::BuffersQueued() +int plDSoundBuffer::BuffersQueued( void ) { if(alIsSource(source)==AL_FALSE) return 0; ALint queued = 0; @@ -450,8 +434,8 @@ bool plDSoundBuffer::StreamingFillBuffer(plAudioFileReader *stream) { unsigned int size = stream->NumBytesLeft() < STREAM_BUFFER_SIZE ? stream->NumBytesLeft() : STREAM_BUFFER_SIZE; stream->Read(size, data); - ALenum format = IGetALFormat(fBufferDesc->lpwfxFormat->wBitsPerSample, fBufferDesc->lpwfxFormat->nChannels); - alBufferData( bufferId, format, data, size, fBufferDesc->lpwfxFormat->nSamplesPerSec ); + ALenum format = IGetALFormat(fBufferDesc->fBitsPerSample, fBufferDesc->fNumChannels); + alBufferData( bufferId, format, data, size, fBufferDesc->fNumSamplesPerSec ); if( (error = alGetError()) != AL_NO_ERROR ) { plStatusLog::AddLineS("audio.log", "Failed to copy data to sound buffer %d", error); @@ -492,10 +476,9 @@ bool plDSoundBuffer::GetAvailableBufferId(unsigned *bufferId) return true; } -bool plDSoundBuffer::SetupVoiceSource() +bool plDSoundBuffer::SetupVoiceSource( void ) { - ALenum error; - alGetError(); + ALenum error = alGetError(); // Generate AL Buffers alGenBuffers( STREAMING_BUFFERS, streamingBuffers ); @@ -523,13 +506,13 @@ bool plDSoundBuffer::SetupVoiceSource() SetScalarVolume(0); alSourcef(source, AL_ROLLOFF_FACTOR, 0.3048); - alGetError(); + error = alGetError(); if( error != AL_NO_ERROR ) { return false; } alSourcei(source, AL_BUFFER, nil); - alGetError(); + error = alGetError(); //alSourcei(source, AL_PITCH, 0); // dont queue any buffers here @@ -537,7 +520,7 @@ bool plDSoundBuffer::SetupVoiceSource() } //============================================================================ -void plDSoundBuffer::UnQueueVoiceBuffers() +void plDSoundBuffer::UnQueueVoiceBuffers( void ) { unsigned buffersProcessed = BuffersProcessed(); if(buffersProcessed) @@ -566,8 +549,8 @@ bool plDSoundBuffer::VoiceFillBuffer(void *data, unsigned bytes, unsigned buffer ALenum error; unsigned int size = bytes < STREAM_BUFFER_SIZE ? bytes : STREAM_BUFFER_SIZE; - ALenum format = IGetALFormat(fBufferDesc->lpwfxFormat->wBitsPerSample, fBufferDesc->lpwfxFormat->nChannels); - alBufferData( bufferId, format, data, size, fBufferDesc->lpwfxFormat->nSamplesPerSec ); + ALenum format = IGetALFormat(fBufferDesc->fBitsPerSample, fBufferDesc->fNumChannels); + alBufferData( bufferId, format, data, size, fBufferDesc->fNumSamplesPerSec ); if( (error = alGetError()) != AL_NO_ERROR ) { plStatusLog::AddLineS("audio.log", "Failed to copy data to sound buffer %d", error); @@ -635,6 +618,16 @@ void plDSoundBuffer::Play( void ) } +//// Pause //////////////////////////////////////////////////////////////////// + +void plDSoundBuffer::Pause( void ) +{ + if (!source) + return; + alSourcePause(source); + alGetError(); +} + //// Stop //////////////////////////////////////////////////////////////////// void plDSoundBuffer::Stop( void ) @@ -692,7 +685,7 @@ void plDSoundBuffer::SetConeOutsideVolume(int vol) } //============================================================================ -void plDSoundBuffer::Rewind() +void plDSoundBuffer::Rewind( void ) { alSourceRewind(source); alGetError(); @@ -719,17 +712,17 @@ hsBool plDSoundBuffer::IsEAXAccelerated( void ) const UInt32 plDSoundBuffer::BytePosToMSecs( UInt32 bytePos ) const { - return (UInt32)(bytePos * 1000 / (hsScalar)fBufferDesc->lpwfxFormat->nAvgBytesPerSec); + return (UInt32)(bytePos * 1000 / (hsScalar)fBufferDesc->fAvgBytesPerSec); } //// GetBufferBytePos //////////////////////////////////////////////////////// UInt32 plDSoundBuffer::GetBufferBytePos( hsScalar timeInSecs ) const { - hsAssert( fBufferDesc != nil && fBufferDesc->lpwfxFormat != nil, "Nil buffer description when calling GetBufferBytePos()" ); + hsAssert( fBufferDesc != nil, "Nil buffer description when calling GetBufferBytePos()" ); - UInt32 byte = (UInt32)( timeInSecs * (hsScalar)fBufferDesc->lpwfxFormat->nSamplesPerSec ); - byte *= fBufferDesc->lpwfxFormat->nBlockAlign; + UInt32 byte = (UInt32)( timeInSecs * (hsScalar)fBufferDesc->fNumSamplesPerSec ); + byte *= fBufferDesc->fBlockAlign; return byte; } @@ -738,7 +731,7 @@ UInt32 plDSoundBuffer::GetBufferBytePos( hsScalar timeInSecs ) const UInt32 plDSoundBuffer::GetLengthInBytes( void ) const { - return (UInt32)fBufferDesc->dwBufferBytes; + return fBufferSize; } //// SetEAXSettings ////////////////////////////////////////////////////////// @@ -752,7 +745,7 @@ void plDSoundBuffer::SetEAXSettings( plEAXSourceSettings *settings, hsBool forc UInt8 plDSoundBuffer::GetBlockAlign( void ) const { - return ( fBufferDesc != nil && fBufferDesc->lpwfxFormat != nil ) ? fBufferDesc->lpwfxFormat->nBlockAlign : 0; + return ( fBufferDesc != nil ) ? fBufferDesc->fBlockAlign : 0; } //// SetScalarVolume ///////////////////////////////////////////////////////// @@ -769,7 +762,7 @@ void plDSoundBuffer::SetScalarVolume( hsScalar volume ) } } -unsigned plDSoundBuffer::GetByteOffset() +unsigned plDSoundBuffer::GetByteOffset( void ) { ALint bytes; alGetSourcei(source, AL_BYTE_OFFSET, &bytes); @@ -777,7 +770,7 @@ unsigned plDSoundBuffer::GetByteOffset() return bytes; } -float plDSoundBuffer::GetTimeOffsetSec() +float plDSoundBuffer::GetTimeOffsetSec( void ) { float time; alGetSourcef(source, AL_SEC_OFFSET, &time); @@ -796,4 +789,4 @@ void plDSoundBuffer::SetTimeOffsetBytes(unsigned bytes) alSourcef(source, AL_BYTE_OFFSET, bytes); ALenum error = alGetError(); } - \ No newline at end of file + diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plDSoundBuffer.h b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plDSoundBuffer.h index f1d16122..3a3a0eab 100644 --- a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plDSoundBuffer.h +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plDSoundBuffer.h @@ -62,9 +62,6 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com class plWAVHeader; class plAudioFileReader; -typedef struct tWAVEFORMATEX WAVEFORMATEX; -typedef struct _DSBUFFERDESC DSBUFFERDESC; - // Ported to OpenAL from DirectSound May 2006. Idealy the openal sources would be seperate from this class. // OpenAl sound buffer, and source. @@ -74,11 +71,12 @@ public: plDSoundBuffer( UInt32 size, plWAVHeader &bufferDesc, hsBool enable3D, hsBool looping, hsBool tryStatic = false, bool streaming = false ); ~plDSoundBuffer(); - void Play( void ); - void Stop( void ); + void Play(); + void Stop(); + void Pause(); void Rewind() ; - UInt32 GetLengthInBytes( void ) const; + UInt32 GetLengthInBytes() const; void SetScalarVolume( hsScalar volume ); // Sets the volume, but on a range from 0 to 1 unsigned GetSource() { return source; } @@ -93,10 +91,10 @@ public: void SetMinDistance( int dist); void SetMaxDistance( int dist ); - hsBool IsValid( void ) const { return fValid; } - hsBool IsPlaying( void ); - hsBool IsLooping( void ) const { return fLooping; } - hsBool IsEAXAccelerated( void ) const; + hsBool IsValid() const { return fValid; } + hsBool IsPlaying(); + hsBool IsLooping() const { return fLooping; } + hsBool IsEAXAccelerated() const; bool FillBuffer(void *data, unsigned bytes, plWAVHeader *header); @@ -143,7 +141,8 @@ protected: hsTArray fPosNotifys; bool fStreaming; - DSBUFFERDESC* fBufferDesc; + plWAVHeader* fBufferDesc; + UInt32 fBufferSize; unsigned buffer; // used if this is not a streaming buffer unsigned streamingBuffers[STREAMING_BUFFERS]; // used if this is a streaming buffer @@ -161,7 +160,7 @@ protected: hsScalar fPrevVolume; void IAllocate( UInt32 size, plWAVHeader &bufferDesc, hsBool enable3D, hsBool tryStatic ); - void IRelease( void ); + void IRelease(); int IGetALFormat(unsigned bitsPerSample, unsigned int numChannels); }; diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plSound.h b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plSound.h index 654f3652..6b6bfb0a 100644 --- a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plSound.h +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plSound.h @@ -163,19 +163,20 @@ public: fLengthInSecs = len; fVolStart = start; fVolEnd = end; fType = type; fStopWhenDone = false; fFadeSoftVol = false; + fCurrTime = -1.f; } void Read( hsStream *s ); void Write( hsStream *s ); - hsScalar InterpValue( void ); + hsScalar InterpValue(); protected: hsScalar fCurrTime; // -1 if we aren't active, else it's how far we're into the animation }; virtual hsBool LoadSound( hsBool is3D ) = 0; - hsScalar GetVirtualStartTime( void ) const { return (hsScalar)fVirtualStartTime; } + hsScalar GetVirtualStartTime() const { return (hsScalar)fVirtualStartTime; } virtual void Play(); void SynchedPlay( unsigned bytes ); @@ -188,16 +189,16 @@ public: virtual int GetMin() const; virtual int GetMax() const; virtual void SetVolume(const float volume); - virtual float GetVolume(void) const { return fCurrVolume; } + virtual float GetVolume() const { return fCurrVolume; } hsScalar GetMaxVolume() { return fMaxVolume; } virtual hsBool IsPlaying() { return fPlaying; } void SetTime(double t); - virtual double GetTime( void ) { return 0.f; } + virtual double GetTime() { return 0.f; } virtual void Activate(hsBool forcePlay = false); virtual void DeActivate(); virtual void SetLength(double l) { fLength = l; } virtual void SetMuted( hsBool muted ); - virtual hsBool IsMuted( void ) { return fMuted; } + virtual hsBool IsMuted() { return fMuted; } void Disable() { fDistAttenuation = 0; } virtual plSoundMsg* GetStatus(plSoundMsg* pMsg){return NULL;} virtual void SetConeOrientation(hsScalar x, hsScalar y, hsScalar z); @@ -210,16 +211,16 @@ public: virtual void Update(); - plSoundBuffer * GetDataBuffer( void ) const { return (plSoundBuffer *)fDataBufferKey->ObjectIsLoaded(); } - hsScalar QueryCurrVolume( void ) const; // Returns the current volume, attenuated + plSoundBuffer * GetDataBuffer() const { return (plSoundBuffer *)fDataBufferKey->ObjectIsLoaded(); } + hsScalar QueryCurrVolume() const; // Returns the current volume, attenuated - const char * GetFileName( void ) const; + const char * GetFileName() const; virtual double GetLength(); void SetProperty( Property prop, hsBool on ) { if( on ) fProperties |= prop; else fProperties &= ~prop; } hsBool IsPropertySet( Property prop ) const { return ( fProperties & prop ) ? true : false; } - virtual void RefreshVolume( void ); + virtual void RefreshVolume(); virtual void SetStartPos(unsigned bytes) = 0; virtual unsigned GetByteOffset(){return 0;} @@ -228,7 +229,7 @@ public: virtual void AddCallbacks(plSoundMsg* pMsg) = 0; virtual void RemoveCallbacks(plSoundMsg* pMsg) = 0; - virtual UInt8 GetChannelSelect( void ) const { return 0; } // Only defined on Win32Sound right now, should be here tho + virtual UInt8 GetChannelSelect() const { return 0; } // Only defined on Win32Sound right now, should be here tho virtual void Read(hsStream* s, hsResMgr* mgr); virtual void Write(hsStream* s, hsResMgr* mgr); @@ -246,34 +247,34 @@ public: // Type setting and getting, from the Types enum void SetType( UInt8 type ) { fType = type; } - UInt8 GetType( void ) const { return fType; } + UInt8 GetType() const { return fType; } // Priority stuff void SetPriority( UInt8 pri ) { fPriority = pri; } - UInt8 GetPriority( void ) const { return fPriority; } + UInt8 GetPriority() const { return fPriority; } // Visualization virtual plDrawableSpans* CreateProxy(const hsMatrix44& l2w, hsGMaterial* mat, hsTArray& idx, plDrawableSpans* addTo); // Forced loading/unloading (for when the audio system's LOD just doesn't cut it) virtual void ForceLoad( ); - virtual void ForceUnload( void ); + virtual void ForceUnload(); // Note: ONLY THE AUDIOSYS SHOULD CALL THIS. If you're not the audioSys, get lost. static void SetCurrDebugPlate( const plKey soundKey ); - void RegisterOnAudioSys( void ); - void UnregisterOnAudioSys( void ); + void RegisterOnAudioSys(); + void UnregisterOnAudioSys(); // Also only for the audio system - hsScalar GetVolumeRank( void ); - void ForceUnregisterFromAudioSys( void ); + hsScalar GetVolumeRank(); + void ForceUnregisterFromAudioSys(); static void SetLoadOnDemand( hsBool activate ) { fLoadOnDemandFlag = activate; } static void SetLoadFromDiskOnDemand( hsBool activate ) { fLoadFromDiskOnDemand = activate; } - const plEAXSourceSettings &GetEAXSettings( void ) const { return fEAXSettings; } - plEAXSourceSettings &GetEAXSettings( void ) { return fEAXSettings; } + const plEAXSourceSettings &GetEAXSettings() const { return fEAXSettings; } + plEAXSourceSettings &GetEAXSettings() { return fEAXSettings; } virtual StreamType GetStreamType() const { return kNoStream; } virtual void FreeSoundData(); @@ -340,40 +341,40 @@ protected: static hsBool fLoadOnDemandFlag, fLoadFromDiskOnDemand; hsBool fLoading; - void IUpdateDebugPlate( void ); + void IUpdateDebugPlate(); void IPrintDbgMessage( const char *msg, hsBool isErr = false ); - virtual void ISetActualVolume(const float v) = 0; - virtual void IActuallyStop( void ); - virtual hsBool IActuallyPlaying( void ) = 0; - virtual void IActuallyPlay( void ) = 0; - virtual void IFreeBuffers( void ) = 0; + virtual void ISetActualVolume(float v) = 0; + virtual void IActuallyStop(); + virtual hsBool IActuallyPlaying() = 0; + virtual void IActuallyPlay() = 0; + virtual void IFreeBuffers() = 0; //NOTE: if isIncidental is true the entire sound will be loaded. virtual plSoundBuffer::ELoadReturnVal IPreLoadBuffer( hsBool playWhenLoaded, hsBool isIncidental = false ); virtual void ISetActualTime( double t ) = 0; - virtual hsBool IActuallyLoaded( void ) = 0; + virtual hsBool IActuallyLoaded() = 0; virtual void IRefreshEAXSettings( hsBool force = false ) = 0; - virtual hsScalar IGetChannelVolume( void ) const; + virtual hsScalar IGetChannelVolume() const; - void ISynchToStartTime( void ); + void ISynchToStartTime(); void ISynchedPlay( double virtualStartTime ); void IStartFade( plFadeParams *params, hsScalar offsetIntoFade = 0.f ); void IStopFade( hsBool shuttingDown = false, hsBool SetVolEnd = true); - hsBool IWillBeAbleToPlay( void ); + hsBool IWillBeAbleToPlay(); void ISetSoftRegion( plSoftVolume *region ); hsScalar IAttenuateActualVolume( hsScalar volume ) const; void ISetSoftOcclusionRegion( plSoftVolume *region ); // Override to make sure the buffer is available before the base class is called - virtual void IRefreshParams( void ); + virtual void IRefreshParams(); - virtual bool ILoadDataBuffer( void ); - virtual void IUnloadDataBuffer( void ); + virtual bool ILoadDataBuffer(); + virtual void IUnloadDataBuffer(); //virtual void ISetMinDistance( const int m ) = 0; //virtual void ISetMaxDistance( const int m ) = 0; diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plWin32VideoSound.cpp b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plWin32VideoSound.cpp new file mode 100644 index 00000000..44540c05 --- /dev/null +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plWin32VideoSound.cpp @@ -0,0 +1,122 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com + or by snail mail at: + Cyan Worlds, Inc. + 14617 N Newport Hwy + Mead, WA 99021 + +*==LICENSE==*/ + +#include "plWin32VideoSound.h" + +#include "hsResMgr.h" +#include "plDSoundBuffer.h" + +static int uniqueID = 0; +plWin32VideoSound::plWin32VideoSound(const plWAVHeader& header) : plWin32Sound() +{ + fCurrVolume = 1.0f; + fDesiredVol = 1.0f; + fSoftVolume = 1.0f; + fType = kGUISound; + + fWAVHeader = header; + fDSoundBuffer = new plDSoundBuffer(0, fWAVHeader, false, false); + + uniqueID++; + + char keyName[32]; + StrPrintf(keyName, arrsize(keyName), "VoiceSound_%d", uniqueID); + + hsgResMgr::ResMgr()->NewKey(keyName, this, plLocation::kGlobalFixedLoc); +} + +plWin32VideoSound::~plWin32VideoSound() +{ + if (fDSoundBuffer) + delete fDSoundBuffer; +} + +void plWin32VideoSound::Play() +{ + IActuallyPlay(); +} + +void plWin32VideoSound::Pause(bool on) +{ + if (on) + fDSoundBuffer->Pause(); + else if (!fReallyPlaying) + fDSoundBuffer->Play(); + fReallyPlaying = !on; +} + +void plWin32VideoSound::FillSoundBuffer(void* buffer, size_t size) +{ + fDSoundBuffer->FillBuffer(buffer, size, &fWAVHeader); + fDSoundBuffer->SetScalarVolume(1.0f); +} + +void plWin32VideoSound::IDerivedActuallyPlay() +{ + if (!fReallyPlaying) { + fDSoundBuffer->Play(); + fReallyPlaying = true; + } +} + +hsBool plWin32VideoSound::LoadSound(hsBool is3D) +{ + hsAssert(false, "unimplemented cause unnecessary for this class"); + return false; +} + +void plWin32VideoSound::SetStartPos(unsigned bytes) +{ + //do nothing + hsAssert(false, "unimplemented cause unnecessary for this class"); +} + +float plWin32VideoSound::GetActualTimeSec() +{ + hsAssert(false, "unimplemented cause unnecessary for this class"); + return 0; +} + +void plWin32VideoSound::ISetActualTime(double t) +{ + hsAssert(false, "unimplemented cause unnecessary for this class"); +} diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plWin32VideoSound.h b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plWin32VideoSound.h new file mode 100644 index 00000000..6a8a1bb5 --- /dev/null +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plAudio/plWin32VideoSound.h @@ -0,0 +1,67 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com + or by snail mail at: + Cyan Worlds, Inc. + 14617 N Newport Hwy + Mead, WA 99021 + +*==LICENSE==*/ + +#ifndef plWin32VideoSound_h +#define plWin32VideoSound_h + +#include "plWin32Sound.h" + +class plWin32VideoSound : public plWin32Sound +{ +public: + plWin32VideoSound(const plWAVHeader& header); + virtual ~plWin32VideoSound(); + + virtual void Play(); + virtual void Pause(bool on); + void FillSoundBuffer(void* buffer, size_t size); + +protected: + void IDerivedActuallyPlay(); + hsBool LoadSound(hsBool is3D); + void SetStartPos(unsigned bytes); + float GetActualTimeSec(); + void ISetActualTime(double t); + + plWAVHeader fWAVHeader; +}; +#endif diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plResMgr/plLocalization.cpp b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plResMgr/plLocalization.cpp index f30627d4..39d71594 100644 --- a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plResMgr/plLocalization.cpp +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plResMgr/plLocalization.cpp @@ -46,7 +46,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com plLocalization::Language plLocalization::fLanguage = plLocalization::kEnglish; -char* plLocalization::fLangTags[] = +const char* plLocalization::fLangTags[] = { "_eng", // kEnglish "_fre", // kFrench @@ -57,7 +57,23 @@ char* plLocalization::fLangTags[] = }; const int kLangTagLen = 4; -char* plLocalization::fLangNames[] = +std::string fLangCodes_en[] = {"eng", "en"}; +std::string fLangCodes_fr[] = {"fre", "fra", "fr"}; +std::string fLangCodes_de[] = {"ger", "deu", "de"}; +std::string fLangCodes_es[] = {"spa", "es"}; +std::string fLangCodes_it[] = {"ita", "it"}; +std::string fLangCodes_ja[] = {"jpn", "ja"}; +const std::set plLocalization::fLangCodes[] = +{ + std::set(std::begin(fLangCodes_en), std::end(fLangCodes_en)), + std::set(std::begin(fLangCodes_fr), std::end(fLangCodes_fr)), + std::set(std::begin(fLangCodes_de), std::end(fLangCodes_de)), + std::set(std::begin(fLangCodes_es), std::end(fLangCodes_it)), + std::set(std::begin(fLangCodes_it), std::end(fLangCodes_it)), + std::set(std::begin(fLangCodes_ja), std::end(fLangCodes_ja)) +}; + +const char* plLocalization::fLangNames[] = { "English", // kEnglish "French", // kFrench diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plResMgr/plLocalization.h b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plResMgr/plLocalization.h index c6a460bd..f41fd817 100644 --- a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plResMgr/plLocalization.h +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/PubUtilLib/plResMgr/plLocalization.h @@ -43,6 +43,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com #define plLocalization_h_inc #include "hsStlUtils.h" +#include class plLocalization { @@ -75,8 +76,9 @@ public: protected: static Language fLanguage; - static char* fLangTags[kNumLanguages]; - static char* fLangNames[kNumLanguages]; + static const char* fLangTags[kNumLanguages]; + static const std::set fLangCodes[kNumLanguages]; + static const char* fLangNames[kNumLanguages]; static bool fUsesUnicode[kNumLanguages]; static encodingTypes fUnicodeEncoding[kNumLanguages]; @@ -89,7 +91,8 @@ public: static void SetLanguage(Language lang) { fLanguage = lang; } static Language GetLanguage() { return fLanguage; } - static char* GetLanguageName(Language lang) { return fLangNames[lang]; } + static const char* GetLanguageName(Language lang) { return fLangNames[lang]; } + static std::set GetLanguageCodes(Language lang) { return fLangCodes[lang]; } static hsBool UsingUnicode() { return fUsesUnicode[fLanguage]; } static encodingTypes UnicodeEncoding() { return fUnicodeEncoding[fLanguage]; } @@ -130,4 +133,4 @@ public: static std::vector StringToLocal(const std::wstring & localizedText); }; -#endif // plLocalization_h_inc \ No newline at end of file +#endif // plLocalization_h_inc