From 84c4ff93cb0d2bce3ac95150dd975936cb06d0f9 Mon Sep 17 00:00:00 2001 From: vortex Date: Tue, 20 Mar 2012 20:56:05 +0000 Subject: [PATCH] Introducing LibAvW - a wrapper DLL to play videos using libav. Basic support includes extraction of video frames (sound files should be placed as separate .ogg's) and scaling. No ABI requirements (wrapper takes all dirty code). LibAvW can be found at: svn://svn.icculus.org/razorwind/trunk/dplibavw . git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@11766 d7cf8633-e32d-0410-b094-e92efae38249 ::stable-branch::merge=e42e4168dcbbade766cfa8ad980e7975a14fa1bb --- cl_video.c | 97 ++++++--- cl_video.h | 1 + cl_video_jamdecode.c | 463 +++++++++++++++++++++++-------------------- cl_video_libavw.c | 390 ++++++++++++++++++++++++++++++++++++ dpvsimpledecode.c | 10 + dpvsimpledecode.h | 3 + 6 files changed, 713 insertions(+), 251 deletions(-) create mode 100644 cl_video_libavw.c diff --git a/cl_video.c b/cl_video.c index 08db0588..f23dc928 100644 --- a/cl_video.c +++ b/cl_video.c @@ -2,13 +2,6 @@ #include "quakedef.h" #include "cl_dyntexture.h" #include "cl_video.h" -#include "dpvsimpledecode.h" - -// VorteX: JAM video module used by Blood Omnicide -#define USEJAM -#ifdef USEJAM - #include "cl_video_jamdecode.c" -#endif // cvars cvar_t cl_video_subtitles = {CVAR_SAVE, "cl_video_subtitles", "0", "show subtitles for videos (if they are present)"}; @@ -22,6 +15,19 @@ cvar_t cl_video_keepaspectratio = {CVAR_SAVE, "cl_video_keepaspectratio", "0", " cvar_t cl_video_fadein = {CVAR_SAVE, "cl_video_fadein", "0", "fading-from-black effect once video is started, in seconds"}; cvar_t cl_video_fadeout = {CVAR_SAVE, "cl_video_fadeout", "0", "fading-to-black effect once video is ended, in seconds"}; +cvar_t v_glslgamma_video = {CVAR_SAVE, "v_glslgamma_video", "1", "applies GLSL gamma to played video, could be a fraction, requires r_glslgamma_2d 1."}; + +// DPV stream decoder +#include "dpvsimpledecode.h" + +// VorteX: libavcodec implementation +#include "cl_video_libavw.c" + +// JAM video decoder used by Blood Omnicide +#ifdef JAMVIDEO +#include "cl_video_jamdecode.c" +#endif + // constants (and semi-constants) static int cl_videormask; static int cl_videobmask; @@ -44,18 +50,23 @@ static clvideo_t *FindUnusedVid( void ) static qboolean OpenStream( clvideo_t * video ) { const char *errorstring; + video->stream = dpvsimpledecode_open( video, video->filename, &errorstring); - if (!video->stream ) - { -#ifdef USEJAM - video->stream = jam_open( video, video->filename, &errorstring); - if (video->stream) - return true; + if (video->stream) + return true; + +#ifdef JAMVIDEO + video->stream = jam_open( video, video->filename, &errorstring); + if (video->stream) + return true; #endif - Con_Printf("unable to open \"%s\", error: %s\n", video->filename, errorstring); - return false; - } - return true; + + video->stream = LibAvW_OpenVideo( video, video->filename, &errorstring); + if (video->stream) + return true; + + Con_Printf("unable to open \"%s\", error: %s\n", video->filename, errorstring); + return false; } static void VideoUpdateCallback(rtexture_t *rt, void *data) @@ -89,7 +100,11 @@ static void SuspendVideo( clvideo_t * video ) UnlinkVideoTexture(video); // if we are in firstframe mode, also close the stream if (video->state == CLVIDEO_FIRSTFRAME) - video->close(video->stream); + { + if (video->stream) + video->close(video->stream); + video->stream = NULL; + } } static qboolean WakeVideo( clvideo_t * video ) @@ -129,7 +144,7 @@ static void LoadSubtitles( clvideo_t *video, const char *subtitlesfile ) subtitle_text = NULL; if (langcvar) { - dpsnprintf(overridename, sizeof(overridename), "script/locale/%s/%s", langcvar->string, subtitlesfile); + dpsnprintf(overridename, sizeof(overridename), "locale/%s/%s", langcvar->string, subtitlesfile); subtitle_text = (char *)FS_LoadFile(overridename, cls.permanentmempool, false, NULL); } if (!subtitle_text) @@ -303,7 +318,9 @@ void CL_RestartVideo(clvideo_t *video) video->framenum = -1; // reopen stream - video->close(video->stream); + if (video->stream) + video->close(video->stream); + video->stream = NULL; if (!OpenStream(video)) video->state = CLVIDEO_UNUSED; } @@ -318,7 +335,11 @@ void CL_CloseVideo(clvideo_t * video) // close stream if (!video->suspended || video->state != CLVIDEO_FIRSTFRAME) - video->close(video->stream); + { + if (video->stream) + video->close(video->stream); + video->stream = NULL; + } // unlink texture if (!video->suspended) UnlinkVideoTexture(video); @@ -389,13 +410,6 @@ void CL_Video_Frame(void) cl_num_videos--; } -void CL_Video_Shutdown( void ) -{ - int i; - for (i = 0 ; i < cl_num_videos ; i++) - CL_CloseVideo(&cl_videos[ i ]); -} - void CL_PurgeOwner( int owner ) { int i; @@ -474,7 +488,7 @@ void CL_DrawVideo(void) st[6] = 1.0; st[7] = 1.0; if (cl_video_keepaspectratio.integer) { - float a = ((float)video->cpif.width / (float)video->cpif.height) / ((float)vid.width / (float)vid.height); + float a = video->getaspectratio(video->stream) / ((float)vid.width / (float)vid.height); if (cl_video_keepaspectratio.integer >= 2) { // clip instead of scale @@ -542,7 +556,14 @@ void CL_DrawVideo(void) #endif // draw video - DrawQ_SuperPic(px, py, &video->cpif, sx, sy, st[0], st[1], b, b, b, 1, st[2], st[3], b, b, b, 1, st[4], st[5], b, b, b, 1, st[6], st[7], b, b, b, 1, 0); + if (v_glslgamma_video.value >= 1) + DrawQ_SuperPic(px, py, &video->cpif, sx, sy, st[0], st[1], b, b, b, 1, st[2], st[3], b, b, b, 1, st[4], st[5], b, b, b, 1, st[6], st[7], b, b, b, 1, 0); + else + { + DrawQ_SuperPic(px, py, &video->cpif, sx, sy, st[0], st[1], b, b, b, 1, st[2], st[3], b, b, b, 1, st[4], st[5], b, b, b, 1, st[6], st[7], b, b, b, 1, DRAWFLAG_NOGAMMA); + if (v_glslgamma_video.value > 0.0) + DrawQ_SuperPic(px, py, &video->cpif, sx, sy, st[0], st[1], b, b, b, v_glslgamma_video.value, st[2], st[3], b, b, b, v_glslgamma_video.value, st[4], st[5], b, b, b, v_glslgamma_video.value, st[6], st[7], b, b, b, v_glslgamma_video.value, 0); + } #ifndef USE_GLES2 // disable video-only stipple @@ -704,5 +725,19 @@ void CL_Video_Init( void ) Cvar_RegisterVariable(&cl_video_fadein); Cvar_RegisterVariable(&cl_video_fadeout); + Cvar_RegisterVariable(&v_glslgamma_video); + R_RegisterModule( "CL_Video", cl_video_start, cl_video_shutdown, cl_video_newmap, NULL, NULL ); -} \ No newline at end of file + + LibAvW_OpenLibrary(); +} + +void CL_Video_Shutdown( void ) +{ + int i; + + for (i = 0 ; i < cl_num_videos ; i++) + CL_CloseVideo(&cl_videos[ i ]); + + LibAvW_CloseLibrary(); +} diff --git a/cl_video.h b/cl_video.h index 4277124e..97960b89 100644 --- a/cl_video.h +++ b/cl_video.h @@ -59,6 +59,7 @@ typedef struct clvideo_s unsigned int (*getwidth) (void *stream); unsigned int (*getheight) (void *stream); double (*getframerate) (void *stream); + double (*getaspectratio) (void *stream); int (*decodeframe) (void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow); // if a video is suspended, it is automatically paused (else we'd still have to process the frames) diff --git a/cl_video_jamdecode.c b/cl_video_jamdecode.c index da65ef29..475f1f7b 100644 --- a/cl_video_jamdecode.c +++ b/cl_video_jamdecode.c @@ -1,135 +1,166 @@ // JAM format decoder, used by Blood Omnicide +#ifdef LIBAVCODEC +//#define JAM_USELIBAVCODECSCALE +#endif + typedef struct jamdecodestream_s { - int error; + qfile_t *file; + double info_framerate; + unsigned int info_frames; + unsigned int info_imagewidth; + unsigned int info_imageheight; + double info_aspectratio; + float colorscale; + unsigned char colorsub; - qfile_t *file; - double info_framerate; - unsigned int info_frames; - unsigned int info_imagewidth; - unsigned int info_imageheight; - int doubleres; - float colorscale; - unsigned char colorsub; - float stipple; + // info used during decoding + unsigned char *frame; + unsigned char *frame_prev; + unsigned char frame_palette[768]; + unsigned char *frame_compressed; + unsigned int framesize; + unsigned int framenum; - // info used durign decoding - unsigned char *videopixels; - unsigned char *compressed; - unsigned char *framedata; - unsigned char *prevframedata; - unsigned char colormap[768]; - unsigned int framesize; - unsigned int framenum; + // libavcodec scaling +#ifdef JAM_USELIBAVCODECSCALE + unsigned char *frame_output_buffer; + AVFrame *frame_output; + AVFrame *frame_output_scale; + unsigned int framewidth; + unsigned int frameheight; +#endif // channel the sound file is being played on int sndchan; } jamdecodestream_t; -#define JAMDECODEERROR_NONE 0 -#define JAMDECODEERROR_EOF 1 -#define JAMDECODEERROR_READERROR 2 -#define JAMDECODEERROR_BAD_FRAME_HEADER 3 -#define JAMDECODEERROR_BAD_OUTPUT_SIZE 4 -#define JAMDECODEERROR_BAD_COLORMAP 5 - // opens a stream void jam_close(void *stream); unsigned int jam_getwidth(void *stream); unsigned int jam_getheight(void *stream); double jam_getframerate(void *stream); +double jam_getaspectratio(void *stream); int jam_video(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow); -static void *jam_open(clvideo_t *video, char *filename, const char **errorstring) +void *jam_open(clvideo_t *video, char *filename, const char **errorstring) { - unsigned char jamHead[16]; - char *wavename; + char jamHead[16]; jamdecodestream_t *s; - qfile_t *file; + char *wavename; + // allocate stream structure s = (jamdecodestream_t *)Z_Malloc(sizeof(jamdecodestream_t)); - if (s != NULL) + memset(s, 0, sizeof(jamdecodestream_t)); + s->sndchan = -1; + if (s == NULL) { - if ((file = FS_OpenVirtualFile(filename, false))) - { - s->file = file; - if (FS_Read(s->file, &jamHead, 16)) - { - if (!memcmp(jamHead, "JAM", 3)) - { - s->info_imagewidth = LittleLong(*(jamHead + 4)); - s->info_imageheight = LittleLong(*(jamHead + 8)); - s->info_frames = LittleLong(*(jamHead + 12)); - s->info_framerate = 15; - s->doubleres = 0; - s->colorscale = 0.70; - s->colorsub = 8; - s->stipple = 0.4; - s->framesize = s->info_imagewidth * s->info_imageheight; - if (s->framesize > 0) - { - s->compressed = (unsigned char *)Z_Malloc(s->framesize); - s->framedata = (unsigned char *)Z_Malloc(s->framesize * 2); - s->prevframedata = (unsigned char *)Z_Malloc(s->framesize * 2); - s->videopixels = (unsigned char *)Z_Malloc(s->framesize * 4); // bgra, doubleres - if (s->compressed != NULL && s->framedata != NULL && s->prevframedata != NULL && s->videopixels != NULL) - { - size_t namelen; + *errorstring = "unable to allocate memory for stream info structure"; + return NULL; + } - namelen = strlen(filename) + 10; - wavename = (char *)Z_Malloc(namelen); - if (wavename) - { - sfx_t* sfx; + // open file + s->file = FS_OpenVirtualFile(filename, true); + if (!s->file) + { + *errorstring = "unable to open videofile"; + jam_close(s); + return NULL; + } - FS_StripExtension(filename, wavename, namelen); - strlcat(wavename, ".wav", namelen); - sfx = S_PrecacheSound(wavename, false, false); - if (sfx != NULL) - s->sndchan = S_StartSound (-1, 0, sfx, vec3_origin, 1.0f, 0); - else - s->sndchan = -1; - Z_Free(wavename); - } - // all is well... - // set the module functions - s->framenum = 0; - video->close = jam_close; - video->getwidth = jam_getwidth; - video->getheight = jam_getheight; - video->getframerate = jam_getframerate; - video->decodeframe = jam_video; - return s; - } - else if (errorstring != NULL) - *errorstring = "unable to allocate memory for stream info structure"; - if (s->compressed != NULL) - Z_Free(s->compressed); - if (s->framedata != NULL) - Z_Free(s->framedata); - if (s->prevframedata != NULL) - Z_Free(s->prevframedata); - if (s->videopixels != NULL) - Z_Free(s->videopixels); - } - else if (errorstring != NULL) - *errorstring = "bad framesize"; - } - else if (errorstring != NULL) - *errorstring = "not JAM videofile"; - } - else if (errorstring != NULL) - *errorstring = "unexpected EOF"; - FS_Close(file); - } - else if (errorstring != NULL) - *errorstring = "unable to open videofile"; + // read header + if (!FS_Read(s->file, jamHead, 16)) + { + *errorstring = "JamDecoder: unexpected EOF reading header"; + jam_close(s); + return NULL; + } + if (memcmp(jamHead, "JAM", 4)) + { + *errorstring = "JamDecoder: not a JAM file"; + jam_close(s); + return NULL; + } + + s->info_imagewidth = LittleLong(*(jamHead + 4)); + s->info_imageheight = LittleLong(*(jamHead + 8)); + s->info_frames = LittleLong(*(jamHead + 12)) - 1; + s->info_framerate = 15; + s->info_aspectratio = (double)s->info_imagewidth / (double)s->info_imageheight; + s->colorscale = 0.90; + s->colorsub = 4; + s->framesize = s->info_imagewidth * s->info_imageheight; + + // allocate frame input/output + if (s->framesize < 0) + { + *errorstring = "JamDecoder: bad framesize"; + jam_close(s); + return NULL; + } + s->frame = (unsigned char *)Z_Malloc(s->framesize * 2); + s->frame_prev = (unsigned char *)Z_Malloc(s->framesize * 2); + s->frame_compressed = (unsigned char *)Z_Malloc(s->framesize); + if (s->frame_compressed == NULL || s->frame == NULL || s->frame_prev == NULL) + { + *errorstring = "JamDecoder: unable to allocate memory for video decoder"; + jam_close(s); + return NULL; + } + + // scale support provided by libavcodec +#ifdef JAM_USELIBAVCODECSCALE + s->framewidth = s->info_imagewidth; + s->frameheight = s->info_imageheight; + + // min size + if (cl_video_libavcodec_minwidth.integer > 0) + s->info_imagewidth = max(s->info_imagewidth, (unsigned int)cl_video_libavcodec_minwidth.integer); + if (cl_video_libavcodec_minheight.integer > 0) + s->info_imageheight = max(s->info_imageheight, (unsigned int)cl_video_libavcodec_minheight.integer); + + // allocate output + s->frame_output_buffer = (unsigned char *)Z_Malloc(s->framesize * 4); + s->frame_output = AvCodec_AllocFrame(); + s->frame_output_scale = AvCodec_AllocFrame(); + if (!s->frame_output_buffer || !s->frame_output || !s->frame_output_scale) + { + *errorstring = "JamDecoder: failed to allocate LibAvcodec frame"; + jam_close(s); Z_Free(s); + return NULL; } - else if (errorstring != NULL) - *errorstring = "unable to allocate memory for stream info structure"; - return NULL; +#endif + + // everything is ok + // set the module functions + s->framenum = 0; + video->close = jam_close; + video->getwidth = jam_getwidth; + video->getheight = jam_getheight; + video->getframerate = jam_getframerate; + video->decodeframe = jam_video; + video->getaspectratio = jam_getaspectratio; + + // set sound + size_t namelen; + namelen = strlen(filename) + 10; + wavename = (char *)Z_Malloc(namelen); + if (wavename) + { + sfx_t* sfx; + FS_StripExtension(filename, wavename, namelen); + strlcat(wavename, ".wav", namelen); + sfx = S_PrecacheSound(wavename, false, false); + if (sfx != NULL) + s->sndchan = S_StartSound (-1, 0, sfx, vec3_origin, 1.0f, 0); + else + s->sndchan = -1; + Z_Free(wavename); + } + + return s; } // closes a stream @@ -138,14 +169,32 @@ void jam_close(void *stream) jamdecodestream_t *s = (jamdecodestream_t *)stream; if (s == NULL) return; - Z_Free(s->compressed); - Z_Free(s->framedata); - Z_Free(s->prevframedata); - Z_Free(s->videopixels); + if (s->frame_compressed) + Z_Free(s->frame_compressed); + s->frame_compressed = NULL; + if (s->frame) + Z_Free(s->frame); + s->frame = NULL; + if (s->frame_prev) + Z_Free(s->frame_prev); + s->frame_prev = NULL; if (s->sndchan != -1) S_StopChannel(s->sndchan, true, true); + s->sndchan = -1; if (s->file) FS_Close(s->file); + s->file = NULL; +#ifdef JAM_USELIBAVCODECSCALE + if (s->frame_output_buffer) + Z_Free(s->frame_output_buffer); + s->frame_output_buffer = NULL; + if (s->frame_output) + AvUtil_Free(s->frame_output); + s->frame_output = NULL; + if (s->frame_output_scale) + AvUtil_Free(s->frame_output_scale); + s->frame_output_scale = NULL; +#endif Z_Free(s); } @@ -153,8 +202,6 @@ void jam_close(void *stream) unsigned int jam_getwidth(void *stream) { jamdecodestream_t *s = (jamdecodestream_t *)stream; - if (s->doubleres) - return s->info_imagewidth * 2; return s->info_imagewidth; } @@ -162,8 +209,6 @@ unsigned int jam_getwidth(void *stream) unsigned int jam_getheight(void *stream) { jamdecodestream_t *s = (jamdecodestream_t *)stream; - if (s->doubleres) - return s->info_imageheight * 2; return s->info_imageheight; } @@ -174,6 +219,12 @@ double jam_getframerate(void *stream) return s->info_framerate; } +// returns aspect ration of the stream +double jam_getaspectratio(void *stream) +{ + jamdecodestream_t *s = (jamdecodestream_t *)stream; + return s->info_aspectratio; +} // decode JAM frame static void jam_decodeframe(unsigned char *inbuf, unsigned char *outbuf, unsigned char *prevbuf, int outsize, int frametype) @@ -239,117 +290,89 @@ static void jam_decodeframe(unsigned char *inbuf, unsigned char *outbuf, unsigne int jam_video(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow) { unsigned char frameHead[16], *b; - unsigned int compsize, outsize, i, j; + unsigned int compsize, outsize, i; jamdecodestream_t *s = (jamdecodestream_t *)stream; - s->error = DPVSIMPLEDECODEERROR_NONE; - if (s->framenum < s->info_frames) - { + // EOF + if (s->framenum >= s->info_frames) + return 1; + s->framenum++; + readframe: - if (FS_Read(s->file, &frameHead, 16)) - { - compsize = LittleLong(*(frameHead + 8)) - 16; - outsize = LittleLong(*(frameHead + 12)); - if (compsize > s->framesize || outsize > s->framesize) - s->error = JAMDECODEERROR_BAD_FRAME_HEADER; - else if (FS_Read(s->file, s->compressed, compsize)) - { - // palette goes interleaved with special flag - if (frameHead[0] == 2) - { - if (compsize == 768) - { - memcpy(s->colormap, s->compressed, 768); - for(i = 0; i < 768; i++) - s->colormap[i] = (unsigned char)(bound(0, (s->colormap[i] * s->colorscale) - s->colorsub, 255)); - goto readframe; - } - //else - // s->error = JAMDECODEERROR_BAD_COLORMAP; - } - else - { - // decode frame - // shift buffers to provide current and previous one, decode - b = s->prevframedata; - s->prevframedata = s->framedata; - s->framedata = b; - jam_decodeframe(s->compressed, s->framedata, s->prevframedata, outsize, frameHead[4]); - // make 32bit imagepixels from 8bit palettized frame - if (s->doubleres) - b = s->videopixels; - else - b = (unsigned char *)imagedata; - for(i = 0; i < s->framesize; i++) - { - // bgra - *b++ = s->colormap[s->framedata[i]*3 + 2]; - *b++ = s->colormap[s->framedata[i]*3 + 1]; - *b++ = s->colormap[s->framedata[i]*3]; - *b++ = 255; - } - // nearest 2x - if (s->doubleres) - { - for (i = 0; i < s->info_imageheight; i++) - { - b = (unsigned char *)imagedata + (s->info_imagewidth*2*4)*(i*2); - for (j = 0; j < s->info_imagewidth; j++) - { - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4]; - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 1]; - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 2]; - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 3]; - // - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4]; - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 1]; - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 2]; - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 3]; - } - b = (unsigned char *)imagedata + (s->info_imagewidth*2*4)*(i*2 + 1); - for (j = 0; j < s->info_imagewidth; j++) - { - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4]; - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 1]; - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 2]; - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 3]; - // - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4]; - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 1]; - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 2]; - *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 3]; - } - } - // do stippling - if (s->stipple) - { - for (i = 0; i < s->info_imageheight; i++) - { - b = (unsigned char *)imagedata + (s->info_imagewidth * 4 * 2 * 2 * i); - for (j = 0; j < s->info_imagewidth; j++) - { - b[0] = b[0] * s->stipple; - b[1] = b[1] * s->stipple; - b[2] = b[2] * s->stipple; - b += 4; - b[0] = b[0] * s->stipple; - b[1] = b[1] * s->stipple; - b[2] = b[2] * s->stipple; - b += 4; - } - } - } - } + // read frame header + if (!FS_Read(s->file, &frameHead, 16)) + { + Con_Printf("JamDecoder: unexpected EOF on frame %i\n", s->framenum); + return 1; + } + compsize = LittleLong(*(frameHead + 8)) - 16; + outsize = LittleLong(*(frameHead + 12)); + if (compsize > s->framesize || outsize > s->framesize) + { + Con_Printf("JamDecoder: got bogus header on frame %i\n", s->framenum); + return 1; + } - } - } - else - s->error = JAMDECODEERROR_READERROR; + // read frame contents + if (!FS_Read(s->file, s->frame_compressed, compsize)) + { + Con_Printf("JamDecoder: unexpected EOF on frame %i\n", s->framenum); + return 1; + } + + // palette goes interleaved with special flag + if (frameHead[0] == 2) + { + if (compsize == 768) + { + memcpy(s->frame_palette, s->frame_compressed, 768); + for(i = 0; i < 768; i++) + s->frame_palette[i] = (unsigned char)(bound(0, (s->frame_palette[i] * s->colorscale) - s->colorsub, 255)); + goto readframe; } - else - s->error = JAMDECODEERROR_READERROR; } else - s->error = DPVSIMPLEDECODEERROR_EOF; - return s->error; -} + { + // decode frame + // shift buffers to provide current and previous one, decode + b = s->frame_prev; + s->frame_prev = s->frame; + s->frame = b; + jam_decodeframe(s->frame_compressed, s->frame, s->frame_prev, outsize, frameHead[4]); +#ifdef JAM_USELIBAVCODECSCALE + // make BGRA imagepixels from 8bit palettized frame + b = (unsigned char *)s->frame_output_buffer; + for(i = 0; i < s->framesize; i++) + { + *b++ = s->frame_palette[s->frame[i]*3 + 2]; + *b++ = s->frame_palette[s->frame[i]*3 + 1]; + *b++ = s->frame_palette[s->frame[i]*3]; + *b++ = 255; + } + // scale + AvCodec_FillPicture((AVPicture *)s->frame_output, (uint8_t *)s->frame_output_buffer, PIX_FMT_BGRA, s->framewidth, s->frameheight); + AvCodec_FillPicture((AVPicture *)s->frame_output_scale, (uint8_t *)imagedata, PIX_FMT_BGRA, s->info_imagewidth, s->info_imageheight); + SwsContext *scale_context = SwScale_GetCachedContext(NULL, s->framewidth, s->frameheight, PIX_FMT_BGRA, s->info_imagewidth, s->info_imageheight, PIX_FMT_BGRA, libavcodec_scalers[max(0, min(LIBAVCODEC_SCALERS, cl_video_libavcodec_scaler.integer))], NULL, NULL, NULL); + if (!scale_context) + { + Con_Printf("JamDecoder: LibAvcodec: error creating scale context frame %i\n", s->framenum); + return 1; + } + if (!SwScale_Scale(scale_context, s->frame_output->data, s->frame_output->linesize, 0, s->frameheight, s->frame_output_scale->data, s->frame_output_scale->linesize)) + Con_Printf("JamDecoder: LibAvcodec : error scaling frame\n", s->framenum); + SwScale_FreeContext(scale_context); +#else + // make BGRA imagepixels from 8bit palettized frame + b = (unsigned char *)imagedata; + for(i = 0; i < s->framesize; i++) + { + // bgra + *b++ = s->frame_palette[s->frame[i]*3 + 2]; + *b++ = s->frame_palette[s->frame[i]*3 + 1]; + *b++ = s->frame_palette[s->frame[i]*3]; + *b++ = 255; + } +#endif + } + return 0; +} \ No newline at end of file diff --git a/cl_video_libavw.c b/cl_video_libavw.c new file mode 100644 index 00000000..54238973 --- /dev/null +++ b/cl_video_libavw.c @@ -0,0 +1,390 @@ +/* + Libavcodec integration for Darkplaces by Timofeyev Pavel + + 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 2 + 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, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#include "common.h" +#ifdef _MSC_VERSION +#include "stdint.h" +#else +#include +#endif + +// scaler type +#define LIBAVW_SCALER_BILINEAR 0 +#define LIBAVW_SCALER_BICUBIC 1 +#define LIBAVW_SCALER_X 2 +#define LIBAVW_SCALER_POINT 3 +#define LIBAVW_SCALER_AREA 4 +#define LIBAVW_SCALER_BICUBLIN 5 +#define LIBAVW_SCALER_GAUSS 6 +#define LIBAVW_SCALER_SINC 7 +#define LIBAVW_SCALER_LANCZOS 8 +#define LIBAVW_SCALER_SPLINE 9 +// output format +#define LIBAVW_PIXEL_FORMAT_BGR 0 +#define LIBAVW_PIXEL_FORMAT_BGRA 1 +// print levels +#define LIBAVW_PRINT_WARNING 1 +#define LIBAVW_PRINT_ERROR 2 +#define LIBAVW_PRINT_FATAL 3 +#define LIBAVW_PRINT_PANIC 4 +// exported callback functions: +typedef void avwCallbackPrint(int, const char *); +typedef int avwCallbackIoRead(void *, uint8_t *, int); +typedef int64_t avwCallbackIoSeek(void *, int64_t, int); +typedef int64_t avwCallbackIoSeekSize(void *); +// exported functions: +int (*qLibAvW_Init)(avwCallbackPrint *printfunction); // init library, returns error code +const char *(*qLibAvW_ErrorString)(int errorcode); // get string for error code +const char *(*qLibAvW_AvcVersion)(void); // get a string containing libavcodec version wrapper was built for +float (*qLibAvW_Version)(void); // get wrapper version +int (*qLibAvW_CreateStream)(void **stream); // create stream, returns error code +void (*qLibAvW_RemoveStream)(void *stream); // flush and remove stream +int (*qLibAvW_StreamGetVideoWidth)(void *stream); // get video parameters of stream +int (*qLibAvW_StreamGetVideoHeight)(void *stream); +double (*qLibAvW_StreamGetFramerate)(void *stream); +int (*qLibAvW_StreamGetError)(void *stream); // get last function errorcode from stream +// simple API to play video +int (*qLibAvW_PlayVideo)(void *stream, void *file, avwCallbackIoRead *IoRead, avwCallbackIoSeek *IoSeek, avwCallbackIoSeekSize *IoSeekSize); +int (*qLibAvW_PlaySeekNextFrame)(void *stream); +int (*qLibAvW_PlayGetFrameImage)(void *stream, int pixel_format, void *imagedata, int imagewidth, int imageheight, int scaler); + +static dllfunction_t libavwfuncs[] = +{ + {"LibAvW_Init", (void **) &qLibAvW_Init }, + {"LibAvW_ErrorString", (void **) &qLibAvW_ErrorString }, + {"LibAvW_AvcVersion", (void **) &qLibAvW_AvcVersion }, + {"LibAvW_Version", (void **) &qLibAvW_Version }, + {"LibAvW_CreateStream", (void **) &qLibAvW_CreateStream }, + {"LibAvW_RemoveStream", (void **) &qLibAvW_RemoveStream }, + {"LibAvW_StreamGetVideoWidth", (void **) &qLibAvW_StreamGetVideoWidth }, + {"LibAvW_StreamGetVideoHeight",(void **) &qLibAvW_StreamGetVideoHeight }, + {"LibAvW_StreamGetFramerate", (void **) &qLibAvW_StreamGetFramerate }, + {"LibAvW_StreamGetError", (void **) &qLibAvW_StreamGetError }, + {"LibAvW_PlayVideo", (void **) &qLibAvW_PlayVideo }, + {"LibAvW_PlaySeekNextFrame", (void **) &qLibAvW_PlaySeekNextFrame }, + {"LibAvW_PlayGetFrameImage", (void **) &qLibAvW_PlayGetFrameImage }, + {NULL, NULL} +}; + +const char* dllnames_libavw[] = +{ +#if defined(WIN32) + "libavw.dll", +#elif defined(MACOSX) + "libavw.dylib", +#else + "libavw.so.1", + "libavw.so", +#endif + NULL +}; + +static dllhandle_t libavw_dll = NULL; + +// DP videostream +typedef struct libavwstream_s +{ + qfile_t *file; + double info_framerate; + unsigned int info_imagewidth; + unsigned int info_imageheight; + double info_aspectratio; + void *stream; + + // channel the sound file is being played on + sfx_t *sfx; + int sndchan; + int sndstarted; +} +libavwstream_t; + +cvar_t cl_video_libavw_minwidth = {CVAR_SAVE, "cl_video_libavw_minwidth", "0", "if videos width is lesser than minimal, thay will be upscaled"}; +cvar_t cl_video_libavw_minheight = {CVAR_SAVE, "cl_video_libavw_minheight", "0", "if videos height is lesser than minimal, thay will be upscaled"}; +cvar_t cl_video_libavw_scaler = {CVAR_SAVE, "cl_video_libavw_scaler", "1", "selects a scaler for libavcode played videos. Scalers are: 0 - bilinear, 1 - bicubic, 2 - x, 3 - point, 4 - area, 5 - bicublin, 6 - gauss, 7 - sinc, 8 - lanczos, 9 - spline."}; + +// video extensions +const char* libavw_extensions[] = +{ + "ogv", + "avi", + "mpg", + "mp4", + "mkv", + "webm", + "bik", + "roq", + "flv", + "wmv", + "mpeg", + "mjpeg", + "mpeg4", + NULL +}; + +/* +================================================================= + + Video decoding + a features that is not supported yet and likely to be done + - streaming audio from videofiles + - streaming subtitles + +================================================================= +*/ + +unsigned int libavw_getwidth(void *stream); +unsigned int libavw_getheight(void *stream); +double libavw_getframerate(void *stream); +double libavw_getaspectratio(void *stream); +void libavw_close(void *stream); + +int libavw_decodeframe(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow) +{ + int pixel_format = LIBAVW_PIXEL_FORMAT_BGR; + int errorcode; + + libavwstream_t *s = (libavwstream_t *)stream; + + // start sound + if (!s->sndstarted) + { + if (s->sfx != NULL) + s->sndchan = S_StartSound(-1, 0, s->sfx, vec3_origin, 1.0f, 0); + s->sndstarted = 1; + } + + // read frame + if (!qLibAvW_PlaySeekNextFrame(s->stream)) + { + // got error or file end + errorcode = qLibAvW_StreamGetError(s->stream); + if (errorcode) + Con_Printf("LibAvW: %s\n", qLibAvW_ErrorString(errorcode)); + return 1; + } + + // decode into bgr texture + if (bytesperpixel == 4) + pixel_format = LIBAVW_PIXEL_FORMAT_BGRA; + else if (bytesperpixel == 3) + pixel_format = LIBAVW_PIXEL_FORMAT_BGR; + else + { + Con_Printf("LibAvW: cannot determine pixel format for bpp %i\n", bytesperpixel); + return 1; + } + if (!qLibAvW_PlayGetFrameImage(s->stream, pixel_format, imagedata, s->info_imagewidth, s->info_imageheight, min(9, max(0, cl_video_libavw_scaler.integer)))) + Con_Printf("LibAvW: %s\n", qLibAvW_ErrorString(qLibAvW_StreamGetError(s->stream))); + return 0; +} + +// get stream info +unsigned int libavw_getwidth(void *stream) +{ + return ((libavwstream_t *)stream)->info_imagewidth; +} + +unsigned int libavw_getheight(void *stream) +{ + return ((libavwstream_t *)stream)->info_imageheight; +} + +double libavw_getframerate(void *stream) +{ + return ((libavwstream_t *)stream)->info_framerate; +} + +double libavw_getaspectratio(void *stream) +{ + return ((libavwstream_t *)stream)->info_aspectratio; +} + +// close stream +void libavw_close(void *stream) +{ + libavwstream_t *s = (libavwstream_t *)stream; + + if (s->stream) + qLibAvW_RemoveStream(s->stream); + s->stream = NULL; + if (s->file) + FS_Close(s->file); + s->file = NULL; + if (s->sndchan >= 0) + S_StopChannel(s->sndchan, true, true); + s->sndchan = -1; +} + +// IO wrapper +int LibAvW_FS_Read(void *opaque, uint8_t *buf, int buf_size) +{ + return FS_Read((qfile_t *)opaque, buf, buf_size); +} +int64_t LibAvW_FS_Seek(void *opaque, int64_t pos, int whence) +{ + return (int64_t)FS_Seek((qfile_t *)opaque, pos, whence); +} +int64_t LibAvW_FS_SeekSize(void *opaque) +{ + return (int64_t)FS_FileSize((qfile_t *)opaque); +} + +// open as DP video stream +void *LibAvW_OpenVideo(clvideo_t *video, char *filename, const char **errorstring) +{ + libavwstream_t *s; + char filebase[MAX_OSPATH], check[MAX_OSPATH]; + unsigned int i; + int errorcode; + char *wavename; + size_t len; + + if (!libavw_dll) + return NULL; + + // allocate stream + s = (libavwstream_t *)Z_Malloc(sizeof(libavwstream_t)); + s->sndchan = -1; + memset(s, 0, sizeof(libavwstream_t)); + if (s == NULL) + { + *errorstring = "unable to allocate memory for stream info structure"; + return NULL; + } + + // open file + s->file = FS_OpenVirtualFile(filename, true); + if (!s->file) + { + FS_StripExtension(filename, filebase, sizeof(filebase)); + // we tried .dpv, try another extensions + for (i = 0; libavw_extensions[i] != NULL; i++) + { + dpsnprintf(check, sizeof(check), "%s.%s", filebase, libavw_extensions[i]); + s->file = FS_OpenVirtualFile(check, true); + if (s->file) + break; + } + if (!s->file) + { + *errorstring = "unable to open videofile"; + libavw_close(s); + Z_Free(s); + return NULL; + } + } + + // allocate libavw stream + if ((errorcode = qLibAvW_CreateStream(&s->stream))) + { + *errorstring = qLibAvW_ErrorString(errorcode); + libavw_close(s); + Z_Free(s); + return NULL; + } + + // open video for playing + if (!qLibAvW_PlayVideo(s->stream, s->file, &LibAvW_FS_Read, &LibAvW_FS_Seek, &LibAvW_FS_SeekSize)) + { + *errorstring = qLibAvW_ErrorString(qLibAvW_StreamGetError(s->stream)); + libavw_close(s); + Z_Free(s); + return NULL; + } + + // all right, start codec + s->info_imagewidth = qLibAvW_StreamGetVideoWidth(s->stream); + s->info_imageheight = qLibAvW_StreamGetVideoHeight(s->stream); + s->info_framerate = qLibAvW_StreamGetFramerate(s->stream); + s->info_aspectratio = (double)s->info_imagewidth / (double)s->info_imageheight; + video->close = libavw_close; + video->getwidth = libavw_getwidth; + video->getheight = libavw_getheight; + video->getframerate = libavw_getframerate; + video->decodeframe = libavw_decodeframe; + video->getaspectratio = libavw_getaspectratio; + + // apply min-width, min-height, keep aspect rate + if (cl_video_libavw_minwidth.integer > 0) + s->info_imagewidth = max(s->info_imagewidth, (unsigned int)cl_video_libavw_minwidth.integer); + if (cl_video_libavw_minheight.integer > 0) + s->info_imageheight = max(s->info_imageheight, (unsigned int)cl_video_libavw_minheight.integer); + + // provide sound in separate .wav + len = strlen(filename) + 10; + wavename = (char *)Z_Malloc(len); + if (wavename) + { + FS_StripExtension(filename, wavename, len-1); + strlcat(wavename, ".wav", len); + s->sfx = S_PrecacheSound(wavename, false, false); + s->sndchan = -1; + Z_Free(wavename); + } + return s; +} + +void libavw_message(int level, const char *message) +{ + if (level == LIBAVW_PRINT_WARNING) + Con_Printf("LibAvcodec warning: %s\n", message); + else if (level == LIBAVW_PRINT_ERROR) + Con_Printf("LibAvcodec error: %s\n", message); + else if (level == LIBAVW_PRINT_FATAL) + Con_Printf("LibAvcodec fatal error: %s\n", message); + else + Con_Printf("LibAvcodec panic: %s\n", message); +} + +qboolean LibAvW_OpenLibrary(void) +{ + int errorcode; + + // COMMANDLINEOPTION: Video: -nolibavw disables libavcodec wrapper support + if (COM_CheckParm("-nolibavw")) + return false; + + // load DLL's + Sys_LoadLibrary(dllnames_libavw, &libavw_dll, libavwfuncs); + if (!libavw_dll) + return false; + + // initialize libav wrapper + if ((errorcode = qLibAvW_Init(&libavw_message))) + { + Con_Printf("LibAvW failed to initialize: %s\n", qLibAvW_ErrorString(errorcode)); + Sys_UnloadLibrary(&libavw_dll); + } + + Cvar_RegisterVariable(&cl_video_libavw_minwidth); + Cvar_RegisterVariable(&cl_video_libavw_minheight); + Cvar_RegisterVariable(&cl_video_libavw_scaler); + + return true; +} + +void LibAvW_CloseLibrary(void) +{ + Sys_UnloadLibrary(&libavw_dll); +} + diff --git a/dpvsimpledecode.c b/dpvsimpledecode.c index a72bf364..ed89260a 100644 --- a/dpvsimpledecode.c +++ b/dpvsimpledecode.c @@ -230,6 +230,7 @@ typedef struct dpvsimpledecodestream_s unsigned int info_imageBmask; unsigned int info_imageBshift; unsigned int info_imagesize; + double info_aspectratio; // current video frame (needed because of delta compression) int videoframenum; @@ -360,6 +361,7 @@ void *dpvsimpledecode_open(clvideo_t *video, char *filename, const char **errors s->info_imagewidth = hz_bitstream_read_short(s->framedatablocks); s->info_imageheight = hz_bitstream_read_short(s->framedatablocks); s->info_framerate = (double) hz_bitstream_read_int(s->framedatablocks) * (1.0 / 65536.0); + s->info_aspectratio = (double)s->info_imagewidth / (double)s->info_imageheight; if (s->info_framerate > 0.0) { @@ -391,6 +393,7 @@ void *dpvsimpledecode_open(clvideo_t *video, char *filename, const char **errors video->getheight = dpvsimpledecode_getheight; video->getframerate = dpvsimpledecode_getframerate; video->decodeframe = dpvsimpledecode_video; + video->getaspectratio = dpvsimpledecode_getaspectratio; return s; } @@ -512,6 +515,13 @@ double dpvsimpledecode_getframerate(void *stream) return s->info_framerate; } +// return aspect ratio of the stream +double dpvsimpledecode_getaspectratio(void *stream) +{ + dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; + return s->info_aspectratio; +} + static int dpvsimpledecode_convertpixels(dpvsimpledecodestream_t *s, void *imagedata, int imagebytesperrow) { unsigned int a, x, y, width, height; diff --git a/dpvsimpledecode.h b/dpvsimpledecode.h index 621b001c..0b0ac35a 100644 --- a/dpvsimpledecode.h +++ b/dpvsimpledecode.h @@ -40,6 +40,9 @@ unsigned int dpvsimpledecode_getheight(void *stream); // returns the framerate of the stream double dpvsimpledecode_getframerate(void *stream); +// returns aspect ratio of the stream +double dpvsimpledecode_getaspectratio(void *stream); + // decodes a video frame to the supplied output pixels int dpvsimpledecode_video(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow); -- 2.39.2