From 41a2a63efd831f2ae60c595d06a4d268831b418c Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Thu, 10 Sep 2020 17:54:27 +0200 Subject: [PATCH] - moved the VP8 decoding loop into the movie player class and got rid of animvpx. This allowed significant simplification of code data and many of the error checks could also be simplified because this player doesn't really need it all. Also use nanoseconds to count frame delays, not milliseconds, as milliseconds can cause timing anomalies with common frame rates very easily. --- source/CMakeLists.txt | 1 - source/build/include/animvpx.h | 94 --------- source/build/src/animvpx.cpp | 361 --------------------------------- source/core/screenjob.cpp | 196 ++++++++++++++---- 4 files changed, 162 insertions(+), 490 deletions(-) delete mode 100644 source/build/include/animvpx.h delete mode 100644 source/build/src/animvpx.cpp diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 4bc01a697..13e8e2fb9 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -767,7 +767,6 @@ set (PCH_SOURCES thirdparty/src/md4.cpp # Todo: Split out the license-safe code from this. - build/src/animvpx.cpp build/src/clip.cpp build/src/defs.cpp build/src/engine.cpp diff --git a/source/build/include/animvpx.h b/source/build/include/animvpx.h deleted file mode 100644 index fcddb398e..000000000 --- a/source/build/include/animvpx.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef USE_OPENGL -# error "VP8 support requires OpenGL" -#endif - -#ifndef ANIM_VPX_H -#define ANIM_VPX_H - -#define VPX_CODEC_DISABLE_COMPAT 1 -#ifndef ANIMVPX_STANDALONE -# include -//#include -#endif - -// IVF format: http://wiki.multimedia.cx/index.php?title=IVF -#pragma pack(push,1) -typedef struct -{ - char magic[4]; - uint16_t version; - uint16_t hdrlen; - - char fourcc[4]; - uint16_t width; - uint16_t height; - - uint32_t fpsnumer; - uint32_t fpsdenom; - - uint32_t numframes; - uint32_t unused_; -} animvpx_ivf_header_t; -#pragma pack(pop) - -#ifndef ANIMVPX_STANDALONE - -extern const char *animvpx_read_ivf_header_errmsg[7]; -int32_t animvpx_read_ivf_header(FileReader & inhandle, animvpx_ivf_header_t *hdr); - -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 - - uint16_t width, height; - uint8_t *pic; // lines of [Y U V 0], calloc'ed on init - - // VVV everything that follows should be considered private! VVV - - FileReader *inhandle; // the kread() file handle - - // state of this struct: - // 0: uninited (either not yet or already) - // 1: codec init OK - // -1: error while initing - // -2: error while uniniting - int32_t initstate; - - // decoder state: - // 0: first time / begin - // 1: have more frames - // 2: reached EOF - // -1: unspecified error - // -2: decoder error - int32_t decstate; - - uint32_t compbuflen; - uint32_t compbufallocsiz; - uint8_t *compbuf; // compressed data buffer (one IVF/VP8 frame) - - vpx_codec_ctx_t codec; - vpx_codec_iter_t iter; - - // statistics - int32_t numframes; - int32_t sumtimes[3]; - int32_t maxtimes[3]; -}; - - -int32_t animvpx_init_codec(const animvpx_ivf_header_t *info, FileReader & inhandle, animvpx_codec_ctx *codec); -int32_t animvpx_uninit_codec(animvpx_codec_ctx *codec); - -extern const char *animvpx_nextpic_errmsg[8]; -int32_t animvpx_nextpic(animvpx_codec_ctx *codec, uint8_t **pic); - -void animvpx_setup_glstate(int32_t animvpx_flags); -void animvpx_restore_glstate(void); -int32_t animvpx_render_frame(animvpx_codec_ctx *codec, double animvpx_aspect); - -void animvpx_print_stats(const animvpx_codec_ctx *codec); -#endif - - -#endif // !defined ANIM_VPX_H diff --git a/source/build/src/animvpx.cpp b/source/build/src/animvpx.cpp deleted file mode 100644 index 41285647e..000000000 --- a/source/build/src/animvpx.cpp +++ /dev/null @@ -1,361 +0,0 @@ -/* ANM file replacement with VP8 video */ - -#ifdef USE_LIBVPX - -#include "compat.h" - -#include "compat.h" -#include "build.h" -#include "printf.h" -#include "matrix.h" -#include "../../glbackend/glbackend.h" -#include "textures.h" -#include "bitmap.h" -#include "v_draw.h" -#include "v_video.h" -#include "texturemanager.h" - -#undef UNUSED -#define VPX_CODEC_DISABLE_COMPAT 1 -#include -#include -#include "animvpx.h" - -struct vec2u_t -{ - uint32_t x, y; -} ; - -const char *animvpx_read_ivf_header_errmsg[] = { - "All OK", - "couldn't read 32-byte IVF header", - "magic mismatch, not an IVF file", - "unrecognized IVF version, expected 0", - "only VP8 video stream supported", - "invalid framerate numerator or denominator after correction, must not be 0", -}; - -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; - - if (inhandle.Read(hdr, sizeof(animvpx_ivf_header_t)) != sizeof(animvpx_ivf_header_t)) - return 1; // "couldn't read header" - - err = animvpx_check_header(hdr); - if (err) - return err; - - hdr->hdrlen = LittleShort(hdr->hdrlen); - - hdr->width = LittleShort(hdr->width); - hdr->height = LittleShort(hdr->height); - hdr->fpsnumer = LittleLong(hdr->fpsnumer); - hdr->fpsdenom = LittleLong(hdr->fpsdenom); - - hdr->numframes = LittleLong(hdr->numframes); - - // the rest is based on vpxdec.c --> file_is_ivf() - - if (hdr->fpsnumer < 1000) - { - // NOTE: We got rid of the 1/(2*fps) correction from libvpx's vpxdec.c, - // users are encouraged to use the "ivfrate" utility instead - - if (hdr->fpsdenom==0 || hdr->fpsnumer==0) - return 5; // "invalid framerate numerator or denominator" - - Printf("animvpx: rate is %d frames / %d seconds (%.03f fps).\n", - hdr->fpsnumer, hdr->fpsdenom, (double)hdr->fpsnumer/hdr->fpsdenom); - } - else - { - double fps = (hdr->fpsdenom==0) ? 0.0 : (double)hdr->fpsnumer/hdr->fpsdenom; - - Printf("animvpx: set rate to 30 fps (header says %d frames / %d seconds = %.03f fps).\n", - hdr->fpsnumer, hdr->fpsdenom, fps); - - /* Don't know FPS for sure, and don't have readahead code - * (yet?), so just default to 30fps. - */ - hdr->fpsnumer = 30; - hdr->fpsdenom = 1; - } - - return 0; -} - -////////// CODEC STUFF ////////// -static void get_codec_error(animvpx_codec_ctx *codec) -{ - codec->errmsg_detail = vpx_codec_error_detail(&codec->codec); - codec->errmsg = vpx_codec_error(&codec->codec); -} - -// no checks for double-init! -int32_t animvpx_init_codec(const animvpx_ivf_header_t *info, FileReader & inhandle, animvpx_codec_ctx *codec) -{ - vpx_codec_dec_cfg_t cfg; - - cfg.threads = 1; - cfg.w = info->width; - cfg.h = info->height; - - codec->width = info->width; - codec->height = info->height; - - // - codec->inhandle = &inhandle; - codec->pic = (uint8_t *)Xcalloc(info->width*info->height,4); - - codec->compbuflen = codec->compbufallocsiz = 0; - codec->compbuf = NULL; - - codec->iter = NULL; - - if (codec->pic == NULL) - { - codec->initstate = -1; - return 1; - } - - if (vpx_codec_dec_init(&codec->codec, &vpx_codec_vp8_dx_algo, &cfg, 0)) - { - get_codec_error(codec); - codec->initstate = -1; - return 1; - } - - codec->initstate = 1; - codec->decstate = 0; - - codec->errmsg_detail = codec->errmsg = NULL; - - codec->numframes = 0; - memset(codec->sumtimes, 0, sizeof(codec->sumtimes)); - memset(codec->maxtimes, 0, sizeof(codec->maxtimes)); - - return 0; -} - -int32_t animvpx_uninit_codec(animvpx_codec_ctx *codec) -{ - if (codec->initstate <= 0) - return 2; - - DO_FREE_AND_NULL(codec->pic); - - if (vpx_codec_destroy(&codec->codec)) - { - get_codec_error(codec); - codec->initstate = -2; - return 1; - } - - codec->initstate = 0; - - return 0; -} - -////////// FRAME RETRIEVAL ////////// - -// read one IVF/VP8 frame, which may code multiple "picture-frames" -static int32_t animvpx_read_frame(FileReader & inhandle, uint8_t **bufptr, uint32_t *bufsizptr, uint32_t *bufallocsizptr) -{ -#pragma pack(push,1) - struct { uint32_t framesiz; uint64_t timestamp; } hdr; -#pragma pack(pop) - - if (inhandle.Read(&hdr, sizeof(hdr)) != sizeof(hdr)) - return 1; - - if (hdr.framesiz == 0) - return 6; // must be 6, see animvpx_nextpic_errmsg[] - -// Printf("frame size: %u\n", hdr.framesiz); - - if (!*bufptr) - { - *bufptr = (uint8_t *)Xmalloc(hdr.framesiz); - if (!*bufptr) - return 2; - *bufallocsizptr = hdr.framesiz; - } - else if (*bufallocsizptr < hdr.framesiz) - { - *bufptr = (uint8_t *)Xrealloc(*bufptr, hdr.framesiz); - if (!*bufptr) - return 2; - *bufallocsizptr = hdr.framesiz; - } - - *bufsizptr = hdr.framesiz; - - if (inhandle.Read(*bufptr, hdr.framesiz) != (signed)hdr.framesiz) - return 3; - - return 0; -} - -const char *animvpx_nextpic_errmsg[] = { - "All OK", - "INTERNAL ERROR, animvpx_codec_ctx not initalized!", - "OUT OF MEMORY", - "couldn't read whole frame", - "decoder error, couldn't decode frame", - "picture dimension mismatch", - "INTERNAL ERROR: read 0 frame length", - "Failed getting corruption status (VP8D_GET_FRAME_CORRUPTED)" -}; - -// retrieves one picture-frame from the stream -// pic format: lines of [Y U V 0] pixels -// *picptr==NULL means EOF has been reached - -int32_t animvpx_nextpic(animvpx_codec_ctx *codec, uint8_t **picptr) -{ - int32_t ret, corrupted; - vpx_image_t *img; - - int32_t t[3]; - - if (codec->initstate <= 0) // not inited or error - return 1; - - t[0] = I_msTime(); - - if (codec->decstate == 0) // first time / begin - { -read_ivf_frame: - corrupted = 0; - - ret = animvpx_read_frame(*codec->inhandle, &codec->compbuf, &codec->compbuflen, - &codec->compbufallocsiz); - if (ret == 1) - { - // reached EOF - *picptr = NULL; - codec->decstate = 2; - return 0; - } - else if (ret == 2 || ret == 3 || ret == 6) - { - *picptr = NULL; - codec->decstate = -1; - return ret; - } - // ^^^ keep in sync with all animvpx_read_frame() errors! - - // codec->compbuf now contains one IVF/VP8 frame - codec->decstate = 1; - - // decode it! - if (vpx_codec_decode(&codec->codec, codec->compbuf, codec->compbuflen, NULL, 0)) - { - get_codec_error(codec); - codec->decstate = -2; - return 4; - } - -// Compilation fix for Debian 6.0 (squeeze), which doesn't have -// VP8D_GET_FRAME_CORRUPTED. -// LibVPX doesn't seem to have a version #define, so we use the -// following one to determine conditional compilation. -#ifdef VPX_CODEC_CAP_ERROR_CONCEALMENT - if (vpx_codec_control(&codec->codec, VP8D_GET_FRAME_CORRUPTED, &corrupted)) - { - get_codec_error(codec); - codec->decstate = -2; - return 7; - } -#endif - if (corrupted) - Printf("warning: corrupted frame!\n"); - } - - img = vpx_codec_get_frame(&codec->codec, &codec->iter); - if (img == NULL) - { - codec->iter = NULL; // ! - goto read_ivf_frame; - } - - if (img->d_w != codec->width || img->d_h != codec->height) - { - codec->decstate = -1; - return 5; - } - - t[1] = I_msTime(); - - uint8_t *const dstpic = codec->pic; - - uint8_t const *const yplane = img->planes[VPX_PLANE_Y]; - uint8_t const *const uplane = img->planes[VPX_PLANE_U]; - uint8_t const *const vplane = img->planes[VPX_PLANE_V]; - - const int32_t ystride = img->stride[VPX_PLANE_Y]; - const int32_t ustride = img->stride[VPX_PLANE_U]; - const int32_t vstride = img->stride[VPX_PLANE_V]; - - /*** 3 planes --> packed conversion ***/ - vec2u_t const dim = { img->d_w, img->d_h }; - - for (unsigned int y = 0; y < dim.y; y += 2) - { - unsigned int const y1 = y + 1; - unsigned int const wy = dim.x * y; - unsigned int const wy1 = dim.x * y1; - - for (unsigned int x = 0; x < dim.x; x += 2) - { - uint8_t u = uplane[ustride * (y >> 1) + (x >> 1)]; - uint8_t v = vplane[vstride * (y >> 1) + (x >> 1)]; - - dstpic[(wy + x) << 2] = yplane[ystride * y + x]; - dstpic[(wy + x + 1) << 2] = yplane[ystride * y + x + 1]; - dstpic[(wy1 + x) << 2] = yplane[ystride * y1 + x]; - dstpic[(wy1 + x + 1) << 2] = yplane[ystride * y1 + x + 1]; - - dstpic[((wy + x) << 2) + 1] = u; - dstpic[((wy + x + 1) << 2) + 1] = u; - dstpic[((wy1 + x) << 2) + 1] = u; - dstpic[((wy1 + x + 1) << 2) + 1] = u; - - dstpic[((wy + x) << 2) + 2] = v; - dstpic[((wy + x + 1) << 2) + 2] = v; - dstpic[((wy1 + x) << 2) + 2] = v; - dstpic[((wy1 + x + 1) << 2) + 2] = v; - } - } - t[2] = I_msTime(); - - codec->sumtimes[0] += t[1]-t[0]; - codec->sumtimes[1] += t[2]-t[1]; - - codec->maxtimes[0] = max(codec->maxtimes[0], t[1]-t[0]); - codec->maxtimes[1] = max(codec->maxtimes[0], t[2]-t[1]); - - *picptr = codec->pic; - return 0; -} - -#endif diff --git a/source/core/screenjob.cpp b/source/core/screenjob.cpp index 9a7fb5bce..f5dbff53d 100644 --- a/source/core/screenjob.cpp +++ b/source/core/screenjob.cpp @@ -48,7 +48,8 @@ #include "SmackerDecoder.h" #include "movie/playmve.h" #include "gamecontrol.h" -#include "animvpx.h" +#include +#include #include "raze_music.h" @@ -240,16 +241,23 @@ class DVpxPlayer : public DScreenJob bool failed = false; FileReader fr; AnimTextures animtex; - animvpx_codec_ctx codec; const AnimSound* animSnd; + unsigned width, height; + TArray Pic; + TArray readBuf; + vpx_codec_ctx_t codec{}; + vpx_codec_iter_t iter = nullptr; + uint32_t convnumer; uint32_t convdenom; - uint32_t msecsperframe; + uint64_t nsecsperframe; uint64_t nextframetime; + int decstate = 0; int framenum = 0; + int numframes; int lastsoundframe = -1; public: int soundtrack = -1; @@ -263,27 +271,23 @@ public: fr = std::move(fr_); animSnd = animSnd_; - animvpx_ivf_header_t info; - - int err = animvpx_read_ivf_header(fr, &info); - - if (err) + if (!ReadIVFHeader(origframedelay)) { - Printf(PRINT_BOLD, "Failed reading IVF file: %s\n", animvpx_read_ivf_header_errmsg[err]); + // We should never get here, because any file failing this has been eliminated before this constructor got called. + Printf(PRINT_BOLD, "Failed reading IVF header\n"); failed = true; } - if (animvpx_init_codec(&info, fr, &codec)) + Pic.Resize(width * height * 4); + + + // Todo: Support VP9 as well? + vpx_codec_dec_cfg_t cfg = { 1, width, height }; + if (vpx_codec_dec_init(&codec, &vpx_codec_vp8_dx_algo, &cfg, 0)) { 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; } //--------------------------------------------------------------------------- @@ -292,6 +296,140 @@ public: // //--------------------------------------------------------------------------- + bool ReadIVFHeader(int origframedelay) + { + // IVF format: http://wiki.multimedia.cx/index.php?title=IVF + uint32_t magic; fr.Read(&magic, 4); // do not byte swap! + if (magic != MAKE_ID('D', 'K', 'I', 'F')) return false; + uint16_t version = fr.ReadUInt16(); + if (version != 0) return false; + uint16_t length = fr.ReadUInt16(); + if (length != 32) return false; + fr.Read(&magic, 4); + if (magic != MAKE_ID('V', 'P', '8', '0')) return false; + + width = fr.ReadUInt16(); + height = fr.ReadUInt16(); + uint32_t fpsdenominator = fr.ReadUInt32(); + uint32_t fpsnumerator = fr.ReadUInt32(); + numframes = fr.ReadUInt32(); + if (numframes == 0) return false; + fr.Seek(4, FileReader::SeekCur); + + if (fpsdenominator > 1000 || fpsnumerator == 0 || fpsdenominator == 0) + { + // default to 30 fps if the header does not provide useful info. + fpsdenominator = 30; + fpsnumerator = 1; + } + + convnumer = 120 * fpsnumerator; + convdenom = fpsdenominator * origframedelay; + + nsecsperframe = int64_t(fpsnumerator) * 1'000'000'000 / fpsdenominator; + nextframetime = 0; + + return true; + } + + //--------------------------------------------------------------------------- + // + // + // + //--------------------------------------------------------------------------- + + bool ReadFrame() + { + int corrupted = 0; + int framesize = fr.ReadInt32(); + fr.Seek(8, FileReader::SeekCur); + if (framesize == 0) return false; + + readBuf.Resize(framesize); + if (fr.Read(readBuf.Data(), framesize) != framesize) return false; + if (vpx_codec_decode(&codec, readBuf.Data(), readBuf.Size(), NULL, 0) != VPX_CODEC_OK) return false; + if (vpx_codec_control(&codec, VP8D_GET_FRAME_CORRUPTED, &corrupted) != VPX_CODEC_OK) return false; + return true; + } + + //--------------------------------------------------------------------------- + // + // + // + //--------------------------------------------------------------------------- + + vpx_image_t *GetFrameData() + { + vpx_image_t *img; + do + { + if (decstate == 0) // first time / begin + { + if (!ReadFrame()) return nullptr; + decstate = 1; + } + + img = vpx_codec_get_frame(&codec, &iter); + if (img == nullptr) + { + decstate = 0; + iter = nullptr; + } + } while (img == nullptr); + + return img->d_w == width && img->d_h == height? img : nullptr; + } + + //--------------------------------------------------------------------------- + // + // + // + //--------------------------------------------------------------------------- + + void SetPixel(uint8_t* dest, uint8_t y, uint8_t u, uint8_t v) + { + dest[0] = y; + dest[1] = u; + dest[2] = v; + } + + bool CreateNextFrame() + { + auto img = GetFrameData(); + if (!img) return false; + uint8_t const* const yplane = img->planes[VPX_PLANE_Y]; + uint8_t const* const uplane = img->planes[VPX_PLANE_U]; + uint8_t const* const vplane = img->planes[VPX_PLANE_V]; + + const int ystride = img->stride[VPX_PLANE_Y]; + const int ustride = img->stride[VPX_PLANE_U]; + const int vstride = img->stride[VPX_PLANE_V]; + + for (unsigned int y = 0; y < height; y += 2) + { + unsigned int y1 = y + 1; + unsigned int wy = width * y; + unsigned int wy1 = width * y1; + + for (unsigned int x = 0; x < width; x += 2) + { + uint8_t u = uplane[ustride * (y >> 1) + (x >> 1)]; + uint8_t v = vplane[vstride * (y >> 1) + (x >> 1)]; + + SetPixel(&Pic[(wy + x) << 2], yplane[ystride * y + x], u, v); + SetPixel(&Pic[(wy + x + 1) << 2], yplane[ystride * y + x + 1], u, v); + SetPixel(&Pic[(wy1 + x) << 2], yplane[ystride * y1 + x], u, v); + SetPixel(&Pic[(wy1 + x + 1) << 2], yplane[ystride * y1 + x + 1], u, v); + } + } + return true; + } + + //--------------------------------------------------------------------------- + // + // + // + //--------------------------------------------------------------------------- int Frame(uint64_t clock, bool skiprequest) override { @@ -301,34 +439,24 @@ public: { Mus_Play(nullptr, fileSystem.GetFileFullName(soundtrack, false), false); } - animtex.SetSize(AnimTexture::YUV, codec.width, codec.height); + animtex.SetSize(AnimTexture::YUV, width, height); } bool stop = false; if (clock > nextframetime) { - nextframetime += 1'000'000 * msecsperframe; + nextframetime += nsecsperframe; - uint8_t* pic; - int i = animvpx_nextpic(&codec, &pic); - if (i) + if (!CreateNextFrame()) { - 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); - } + Printf(PRINT_BOLD, "Failed reading next frame\n"); stop = true; } - if (!pic) stop = true; - - if (!stop) + else { - animtex.SetFrame(nullptr, pic); + animtex.SetFrame(nullptr, Pic.Data()); } - framenum++; + if (framenum >= numframes) stop = true; int soundframe = convdenom ? scale(framenum, convnumer, convdenom) : framenum; if (soundframe > lastsoundframe) @@ -355,7 +483,7 @@ public: void OnDestroy() override { - animvpx_uninit_codec(&codec); + vpx_codec_destroy(&codec); } };