- re-implemented VP8 support.

Since the decoder cannot handle sound, there's two options:

1: Use the same sounds as the video it replaces.
2: If an identifiable streamable sound with the same base name is found, it will be played along with the video.
Fixes #133
This commit is contained in:
Christoph Oelckers 2020-09-05 11:58:19 +02:00
parent 80cea90854
commit b23424485a
6 changed files with 224 additions and 152 deletions

View File

@ -36,7 +36,7 @@ typedef struct
extern const char *animvpx_read_ivf_header_errmsg[7]; extern const char *animvpx_read_ivf_header_errmsg[7];
int32_t animvpx_read_ivf_header(FileReader & inhandle, animvpx_ivf_header_t *hdr); 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; // non-NULL if codec error? better always check...
const char *errmsg_detail; // may be NULL even if codec error const char *errmsg_detail; // may be NULL even if codec error
@ -74,7 +74,7 @@ typedef struct
int32_t numframes; int32_t numframes;
int32_t sumtimes[3]; int32_t sumtimes[3];
int32_t maxtimes[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); 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); void animvpx_print_stats(const animvpx_codec_ctx *codec);
#endif #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 #endif // !defined ANIM_VPX_H

View File

@ -14,7 +14,6 @@
#include "v_draw.h" #include "v_draw.h"
#include "v_video.h" #include "v_video.h"
#include "texturemanager.h" #include "texturemanager.h"
#include "animtexture.h"
#undef UNUSED #undef UNUSED
#define VPX_CODEC_DISABLE_COMPAT 1 #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); 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 animvpx_read_ivf_header(FileReader & inhandle, animvpx_ivf_header_t *hdr)
{ {
int32_t err; int32_t err;
@ -44,14 +59,14 @@ int32_t animvpx_read_ivf_header(FileReader & inhandle, animvpx_ivf_header_t *hdr
if (err) if (err)
return err; return err;
hdr->hdrlen = B_LITTLE16(hdr->hdrlen); hdr->hdrlen = LittleShort(hdr->hdrlen);
hdr->width = B_LITTLE16(hdr->width); hdr->width = LittleShort(hdr->width);
hdr->height = B_LITTLE16(hdr->height); hdr->height = LittleShort(hdr->height);
hdr->fpsnumer = B_LITTLE32(hdr->fpsnumer); hdr->fpsnumer = LittleLong(hdr->fpsnumer);
hdr->fpsdenom = B_LITTLE32(hdr->fpsdenom); 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() // the rest is based on vpxdec.c --> file_is_ivf()
@ -338,89 +353,4 @@ read_ivf_frame:
return 0; 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<AnimTexture*>(vpxtex[which]->GetTexture())->SetFrameSize(AnimTexture::YUV, codec->width, codec->height);
static_cast<AnimTexture*>(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 #endif

View File

@ -44,19 +44,44 @@
void AnimTexture::SetFrameSize(int format, int width, int height) void AnimTexture::SetFrameSize(int format, int width, int height)
{ {
pixelformat = format;
FTexture::SetSize(width, height); FTexture::SetSize(width, height);
Image.Resize(width * height * (format == Paletted ? 1 : 3)); Image.Resize(width * height * (format == Paletted ? 1 : 3));
memset(Image.Data(), 0, Image.Size()); memset(Image.Data(), 0, Image.Size());
CleanHardwareTextures(); CleanHardwareTextures();
pixelformat = format;
} }
void AnimTexture::SetFrame(const uint8_t* palette, const void* data_) void AnimTexture::SetFrame(const uint8_t* palette, const void* data_)
{ {
if (palette) memcpy(Palette, palette, 768); 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(); CleanHardwareTextures();
pixelformat = Paletted;
} }
//=========================================================================== //===========================================================================
@ -85,40 +110,18 @@ FBitmap AnimTexture::GetBgraBitmap(const PalEntry* remap, int* trans)
dpix[p + 3] = 255; dpix[p + 3] = 255;
} }
} }
else if (pixelformat == RGB) else if (pixelformat == RGB || pixelformat == YUV)
{ {
for (int i = 0; i < Width * Height; i++) for (int i = 0; i < Width * Height; i++)
{ {
int p = i * 4; int p = i * 4;
dpix[p + 0] = spix[p + 2]; int q = i * 3;
dpix[p + 1] = spix[p + 1]; dpix[p + 0] = spix[q + 2];
dpix[p + 2] = spix[p]; dpix[p + 1] = spix[q + 1];
dpix[p + 2] = spix[q];
dpix[p + 3] = 255; 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; return bmp;
} }

View File

@ -95,13 +95,13 @@ FString MusicFileExists(const char* fn)
return FString(); 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); FString name = StripExtension(fn);
int l = fileSystem.FindFileWithExtensions(name, knownMusicExts, countof(knownMusicExts)); 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); return fileSystem.CheckNumForFullName(fn, true, ns_music);
} }
@ -131,20 +131,20 @@ FileReader OpenMusic(const char* musicname)
} }
if (!reader.isOpen()) if (!reader.isOpen())
{ {
int lumpnum = LookupMusicLump(musicname); int lumpnum = LookupMusic(musicname);
if (mus_extendedlookup && lumpnum >= 0) 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. // 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 rfn = fileSystem.GetResourceFileName(fileSystem.GetFileContainer(lumpnum));
auto rfbase = ExtractFileBase(rfn); auto rfbase = ExtractFileBase(rfn);
FStringf aliasMusicname("music/%s/%s", rfbase.GetChars(), musicname); FStringf aliasMusicname("music/%s/%s", rfbase.GetChars(), musicname);
lumpnum = LookupMusicLump(aliasMusicname); lumpnum = LookupMusic(aliasMusicname);
} }
if (lumpnum == -1) if (lumpnum == -1)
{ {
// Always look in the 'music' subfolder as well. This gets used by multiple setups to store ripped CD tracks. // Always look in the 'music' subfolder as well. This gets used by multiple setups to store ripped CD tracks.
FStringf aliasMusicname("music/%s", musicname); FStringf aliasMusicname("music/%s", musicname);
lumpnum = LookupMusicLump(aliasMusicname); lumpnum = LookupMusic(aliasMusicname);
} }
if (lumpnum == -1 && (g_gameType & GAMEFLAG_SW)) 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. // 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); FName* aliasp = MusicAliases.CheckKey(musicname);
@ -270,7 +270,7 @@ void Mus_InitMusic()
I_InitMusic(); I_InitMusic();
static MusicCallbacks mus_cb = static MusicCallbacks mus_cb =
{ {
LookupMusic, LookupMusicCB,
OpenMusic OpenMusic
}; };
S_SetMusicCallbacks(&mus_cb); S_SetMusicCallbacks(&mus_cb);

View File

@ -18,3 +18,4 @@ void Mus_ResumeSaved();
FString G_SetupFilenameBasedMusic(const char* fileName, const char *defaultfn); FString G_SetupFilenameBasedMusic(const char* fileName, const char *defaultfn);
class FSerializer; class FSerializer;
void Mus_Serialize(FSerializer& arc); void Mus_Serialize(FSerializer& arc);
int LookupMusic(const char* fn, bool onlyextended = false);

View File

@ -48,6 +48,8 @@
#include "SmackerDecoder.h" #include "SmackerDecoder.h"
#include "movie/playmve.h" #include "movie/playmve.h"
#include "gamecontrol.h" #include "gamecontrol.h"
#include "animvpx.h"
#include "raze_music.h"
IMPLEMENT_CLASS(DScreenJob, true, false) IMPLEMENT_CLASS(DScreenJob, true, false)
@ -134,7 +136,7 @@ public:
if (curframe > 4 && currentclock > frametime + 60) 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(); soundEngine->StopAllChannels();
return -1; 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(); 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()) if (!fr.isOpen())
{ {
int nLen = strlen(filename); int nLen = strlen(filename);
@ -349,7 +490,7 @@ DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* fra
} }
if (!fr.isOpen()) if (!fr.isOpen())
{ {
Printf("%s: Unable to open video\n", filename); Printf(PRINT_BOLD, "%s: Unable to open video\n", filename);
return nothing(); return nothing();
} }
} }
@ -363,7 +504,7 @@ DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* fra
auto anm = Create<DAnmPlayer>(fr, ans, frameticks, nosoundcutoff); auto anm = Create<DAnmPlayer>(fr, ans, frameticks, nosoundcutoff);
if (!anm->isvalid()) if (!anm->isvalid())
{ {
Printf("%s: invalid ANM file.\n", filename); Printf(PRINT_BOLD, "%s: invalid ANM file.\n", filename);
anm->Destroy(); anm->Destroy();
return nothing(); return nothing();
} }
@ -375,7 +516,7 @@ DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* fra
auto anm = Create<DSmkPlayer>(filename, ans, true); // Fixme: Handle Blood's video scaling behavior more intelligently. auto anm = Create<DSmkPlayer>(filename, ans, true); // Fixme: Handle Blood's video scaling behavior more intelligently.
if (!anm->isvalid()) if (!anm->isvalid())
{ {
Printf("%s: invalid SMK file.\n", filename); Printf(PRINT_BOLD, "%s: invalid SMK file.\n", filename);
anm->Destroy(); anm->Destroy();
return nothing(); return nothing();
} }
@ -391,10 +532,21 @@ DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* fra
} }
return anm; return anm;
} }
else if (!memcmp(id, "DKIF\0\0 \0VP80", 12))
{
auto anm = Create<DVpxPlayer>(fr, ans, frameticks? frameticks[1] : 0 );
if (!anm->isvalid())
{
anm->Destroy();
return nothing();
}
anm->soundtrack = LookupMusic(filename, true);
return anm;
}
// add more formats here. // add more formats here.
else else
{ {
Printf("%s: Unknown video format\n", filename); Printf(PRINT_BOLD, "%s: Unknown video format\n", filename);
} }
return nothing(); return nothing();
} }