diff --git a/source/build/include/animvpx.h b/source/build/include/animvpx.h index b6fc2cbbd..fcddb398e 100644 --- a/source/build/include/animvpx.h +++ b/source/build/include/animvpx.h @@ -36,7 +36,7 @@ typedef struct extern const char *animvpx_read_ivf_header_errmsg[7]; int32_t animvpx_read_ivf_header(FileReader & inhandle, animvpx_ivf_header_t *hdr); -typedef struct +struct animvpx_codec_ctx { const char *errmsg; // non-NULL if codec error? better always check... const char *errmsg_detail; // may be NULL even if codec error @@ -74,7 +74,7 @@ typedef struct int32_t numframes; int32_t sumtimes[3]; int32_t maxtimes[3]; -} animvpx_codec_ctx; +}; int32_t animvpx_init_codec(const animvpx_ivf_header_t *info, FileReader & inhandle, animvpx_codec_ctx *codec); @@ -90,19 +90,5 @@ int32_t animvpx_render_frame(animvpx_codec_ctx *codec, double animvpx_aspect); void animvpx_print_stats(const animvpx_codec_ctx *codec); #endif -static inline int32_t animvpx_check_header(const animvpx_ivf_header_t *hdr) -{ - if (memcmp(hdr->magic,"DKIF",4)) - return 2; // "not an IVF file" - - if (hdr->version != 0) - return 3; // "unrecognized IVF version" - - // fourcc is left as-is - if (memcmp(hdr->fourcc, "VP80", 4)) - return 4; // "only VP8 supported" - - return 0; -} #endif // !defined ANIM_VPX_H diff --git a/source/build/src/animvpx.cpp b/source/build/src/animvpx.cpp index 3320d1dd4..293a66bc5 100644 --- a/source/build/src/animvpx.cpp +++ b/source/build/src/animvpx.cpp @@ -14,7 +14,6 @@ #include "v_draw.h" #include "v_video.h" #include "texturemanager.h" -#include "animtexture.h" #undef UNUSED #define VPX_CODEC_DISABLE_COMPAT 1 @@ -33,6 +32,22 @@ const char *animvpx_read_ivf_header_errmsg[] = { static_assert(sizeof(animvpx_ivf_header_t) == 32); +inline int32_t animvpx_check_header(const animvpx_ivf_header_t* hdr) +{ + if (memcmp(hdr->magic, "DKIF", 4)) + return 2; // "not an IVF file" + + if (hdr->version != 0) + return 3; // "unrecognized IVF version" + + // fourcc is left as-is + if (memcmp(hdr->fourcc, "VP80", 4)) + return 4; // "only VP8 supported" + + return 0; +} + + int32_t animvpx_read_ivf_header(FileReader & inhandle, animvpx_ivf_header_t *hdr) { int32_t err; @@ -44,14 +59,14 @@ int32_t animvpx_read_ivf_header(FileReader & inhandle, animvpx_ivf_header_t *hdr if (err) return err; - hdr->hdrlen = B_LITTLE16(hdr->hdrlen); + hdr->hdrlen = LittleShort(hdr->hdrlen); - hdr->width = B_LITTLE16(hdr->width); - hdr->height = B_LITTLE16(hdr->height); - hdr->fpsnumer = B_LITTLE32(hdr->fpsnumer); - hdr->fpsdenom = B_LITTLE32(hdr->fpsdenom); + hdr->width = LittleShort(hdr->width); + hdr->height = LittleShort(hdr->height); + hdr->fpsnumer = LittleLong(hdr->fpsnumer); + hdr->fpsdenom = LittleLong(hdr->fpsdenom); - hdr->numframes = B_LITTLE32(hdr->numframes); + hdr->numframes = LittleLong(hdr->numframes); // the rest is based on vpxdec.c --> file_is_ivf() @@ -338,89 +353,4 @@ read_ivf_frame: return 0; } - -/////////////// DRAWING! /////////////// -static int sampler; -static FGameTexture* vpxtex[2]; -static int which; - -void animvpx_setup_glstate(int32_t animvpx_flags) -{ - ////////// GL STATE ////////// - vpxtex[0] = TexMan.FindGameTexture("AnimTextureFrame1", ETextureType::Override); - vpxtex[1] = TexMan.FindGameTexture("AnimTextureFrame2", ETextureType::Override); - - sampler = CLAMP_XY; - GLInterface.ClearScreen(0, true); -} - -void animvpx_restore_glstate(void) -{ - vpxtex[0]->CleanHardwareData(); - vpxtex[0] = nullptr; - vpxtex[1]->CleanHardwareData(); - vpxtex[1] = nullptr; -} - -int32_t animvpx_render_frame(animvpx_codec_ctx *codec, double animvpx_aspect) -{ - int32_t t = I_msTime(); - - if (codec->initstate <= 0) // not inited or error - return 1; - - if (codec->pic == NULL) - return 2; // shouldn't happen - - which ^= 1; - static_cast(vpxtex[which]->GetTexture())->SetFrameSize(AnimTexture::YUV, codec->width, codec->height); - static_cast(vpxtex[which]->GetTexture())->SetFrame(nullptr, codec->pic); - - float vid_wbyh = ((float)codec->width)/codec->height; - if (animvpx_aspect > 0) - vid_wbyh = animvpx_aspect; - float scr_wbyh = ((float)xdim)/ydim; - - float x=1.0, y=1.0; -#if 1 - // aspect correction by pillarboxing/letterboxing - // TODO: fullscreen? can't assume square pixels there - if (vid_wbyh != scr_wbyh) - { - if (vid_wbyh < scr_wbyh) - x = vid_wbyh/scr_wbyh; - else - y = scr_wbyh/vid_wbyh; - } -#endif - - x *= screen->GetWidth() / 2; - y *= screen->GetHeight() / 2; - DrawTexture(twod, vpxtex[which], screen->GetWidth() / 2 - int(x), screen->GetHeight()/2 - int(y), DTA_DestWidth, 2*int(x), DTA_DestHeight, 2*int(y), - DTA_Masked, false, DTA_KeepRatio, true, DTA_LegacyRenderStyle, STYLE_Normal, TAG_DONE); - - t = I_msTime()-t; - codec->sumtimes[2] += t; - codec->maxtimes[2] = max(codec->maxtimes[2], t); - codec->numframes++; - - return 0; -} - -void animvpx_print_stats(const animvpx_codec_ctx *codec) -{ - if (codec->numframes != 0) - { - const int32_t *s = codec->sumtimes; - const int32_t *m = codec->maxtimes; - int32_t n = codec->numframes; - - Printf("VP8 timing stats (mean, max) [ms] for %d frames:\n" - " read and decode frame: %.02f, %d\n" - " 3 planes -> packed conversion: %.02f, %d\n" - " upload and display: %.02f, %d\n", - n, (double)s[0]/n, m[0], (double)s[1]/n, m[1], (double)s[2]/n, m[2]); - } -} - #endif diff --git a/source/common/textures/animtexture.cpp b/source/common/textures/animtexture.cpp index c54cd5d81..6a96365a8 100644 --- a/source/common/textures/animtexture.cpp +++ b/source/common/textures/animtexture.cpp @@ -44,19 +44,44 @@ void AnimTexture::SetFrameSize(int format, int width, int height) { + pixelformat = format; FTexture::SetSize(width, height); Image.Resize(width * height * (format == Paletted ? 1 : 3)); memset(Image.Data(), 0, Image.Size()); CleanHardwareTextures(); - pixelformat = format; } void AnimTexture::SetFrame(const uint8_t* palette, const void* data_) { if (palette) memcpy(Palette, palette, 768); - if (data_) memcpy(Image.Data(), data_, Width * Height * (pixelformat == Paletted ? 1 : 3)); + if (data_) + { + if (pixelformat == YUV) + { + auto spix = (const uint8_t*)data_; + auto dpix = Image.Data(); + for (int i = 0; i < Width * Height; i++) + { + int p = i * 4; + int q = i * 3; + float y = spix[p] * (1 / 255.f); + float u = spix[p + 1] * (1 / 255.f) - 0.5f; + float v = spix[p + 2] * (1 / 255.f) - 0.5f; + + y = 1.1643f * (y - 0.0625f); + + float r = y + 1.5958f * v; + float g = y - 0.39173f * u - 0.81290f * v; + float b = y + 2.017f * u; + + dpix[q + 0] = (uint8_t)(clamp(r, 0.f, 1.f) * 255); + dpix[q + 1] = (uint8_t)(clamp(g, 0.f, 1.f) * 255); + dpix[q + 2] = (uint8_t)(clamp(b, 0.f, 1.f) * 255); + } + } + else memcpy(Image.Data(), data_, Width * Height * (pixelformat == Paletted ? 1 : 3)); + } CleanHardwareTextures(); - pixelformat = Paletted; } //=========================================================================== @@ -85,40 +110,18 @@ FBitmap AnimTexture::GetBgraBitmap(const PalEntry* remap, int* trans) dpix[p + 3] = 255; } } - else if (pixelformat == RGB) + else if (pixelformat == RGB || pixelformat == YUV) { for (int i = 0; i < Width * Height; i++) { int p = i * 4; - dpix[p + 0] = spix[p + 2]; - dpix[p + 1] = spix[p + 1]; - dpix[p + 2] = spix[p]; + int q = i * 3; + dpix[p + 0] = spix[q + 2]; + dpix[p + 1] = spix[q + 1]; + dpix[p + 2] = spix[q]; dpix[p + 3] = 255; } } - else if (pixelformat == YUV) - { - for (int i = 0; i < Width * Height; i++) - { - int p = i * 4; - float y = spix[p] * (1 / 255.f); - float u = spix[p + 1] * (1 / 255.f) - 0.5f; - float v = spix[p + 2] * (1 / 255.f) - 0.5f; - - y = 1.1643f * (y - 0.0625f); - - float r = y + 1.5958f * v; - float g = y - 0.39173f * u - 0.81290f * v; - float b = y + 2.017f * u; - - dpix[p + 0] = (uint8_t)(clamp(b, 0.f, 1.f) * 255); - dpix[p + 1] = (uint8_t)(clamp(g, 0.f, 1.f) * 255); - dpix[p + 2] = (uint8_t)(clamp(r, 0.f, 1.f) * 255); - dpix[p + 3] = 255; - } - return bmp; - - } return bmp; } diff --git a/source/core/raze_music.cpp b/source/core/raze_music.cpp index 02373c1f2..2a6fd69f6 100644 --- a/source/core/raze_music.cpp +++ b/source/core/raze_music.cpp @@ -95,13 +95,13 @@ FString MusicFileExists(const char* fn) return FString(); } -int LookupMusicLump(const char* fn) +int LookupMusic(const char* fn, bool onlyextended) { - if (mus_extendedlookup) + if (mus_extendedlookup || onlyextended) { FString name = StripExtension(fn); int l = fileSystem.FindFileWithExtensions(name, knownMusicExts, countof(knownMusicExts)); - if (l >= 0) return l; + if (l >= 0 || onlyextended) return l; } return fileSystem.CheckNumForFullName(fn, true, ns_music); } @@ -131,20 +131,20 @@ FileReader OpenMusic(const char* musicname) } if (!reader.isOpen()) { - int lumpnum = LookupMusicLump(musicname); + int lumpnum = LookupMusic(musicname); if (mus_extendedlookup && lumpnum >= 0) { // EDuke also looks in a subfolder named after the main game resource. Do this as well if extended lookup is active. auto rfn = fileSystem.GetResourceFileName(fileSystem.GetFileContainer(lumpnum)); auto rfbase = ExtractFileBase(rfn); FStringf aliasMusicname("music/%s/%s", rfbase.GetChars(), musicname); - lumpnum = LookupMusicLump(aliasMusicname); + lumpnum = LookupMusic(aliasMusicname); } if (lumpnum == -1) { // Always look in the 'music' subfolder as well. This gets used by multiple setups to store ripped CD tracks. FStringf aliasMusicname("music/%s", musicname); - lumpnum = LookupMusicLump(aliasMusicname); + lumpnum = LookupMusic(aliasMusicname); } if (lumpnum == -1 && (g_gameType & GAMEFLAG_SW)) { @@ -170,7 +170,7 @@ FileReader OpenMusic(const char* musicname) } -static FString LookupMusic(const char* musicname, int& order) +static FString LookupMusicCB(const char* musicname, int& order) { // Now perform music aliasing. This also needs to be done before checking identities because multiple names can map to the same song. FName* aliasp = MusicAliases.CheckKey(musicname); @@ -270,7 +270,7 @@ void Mus_InitMusic() I_InitMusic(); static MusicCallbacks mus_cb = { - LookupMusic, + LookupMusicCB, OpenMusic }; S_SetMusicCallbacks(&mus_cb); diff --git a/source/core/raze_music.h b/source/core/raze_music.h index fd968aefd..a9051d650 100644 --- a/source/core/raze_music.h +++ b/source/core/raze_music.h @@ -18,3 +18,4 @@ void Mus_ResumeSaved(); FString G_SetupFilenameBasedMusic(const char* fileName, const char *defaultfn); class FSerializer; void Mus_Serialize(FSerializer& arc); +int LookupMusic(const char* fn, bool onlyextended = false); diff --git a/source/core/screenjob.cpp b/source/core/screenjob.cpp index 9ced2bd7e..6a9765b64 100644 --- a/source/core/screenjob.cpp +++ b/source/core/screenjob.cpp @@ -48,6 +48,8 @@ #include "SmackerDecoder.h" #include "movie/playmve.h" #include "gamecontrol.h" +#include "animvpx.h" +#include "raze_music.h" IMPLEMENT_CLASS(DScreenJob, true, false) @@ -134,7 +136,7 @@ public: if (curframe > 4 && currentclock > frametime + 60) { - Printf("WARNING: slowdown in video playback, aborting\n"); + Printf(PRINT_BOLD, "WARNING: slowdown in video playback, aborting\n"); soundEngine->StopAllChannels(); return -1; } @@ -226,6 +228,136 @@ public: } }; +//--------------------------------------------------------------------------- +// +// +// +//--------------------------------------------------------------------------- + +class DVpxPlayer : public DScreenJob +{ + bool failed = false; + FileReader fr; + AnimTextures animtex; + animvpx_codec_ctx codec; + const AnimSound* animSnd; + + uint32_t convnumer; + uint32_t convdenom; + + uint32_t msecsperframe; + uint64_t nextframetime; + + int framenum = 0; + int lastsoundframe = -1; +public: + int soundtrack = -1; + + +public: + bool isvalid() { return !failed; } + + DVpxPlayer(FileReader& fr_, const AnimSound* animSnd_, int origframedelay) + { + fr = std::move(fr_); + animSnd = animSnd_; + + animvpx_ivf_header_t info; + + int err = animvpx_read_ivf_header(fr, &info); + + if (err) + { + Printf(PRINT_BOLD, "Failed reading IVF file: %s\n", animvpx_read_ivf_header_errmsg[err]); + failed = true; + } + + if (animvpx_init_codec(&info, fr, &codec)) + { + Printf(PRINT_BOLD, "Error initializing VPX codec.\n"); + failed = true; + } + + convnumer = 120 * info.fpsdenom; + convdenom = info.fpsnumer * origframedelay; + + msecsperframe = scale(info.fpsdenom, 1000, info.fpsnumer); + nextframetime = 0; + } + + //--------------------------------------------------------------------------- + // + // + // + //--------------------------------------------------------------------------- + + + int Frame(uint64_t clock, bool skiprequest) override + { + if (clock == 0) + { + if (soundtrack > 0) + { + Mus_Play(nullptr, fileSystem.GetFileFullName(soundtrack, false), false); + } + animtex.SetSize(AnimTexture::YUV, codec.width, codec.height); + } + bool stop = false; + if (clock > nextframetime) + { + nextframetime += 1'000'000 * msecsperframe; + + uint8_t* pic; + int i = animvpx_nextpic(&codec, &pic); + if (i) + { + Printf(PRINT_BOLD, "Failed getting next pic: %s\n", animvpx_nextpic_errmsg[i]); + if (codec.errmsg) + { + Printf(PRINT_BOLD, " %s\n", codec.errmsg); + if (codec.errmsg_detail) + Printf(PRINT_BOLD, " detail: %s\n", codec.errmsg_detail); + } + stop = true; + } + if (!pic) stop = true; + + if (!stop) + { + animtex.SetFrame(nullptr, pic); + } + + framenum++; + + int soundframe = convdenom ? scale(framenum, convnumer, convdenom) : framenum; + if (soundframe > lastsoundframe) + { + if (animSnd && soundtrack == -1) for (int i = 0; animSnd[i].framenum >= 0; i++) + { + if (animSnd[i].framenum == soundframe) + { + int sound = animSnd[i].soundnum; + if (sound == -1) + soundEngine->StopAllChannels(); + else if (SoundEnabled()) + soundEngine->StartSound(SOURCE_None, nullptr, nullptr, CHAN_AUTO, CHANF_UI, sound, 1.f, ATTN_NONE); + } + } + lastsoundframe = soundframe; + } + } + DrawTexture(twod, animtex.GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit, TAG_DONE); + if (stop || skiprequest) Mus_Stop(); + if (stop) return 0; + return skiprequest ? -1 : 1; + } + + void OnDestroy() override + { + animvpx_uninit_codec(&codec); + } +}; + //--------------------------------------------------------------------------- // // @@ -337,7 +469,16 @@ DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* fra { return nothing(); } - auto fr = fileSystem.OpenFileReader(filename); + FileReader fr; + // first try as .ivf - but only if sounds are provided - the decoder is video only. + if (ans) + { + auto fn = StripExtension(filename); + DefaultExtension(fn, ".ivf"); + fr = fileSystem.OpenFileReader(fn); + } + + if (!fr.isOpen()) fr = fileSystem.OpenFileReader(filename); if (!fr.isOpen()) { int nLen = strlen(filename); @@ -349,7 +490,7 @@ DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* fra } if (!fr.isOpen()) { - Printf("%s: Unable to open video\n", filename); + Printf(PRINT_BOLD, "%s: Unable to open video\n", filename); return nothing(); } } @@ -363,7 +504,7 @@ DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* fra auto anm = Create(fr, ans, frameticks, nosoundcutoff); if (!anm->isvalid()) { - Printf("%s: invalid ANM file.\n", filename); + Printf(PRINT_BOLD, "%s: invalid ANM file.\n", filename); anm->Destroy(); return nothing(); } @@ -375,7 +516,7 @@ DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* fra auto anm = Create(filename, ans, true); // Fixme: Handle Blood's video scaling behavior more intelligently. if (!anm->isvalid()) { - Printf("%s: invalid SMK file.\n", filename); + Printf(PRINT_BOLD, "%s: invalid SMK file.\n", filename); anm->Destroy(); return nothing(); } @@ -391,10 +532,21 @@ DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* fra } return anm; } + else if (!memcmp(id, "DKIF\0\0 \0VP80", 12)) + { + auto anm = Create(fr, ans, frameticks? frameticks[1] : 0 ); + if (!anm->isvalid()) + { + anm->Destroy(); + return nothing(); + } + anm->soundtrack = LookupMusic(filename, true); + return anm; + } // add more formats here. else { - Printf("%s: Unknown video format\n", filename); + Printf(PRINT_BOLD, "%s: Unknown video format\n", filename); } return nothing(); }