/* ANM file replacement with VP8 video */ #ifdef USE_LIBVPX #include "compat.h" #include "compat.h" #include "baselayer.h" #include "build.h" #include "glad/glad.h" #include "cache1d.h" #undef UNUSED #define VPX_CODEC_DISABLE_COMPAT 1 #include #include #include "animvpx.h" 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", }; EDUKE32_STATIC_ASSERT(sizeof(animvpx_ivf_header_t) == 32); int32_t animvpx_read_ivf_header(buildvfs_kfd inhandle, animvpx_ivf_header_t *hdr) { int32_t err; if (kread(inhandle, 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 = B_LITTLE16(hdr->hdrlen); hdr->width = B_LITTLE16(hdr->width); hdr->height = B_LITTLE16(hdr->height); hdr->fpsnumer = B_LITTLE16(hdr->fpsnumer); hdr->fpsdenom = B_LITTLE16(hdr->fpsdenom); hdr->numframes = B_LITTLE32(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" initprintf("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; initprintf("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, buildvfs_kfd 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; Bmemset(codec->sumtimes, 0, sizeof(codec->sumtimes)); Bmemset(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(buildvfs_kfd 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 (kread(inhandle, &hdr, sizeof(hdr)) != sizeof(hdr)) return 1; if (hdr.framesiz == 0) return 6; // must be 6, see animvpx_nextpic_errmsg[] // OSD_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 (kread(inhandle, *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 #ifdef DEBUGGINGAIDS ATTRIBUTE_OPTIMIZE("O1") #else ATTRIBUTE_OPTIMIZE("O3") #endif 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] = timerGetTicks(); 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) OSD_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] = timerGetTicks(); 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]; if (glinfo.glsl) /*** 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; } } } else /*** 3 planes --> packed conversion (RGB) ***/ { int i = 0; for (unsigned int imgY = 0; imgY < img->d_h; imgY++) { for (unsigned int imgX = 0; imgX < img->d_w; imgX++) { uint8_t const y = yplane[imgY * ystride + imgX]; uint8_t const u = uplane[(imgY >> 1) * ustride + (imgX >> 1)]; uint8_t const v = vplane[(imgY >> 1) * vstride + (imgX >> 1)]; int const c = y - 16; int const d = (u + -128); int const e = (v + -128); int const c298 = c * 298; dstpic[i + 0] = (uint8_t)clamp((c298 + 409 * e - -128) >> 8, 0, 255); dstpic[i + 1] = (uint8_t)clamp((c298 - 100 * d - 208 * e - -128) >> 8, 0, 255); dstpic[i + 2] = (uint8_t)clamp((c298 + 516 * d - -128) >> 8, 0, 255); i += 3; } } } t[2] = timerGetTicks(); 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; } /////////////// DRAWING! /////////////// static GLuint texname = 0; static int32_t texuploaded; #ifdef USE_GLEXT // YUV->RGB conversion fragment shader adapted from // http://www.fourcc.org/fccyvrgb.php: "Want some sample code?" // direct link: http://www.fourcc.org/source/YUV420P-OpenGL-GLSLang.c static const char *fragprog_src = "#version 120\n" "uniform sampler2D tex;\n" "void main(void) {\n" " float r,g,b,y,u,v;\n" " vec3 yuv;\n" " yuv = texture2D(tex, gl_TexCoord[0].st).rgb;\n" " y = yuv.r;\n" " u = yuv.g;\n" " v = yuv.b;\n" " y = 1.1643*(y-0.0625);\n" " u = u-0.5;\n" " v = v-0.5;\n" " r = y + 1.5958*v;\n" " g = y - 0.39173*u - 0.81290*v;\n" " b = y + 2.017*u;\n" " gl_FragColor = vec4(r,g,b,1.0);\n" "}\n"; #endif void animvpx_setup_glstate(int32_t animvpx_flags) { #ifdef USE_GLEXT if (glinfo.glsl) { GLint gli; GLuint FSHandle, PHandle; static char logbuf[512]; // first, compile the fragment shader /* Set up program objects. */ PHandle = glCreateProgram(); FSHandle = glCreateShader(GL_FRAGMENT_SHADER); /* Compile the shader. */ glShaderSource(FSHandle, 1, (const GLchar **)&fragprog_src, NULL); glCompileShader(FSHandle); /* Print the compilation log. */ glGetShaderiv(FSHandle, GL_COMPILE_STATUS, &gli); glGetShaderInfoLog(FSHandle, sizeof(logbuf), NULL, logbuf); if (logbuf[0]) OSD_Printf("animvpx compile log: %s\n", logbuf); /* Create a complete program object. */ glAttachShader(PHandle, FSHandle); glLinkProgram(PHandle); /* And print the link log. */ glGetProgramInfoLog(PHandle, sizeof(logbuf), NULL, logbuf); if (logbuf[0]) OSD_Printf("animvpx link log: %s\n", logbuf); /* Finally, use the program. */ useShaderProgram(PHandle); } #endif ////////// GL STATE ////////// //Force fullscreen (glox1=-1 forces it to restore afterwards) glViewport(0,0,xdim,ydim); glox1 = -1; glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_TEXTURE); glLoadIdentity(); // glPushAttrib(GL_ENABLE_BIT); glDisable(GL_ALPHA_TEST); glDisable(GL_DEPTH_TEST); glDisable(GL_BLEND); glDisable(GL_CULL_FACE); glEnable(GL_TEXTURE_2D); #ifdef USE_GLEXT glActiveTexture(GL_TEXTURE0); #endif glGenTextures(1, &texname); glBindTexture(GL_TEXTURE_2D, texname); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glinfo.clamptoedge?GL_CLAMP_TO_EDGE:GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glinfo.clamptoedge?GL_CLAMP_TO_EDGE:GL_CLAMP); if ((animvpx_flags & CUTSCENE_TEXTUREFILTER && gltexfiltermode == TEXFILTER_ON) || animvpx_flags & CUTSCENE_FORCEFILTER || (!(animvpx_flags & CUTSCENE_TEXTUREFILTER) && !(animvpx_flags & CUTSCENE_FORCENOFILTER))) // if no flags, then use filter for IVFs { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } texuploaded = 0; //////////////////// glClearColor(0.0,0.0,0.0,1.0); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); } void animvpx_restore_glstate(void) { #ifdef USE_GLEXT if (glinfo.glsl) { useShaderProgram(0); polymost_resetProgram(); } #endif // glPopAttrib(); glDeleteTextures(1, &texname); texname = 0; texuploaded = 0; } int32_t animvpx_render_frame(animvpx_codec_ctx *codec, double animvpx_aspect) { int32_t t = timerGetTicks(); if (codec->initstate <= 0) // not inited or error return 1; if (codec->pic == NULL) return 2; // shouldn't happen int fmt = glinfo.glsl ? GL_RGBA : GL_RGB; if (!texuploaded) { glTexImage2D(GL_TEXTURE_2D, 0, fmt, codec->width,codec->height, 0, fmt, GL_UNSIGNED_BYTE, codec->pic); if (glGetError() != GL_NO_ERROR) return 1; texuploaded = 1; } else { glTexSubImage2D(GL_TEXTURE_2D, 0, 0,0, codec->width,codec->height, fmt, GL_UNSIGNED_BYTE, codec->pic); if (glGetError() != GL_NO_ERROR) return 1; } 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 glBegin(GL_QUADS); if (!glinfo.glsl) glColor3f(1.0, 1.0, 1.0); glTexCoord2f(0.0,1.0); glVertex3f(-x, -y, 0.0); glTexCoord2f(0.0,0.0); glVertex3f(-x, y, 0.0); glTexCoord2f(1.0,0.0); glVertex3f(x, y, 0.0); glTexCoord2f(1.0,1.0); glVertex3f(x, -y, 0.0); glEnd(); t = timerGetTicks()-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; if (glinfo.glsl) initprintf("animvpx: GLSL mode\n"); initprintf("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