diff options
author | neonloop | 2021-05-07 20:00:12 +0000 |
---|---|---|
committer | neonloop | 2021-05-07 20:00:12 +0000 |
commit | 7f6002caba3f0a6749820c2772161caf55b8d267 (patch) | |
tree | 1ed4bdd8c9ac897d1a3f77c223c1fd286dded458 /src/libs/video | |
download | uqm-7f6002caba3f0a6749820c2772161caf55b8d267.tar.gz uqm-7f6002caba3f0a6749820c2772161caf55b8d267.tar.bz2 uqm-7f6002caba3f0a6749820c2772161caf55b8d267.zip |
Initial commit (uqm-0.8.0)
Diffstat (limited to 'src/libs/video')
-rw-r--r-- | src/libs/video/Makeinfo | 3 | ||||
-rw-r--r-- | src/libs/video/dukvid.c | 748 | ||||
-rw-r--r-- | src/libs/video/dukvid.h | 36 | ||||
-rw-r--r-- | src/libs/video/legacyplayer.c | 81 | ||||
-rw-r--r-- | src/libs/video/vfileins.c | 28 | ||||
-rw-r--r-- | src/libs/video/video.c | 190 | ||||
-rw-r--r-- | src/libs/video/video.h | 56 | ||||
-rw-r--r-- | src/libs/video/videodec.c | 363 | ||||
-rw-r--r-- | src/libs/video/videodec.h | 124 | ||||
-rw-r--r-- | src/libs/video/vidintrn.h | 41 | ||||
-rw-r--r-- | src/libs/video/vidplayer.c | 481 | ||||
-rw-r--r-- | src/libs/video/vidplayer.h | 31 | ||||
-rw-r--r-- | src/libs/video/vresins.c | 186 |
13 files changed, 2368 insertions, 0 deletions
diff --git a/src/libs/video/Makeinfo b/src/libs/video/Makeinfo new file mode 100644 index 0000000..1282e49 --- /dev/null +++ b/src/libs/video/Makeinfo @@ -0,0 +1,3 @@ +uqm_CFILES="vfileins.c vresins.c video.c videodec.c vidplayer.c dukvid.c \ + legacyplayer.c" +uqm_HFILES="dukvid.h videodec.h video.h vidintrn.h vidplayer.h" diff --git a/src/libs/video/dukvid.c b/src/libs/video/dukvid.c new file mode 100644 index 0000000..d9cb8fa --- /dev/null +++ b/src/libs/video/dukvid.c @@ -0,0 +1,748 @@ +/* + * 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 the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* DUCK video player + * + * Status: fully functional + */ + +#include "video.h" +#include "dukvid.h" +#include <stdio.h> +#include <string.h> +#include "libs/uio.h" +#include "libs/memlib.h" +#include "endian_uqm.h" + +#define THIS_PTR TFB_VideoDecoder* This + +static const char* dukv_GetName (void); +static bool dukv_InitModule (int flags); +static void dukv_TermModule (void); +static uint32 dukv_GetStructSize (void); +static int dukv_GetError (THIS_PTR); +static bool dukv_Init (THIS_PTR, TFB_PixelFormat* fmt); +static void dukv_Term (THIS_PTR); +static bool dukv_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void dukv_Close (THIS_PTR); +static int dukv_DecodeNext (THIS_PTR); +static uint32 dukv_SeekFrame (THIS_PTR, uint32 frame); +static float dukv_SeekTime (THIS_PTR, float time); +static uint32 dukv_GetFrame (THIS_PTR); +static float dukv_GetTime (THIS_PTR); + +TFB_VideoDecoderFuncs dukv_DecoderVtbl = +{ + dukv_GetName, + dukv_InitModule, + dukv_TermModule, + dukv_GetStructSize, + dukv_GetError, + dukv_Init, + dukv_Term, + dukv_Open, + dukv_Close, + dukv_DecodeNext, + dukv_SeekFrame, + dukv_SeekTime, + dukv_GetFrame, + dukv_GetTime, +}; + +typedef struct tfb_duckvideoheader +{ + uint32 version; + uint32 scrn_x_ofs; // horz screen offset in pixels + uint32 scrn_y_ofs; // vert screen offset in pixels + uint16 wb, hb; // width + height in blocks + sint16 lumas[8]; // future luminance deltas + sint16 chromas[8]; // future chrominance deltas + +} TFB_DuckVideoHeader; + +#define NUM_VEC_ITEMS 0x010 +#define NUM_VECTORS 0x100 + +typedef struct tfb_duckvideodeltas +{ + sint32 lumas[NUM_VECTORS][NUM_VEC_ITEMS]; + sint32 chromas[NUM_VECTORS][NUM_VEC_ITEMS]; + +} TFB_DuckVideoDeltas; + +// specific video decoder struct derived from TFB_VideoDecoder +// the only sane way in C one can :) +typedef struct tfb_duckvideodecoder +{ + // always the first member + TFB_VideoDecoder decoder; + + sint32 last_error; + uio_DirHandle* basedir; + char* basename; + uio_Stream *stream; + +// loaded from disk + uint32* frames; + uint32 cframes; + uint32 iframe; + uint32 version; + uint32 wb, hb; // width, height in blocks + +// generated + TFB_DuckVideoDeltas d; + + uint8* inbuf; + uint32* decbuf; + +} TFB_DuckVideoDecoder; + +#define DUCK_GENERAL_FPS 14.622f +#define DUCK_MAX_FRAME_SIZE 0x8000U +#define DUCK_END_OF_SEQUENCE 1 + +static void +dukv_DecodeFrame (uint8* src_p, uint32* dst_p, uint32 wb, uint32 hb, + TFB_DuckVideoDeltas* deltas) +{ + int iVec; + int iSeq; + uint32 x, y; + sint32 w; + uint32 *d_p0, *d_p1; + int i; + + w = wb * 4; + + iVec = *(src_p++); + iSeq = 0; + + for (y = 0; y < hb; ++y) + { + sint32 accum0, accum1, corr, corr0, corr1, delta; + sint32 pix[4]; + + d_p0 = dst_p + y * w * 2; + d_p1 = d_p0 + w; + + accum0 = 0; + accum1 = 0; + corr0 = 0; + corr1 = 0; + + for (x = 0; x < wb; ++x) + { + if (y == 0) + { + pix[0] = pix[1] = pix[2] = pix[3] = 0; + } + else + { + uint32* p_p = d_p0 - w; + pix[0] = p_p[0]; + pix[1] = p_p[1]; + pix[2] = p_p[2]; + pix[3] = p_p[3]; + } + + // start with chroma delta + delta = deltas->chromas[iVec][iSeq++]; + iSeq++; // correctors ignored + + accum0 += delta >> 1; + if (delta & 1) + { + iVec = *(src_p++); + iSeq = 0; + } + + // line 0 + for (i = 0; i < 4; ++i, ++d_p0) + { + delta = deltas->lumas[iVec][iSeq++]; + corr = deltas->lumas[iVec][iSeq++]; + + accum0 += delta >> 1; + corr0 ^= corr; + pix[i] += accum0; + pix[i] ^= corr0; + + if (delta & 1) + { + iVec = *(src_p++); + iSeq = 0; + } + + *d_p0 = pix[i]; + } + + // line 1 + for (i = 0; i < 4; ++i, ++d_p1) + { + delta = deltas->lumas[iVec][iSeq++]; + corr = deltas->lumas[iVec][iSeq++]; + + accum1 += delta >> 1; + corr1 ^= corr; + pix[i] += accum1; + pix[i] ^= corr1; + + if (delta & 1) + { + iVec = *(src_p++); + iSeq = 0; + } + + *d_p1 = pix[i]; + } + } + } +} + +static void +dukv_DecodeFrameV3 (uint8* src_p, uint32* dst_p, uint32 wb, uint32 hb, + TFB_DuckVideoDeltas* deltas) +{ + int iVec; + int iSeq; + uint32 x, y; + sint32 w; + uint32* d_p; + int i; + + iVec = *(src_p++); + iSeq = 0; + + hb *= 2; + w = wb * 4; + + for (y = 0; y < hb; ++y) + { + sint32 accum, delta, pix; + + d_p = dst_p + y * w; + + accum = 0; + + for (x = 0; x < wb; ++x) + { + // start with chroma delta + delta = deltas->chromas[iVec][iSeq]; + iSeq += 2; // correctors ignored + + accum += delta >> 1; + + if (delta & DUCK_END_OF_SEQUENCE) + { + iVec = *(src_p++); + iSeq = 0; + } + + for (i = 0; i < 4; ++i, ++d_p) + { + if (y == 0) + pix = 0; + else + pix = d_p[-w]; + + // get next luma delta + delta = deltas->lumas[iVec][iSeq]; + iSeq += 2; // correctors ignored + + accum += delta >> 1; + pix += accum; + + if (delta & DUCK_END_OF_SEQUENCE) + { + iVec = *(src_p++); + iSeq = 0; + } + + *d_p = pix; + } + } + } +} + +static bool +dukv_OpenStream (TFB_DuckVideoDecoder* dukv) +{ + char filename[280]; + + strcat (strcpy (filename, dukv->basename), ".duk"); + + return (dukv->stream = + uio_fopen (dukv->basedir, filename, "rb")) != NULL; +} + +static bool +dukv_ReadFrames (TFB_DuckVideoDecoder* dukv) +{ + char filename[280]; + uint32 i; + uio_Stream *fp; + + strcat (strcpy (filename, dukv->basename), ".frm"); + + if (!(fp = uio_fopen (dukv->basedir, filename, "rb"))) + return false; + + // get number of frames + uio_fseek (fp, 0, SEEK_END); + dukv->cframes = uio_ftell (fp) / sizeof (uint32); + uio_fseek (fp, 0, SEEK_SET); + dukv->frames = (uint32*) HMalloc (dukv->cframes * sizeof (uint32)); + + if (uio_fread (dukv->frames, sizeof (uint32), dukv->cframes, + fp) != dukv->cframes) + { + HFree (dukv->frames); + dukv->frames = 0; + return 0; + } + uio_fclose (fp); + + for (i = 0; i < dukv->cframes; ++i) + dukv->frames[i] = UQM_SwapBE32 (dukv->frames[i]); + + return true; +} + +static bool +dukv_ReadVectors (TFB_DuckVideoDecoder* dukv, uint8* vectors) +{ + uio_Stream *fp; + char filename[280]; + int ret; + + strcat (strcpy (filename, dukv->basename), ".tbl"); + + if (!(fp = uio_fopen (dukv->basedir, filename, "rb"))) + return false; + + ret = uio_fread (vectors, NUM_VEC_ITEMS, NUM_VECTORS, fp); + uio_fclose (fp); + + return ret == NUM_VECTORS; +} + +static bool +dukv_ReadHeader (TFB_DuckVideoDecoder* dukv, sint32* pl, sint32* pc) +{ + uio_Stream *fp; + char filename[280]; + int ret; + int i; + TFB_DuckVideoHeader hdr; + + strcat (strcpy (filename, dukv->basename), ".hdr"); + + if (!(fp = uio_fopen (dukv->basedir, filename, "rb"))) + return false; + + ret = uio_fread (&hdr, sizeof (hdr), 1, fp); + uio_fclose (fp); + if (!ret) + return false; + + dukv->version = UQM_SwapBE32 (hdr.version); + dukv->wb = UQM_SwapBE16 (hdr.wb); + dukv->hb = UQM_SwapBE16 (hdr.hb); + + for (i = 0; i < 8; ++i) + { + pl[i] = (sint16) UQM_SwapBE16 (hdr.lumas[i]); + pc[i] = (sint16) UQM_SwapBE16 (hdr.chromas[i]); + } + + dukv->decoder.w = dukv->wb * 4; + dukv->decoder.h = dukv->hb * 4; + + return true; +} + +static sint32 +dukv_make_delta (sint32* protos, bool is_chroma, int i1, int i2) +{ + sint32 d1, d2; + + if (!is_chroma) + { + // 0x421 is (r,g,b)=(1,1,1) in 15bit pixel coding + d1 = (protos[i1] >> 1) * 0x421; + d2 = (protos[i2] >> 1) * 0x421; + return ((d1 << 16) + d2) << 1; + } + else + { + d1 = (protos[i1] << 10) + protos[i2]; + return ((d1 << 16) + d1) << 1; + } +} + +static sint32 +dukv_make_corr (sint32* protos, bool is_chroma, int i1, int i2) +{ + sint32 d1, d2; + + if (!is_chroma) + { + d1 = (protos[i1] & 1) << 15; + d2 = (protos[i2] & 1) << 15; + return (d1 << 16) + d2; + } + else + { + return (i1 << 3) + i2; + } +} + +static void +dukv_DecodeVector (uint8* vec, sint32* p, bool is_chroma, sint32* deltas) +{ + int citems = vec[0]; + int i; + + for (i = 0; i < citems; i += 2, vec += 2, deltas += 2) + { + sint32 d = dukv_make_delta (p, is_chroma, vec[1], vec[2]); + + if (i == citems - 2) + d |= DUCK_END_OF_SEQUENCE; + + deltas[0] = d; + deltas[1] = dukv_make_corr (p, is_chroma, vec[1], vec[2]); + } + +} + +static void +dukv_InitDeltas (TFB_DuckVideoDecoder* dukv, uint8* vectors, + sint32* pl, sint32* pc) +{ + int i; + + for (i = 0; i < NUM_VECTORS; ++i) + { + uint8* vector = vectors + i * NUM_VEC_ITEMS; + dukv_DecodeVector (vector, pl, false, dukv->d.lumas[i]); + dukv_DecodeVector (vector, pc, true, dukv->d.chromas[i]); + } +} + +static inline uint32 +dukv_PixelConv (uint16 pix, const TFB_PixelFormat* fmt) +{ + uint32 r, g, b; + + r = (pix >> 7) & 0xf8; + g = (pix >> 2) & 0xf8; + b = (pix << 3) & 0xf8; + + return + ((r >> fmt->Rloss) << fmt->Rshift) | + ((g >> fmt->Gloss) << fmt->Gshift) | + ((b >> fmt->Bloss) << fmt->Bshift); +} + +static void +dukv_RenderFrame (THIS_PTR) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + const TFB_PixelFormat* fmt = This->format; + uint32 h, x, y; + uint32* dec = dukv->decbuf; + + h = dukv->decoder.h / 2; + + // separate bpp versions for speed + switch (fmt->BytesPerPixel) + { + case 2: + { + for (y = 0; y < h; ++y) + { + uint16 *dst0, *dst1; + + dst0 = (uint16*) This->callbacks.GetCanvasLine (This, y * 2); + dst1 = (uint16*) This->callbacks.GetCanvasLine (This, y * 2 + 1); + + for (x = 0; x < dukv->decoder.w; ++x, ++dec, ++dst0, ++dst1) + { + uint32 pair = *dec; + *dst0 = dukv_PixelConv ((uint16)(pair >> 16), fmt); + *dst1 = dukv_PixelConv ((uint16)(pair & 0xffff), fmt); + } + } + break; + } + case 3: + { + for (y = 0; y < h; ++y) + { + uint8 *dst0, *dst1; + + dst0 = (uint8*) This->callbacks.GetCanvasLine (This, y * 2); + dst1 = (uint8*) This->callbacks.GetCanvasLine (This, y * 2 + 1); + + for (x = 0; x < dukv->decoder.w; + ++x, ++dec, dst0 += 3, dst1 += 3) + { + uint32 pair = *dec; + *(uint32*)dst0 = + dukv_PixelConv ((uint16)(pair >> 16), fmt); + *(uint32*)dst1 = + dukv_PixelConv ((uint16)(pair & 0xffff), fmt); + } + } + break; + } + case 4: + { + for (y = 0; y < h; ++y) + { + uint32 *dst0, *dst1; + + dst0 = (uint32*) This->callbacks.GetCanvasLine (This, y * 2); + dst1 = (uint32*) This->callbacks.GetCanvasLine (This, y * 2 + 1); + + for (x = 0; x < dukv->decoder.w; ++x, ++dec, ++dst0, ++dst1) + { + uint32 pair = *dec; + *dst0 = dukv_PixelConv ((uint16)(pair >> 16), fmt); + *dst1 = dukv_PixelConv ((uint16)(pair & 0xffff), fmt); + } + } + break; + } + default: + ; + } +} + +static const char* +dukv_GetName (void) +{ + return "DukVid"; +} + +static bool +dukv_InitModule (int flags) +{ + // no flags are defined for now + return true; + + (void)flags; // dodge compiler warning +} + +static void +dukv_TermModule (void) +{ + // do an extensive search on the word 'nothing' +} + +static uint32 +dukv_GetStructSize (void) +{ + return sizeof (TFB_DuckVideoDecoder); +} + +static int +dukv_GetError (THIS_PTR) +{ + return This->error; +} + +static bool +dukv_Init (THIS_PTR, TFB_PixelFormat* fmt) +{ + This->format = fmt; + This->audio_synced = true; + return true; +} + +static void +dukv_Term (THIS_PTR) +{ + //TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + + dukv_Close (This); +} + +static bool +dukv_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + char* pext; + sint32 lumas[8], chromas[8]; + uint8* vectors; + + dukv->basedir = dir; + dukv->basename = HMalloc (strlen (filename) + 1); + strcpy (dukv->basename, filename); + pext = strrchr (dukv->basename, '.'); + if (pext) // strip extension + *pext = 0; + + vectors = HMalloc (NUM_VEC_ITEMS * NUM_VECTORS); + + if (!dukv_OpenStream (dukv) + || !dukv_ReadFrames (dukv) + || !dukv_ReadHeader (dukv, lumas, chromas) + || !dukv_ReadVectors (dukv, vectors)) + { + HFree (vectors); + dukv_Close (This); + dukv->last_error = dukve_BadFile; + return false; + } + + dukv_InitDeltas (dukv, vectors, lumas, chromas); + HFree (vectors); + + This->length = (float) dukv->cframes / DUCK_GENERAL_FPS; + This->frame_count = dukv->cframes; + This->interframe_wait = (uint32) (1000.0 / DUCK_GENERAL_FPS); + + dukv->inbuf = HMalloc (DUCK_MAX_FRAME_SIZE); + dukv->decbuf = HMalloc ( + dukv->decoder.w * dukv->decoder.h * sizeof (uint16)); + + return true; +} + +static void +dukv_Close (THIS_PTR) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + + if (dukv->basename) + { + HFree (dukv->basename); + dukv->basename = NULL; + } + if (dukv->frames) + { + HFree (dukv->frames); + dukv->frames = NULL; + } + if (dukv->stream) + { + uio_fclose (dukv->stream); + dukv->stream = NULL; + } + if (dukv->inbuf) + { + HFree (dukv->inbuf); + dukv->inbuf = NULL; + } + if (dukv->decbuf) + { + HFree (dukv->decbuf); + dukv->decbuf = NULL; + } +} + +static int +dukv_DecodeNext (THIS_PTR) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + uint32 fh[2]; + uint32 vofs; + uint32 vsize; + uint16 ver; + + if (!dukv->stream || dukv->iframe >= dukv->cframes) + return 0; + + uio_fseek (dukv->stream, dukv->frames[dukv->iframe], SEEK_SET); + if (uio_fread (&fh, sizeof (fh), 1, dukv->stream) != 1) + { + dukv->last_error = dukve_EOF; + return 0; + } + + vofs = UQM_SwapBE32 (fh[0]); + vsize = UQM_SwapBE32 (fh[1]); + if (vsize > DUCK_MAX_FRAME_SIZE) + { + dukv->last_error = dukve_OutOfBuf; + return -1; + } + + uio_fseek (dukv->stream, vofs, SEEK_CUR); + if (uio_fread (dukv->inbuf, 1, vsize, dukv->stream) != vsize) + { + dukv->last_error = dukve_EOF; + return 0; + } + + ver = UQM_SwapBE16 (*(uint16*)dukv->inbuf); + if (ver == 0x0300) + dukv_DecodeFrameV3 (dukv->inbuf + 0x10, dukv->decbuf, + dukv->wb, dukv->hb, &dukv->d); + else + dukv_DecodeFrame (dukv->inbuf + 0x10, dukv->decbuf, + dukv->wb, dukv->hb, &dukv->d); + + dukv->iframe++; + + This->callbacks.BeginFrame (This); + dukv_RenderFrame (This); + This->callbacks.EndFrame (This); + + if (!This->audio_synced) + This->callbacks.SetTimer (This, (uint32) (1000.0f / DUCK_GENERAL_FPS)); + + return 1; +} + +static uint32 +dukv_SeekFrame (THIS_PTR, uint32 frame) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + + if (frame > dukv->cframes) + frame = dukv->cframes; // EOS + + return dukv->iframe = frame; +} + +static float +dukv_SeekTime (THIS_PTR, float time) +{ + //TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + uint32 frame = (uint32) (time * DUCK_GENERAL_FPS); + + // Note that DUCK_GENERAL_FPS is a float constant + return dukv_SeekFrame (This, frame) / DUCK_GENERAL_FPS; +} + +static uint32 +dukv_GetFrame (THIS_PTR) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + + return dukv->iframe; +} + +static float +dukv_GetTime (THIS_PTR) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + + return (float) dukv->iframe / DUCK_GENERAL_FPS; +} diff --git a/src/libs/video/dukvid.h b/src/libs/video/dukvid.h new file mode 100644 index 0000000..a6d70b0 --- /dev/null +++ b/src/libs/video/dukvid.h @@ -0,0 +1,36 @@ +/* + * 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 the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_VIDEO_DUKVID_H_ +#define LIBS_VIDEO_DUKVID_H_ + +#include "libs/video/videodec.h" + +extern TFB_VideoDecoderFuncs dukv_DecoderVtbl; + +typedef enum +{ + // positive values are the same as in errno + dukve_None = 0, + dukve_Unknown = -1, + dukve_BadFile = -2, + dukve_BadArg = -3, + dukve_OutOfBuf = -4, + dukve_EOF = -5, + dukve_Other = -1000, +} DukVid_Error; + +#endif // LIBS_VIDEO_DUKVID_H_ diff --git a/src/libs/video/legacyplayer.c b/src/libs/video/legacyplayer.c new file mode 100644 index 0000000..5dfddad --- /dev/null +++ b/src/libs/video/legacyplayer.c @@ -0,0 +1,81 @@ +/* + * 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 the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "vidintrn.h" +#include "video.h" +#include "vidplayer.h" +#include "libs/memlib.h" + + +LEGACY_VIDEO_REF +PlayLegacyVideo (LEGACY_VIDEO vid) +{ + const char *name, *audname, *speechname; + uint32 loopframe; + LEGACY_VIDEO_REF ref; + VIDEO_TYPE type; + + if (!vid) + return NULL; + ref = HCalloc (sizeof (*ref)); + if (!ref) + return NULL; + name = vid->video; + audname = vid->audio; + speechname = vid->speech; + loopframe = vid->loop; + + ref->vidref = LoadVideoFile (name); + if (!ref->vidref) + return NULL; + if (audname) + ref->audref = LoadMusicFile (audname); + if (speechname) + ref->speechref = LoadMusicFile (speechname); + + type = VidPlayEx (ref->vidref, ref->audref, ref->speechref, loopframe); + if (type == NO_FMV) + { // Video failed to start + StopLegacyVideo (ref); + return NULL; + } + + return ref; +} + +void +StopLegacyVideo (LEGACY_VIDEO_REF ref) +{ + if (!ref) + return; + VidStop (); + + DestroyVideo (ref->vidref); + if (ref->speechref) + DestroyMusic (ref->speechref); + if (ref->audref) + DestroyMusic (ref->audref); + + HFree (ref); +} + +BOOLEAN +PlayingLegacyVideo (LEGACY_VIDEO_REF ref) +{ + if (!ref) + return FALSE; + return TFB_VideoPlaying (ref->vidref); +} diff --git a/src/libs/video/vfileins.c b/src/libs/video/vfileins.c new file mode 100644 index 0000000..26cb7c1 --- /dev/null +++ b/src/libs/video/vfileins.c @@ -0,0 +1,28 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * 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 the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "libs/vidlib.h" +#include "video.h" + +VIDEO_REF +LoadVideoFile (const char *pStr) +{ + return _init_video_file (pStr); +} + + diff --git a/src/libs/video/video.c b/src/libs/video/video.c new file mode 100644 index 0000000..dd4cd46 --- /dev/null +++ b/src/libs/video/video.c @@ -0,0 +1,190 @@ +/* + * 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 the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "video.h" + +#include "vidintrn.h" +#include "options.h" +#include "vidplayer.h" +#include "libs/memlib.h" +#include "libs/sndlib.h" + + +#define NULL_VIDEO_REF (0) +static VIDEO_REF _cur_video = NULL_VIDEO_REF; +static MUSIC_REF _cur_speech = 0; + +BOOLEAN +InitVideoPlayer (BOOLEAN useCDROM) + //useCDROM doesn't really apply to us +{ + TFB_PixelFormat fmt; + + TFB_DrawCanvas_GetScreenFormat (&fmt); + if (!VideoDecoder_Init (0, fmt.BitsPerPixel, fmt.Rmask, + fmt.Gmask, fmt.Bmask, 0)) + return FALSE; + + return TFB_InitVideoPlayer (); + + (void)useCDROM; /* dodge compiler warning */ +} + +void +UninitVideoPlayer (void) +{ + TFB_UninitVideoPlayer (); + VideoDecoder_Uninit (); +} + +void +VidStop (void) +{ + if (_cur_speech) + snd_StopSpeech (); + if (_cur_video) + TFB_StopVideo (_cur_video); + _cur_speech = 0; + _cur_video = NULL_VIDEO_REF; +} + +VIDEO_REF +VidPlaying (void) + // this should just probably return BOOLEAN +{ + if (!_cur_video) + return NULL_VIDEO_REF; + + if (TFB_VideoPlaying (_cur_video)) + return _cur_video; + + return NULL_VIDEO_REF; +} + +BOOLEAN +VidProcessFrame (void) +{ + if (!_cur_video) + return FALSE; + return TFB_ProcessVideoFrame (_cur_video); +} + +// return current video position in milliseconds +DWORD +VidGetPosition (void) +{ + if (!VidPlaying ()) + return 0; + return TFB_GetVideoPosition (_cur_video); +} + +BOOLEAN +VidSeek (DWORD pos) + // pos in milliseconds +{ + if (!VidPlaying ()) + return FALSE; + return TFB_SeekVideo (_cur_video, pos); +} + +VIDEO_TYPE +VidPlayEx (VIDEO_REF vid, MUSIC_REF AudRef, MUSIC_REF SpeechRef, + DWORD LoopFrame) +{ + VIDEO_TYPE ret; + + if (!vid) + return NO_FMV; + + if (AudRef) + { + if (vid->hAudio) + DestroyMusic (vid->hAudio); + vid->hAudio = AudRef; + vid->decoder->audio_synced = FALSE; + } + + vid->loop_frame = LoopFrame; + vid->loop_to = 0; + + if (_cur_speech) + snd_StopSpeech (); + if (_cur_video) + TFB_StopVideo (_cur_video); + _cur_speech = 0; + _cur_video = NULL_VIDEO_REF; + + // play video in the center of the screen + if (TFB_PlayVideo (vid, (ScreenWidth - vid->w) / 2, + (ScreenHeight - vid->h) / 2)) + { + _cur_video = vid; + ret = SOFTWARE_FMV; + if (SpeechRef) + { + snd_PlaySpeech (SpeechRef); + _cur_speech = SpeechRef; + } + } + else + { + ret = NO_FMV; + } + + return ret; +} + +VIDEO_TYPE +VidPlay (VIDEO_REF VidRef) +{ + return VidPlayEx (VidRef, 0, 0, VID_NO_LOOP); +} + +VIDEO_REF +_init_video_file (const char *pStr) +{ + TFB_VideoClip* vid; + TFB_VideoDecoder* dec; + + dec = VideoDecoder_Load (contentDir, pStr); + if (!dec) + return NULL_VIDEO_REF; + + vid = HCalloc (sizeof (*vid)); + vid->decoder = dec; + vid->length = dec->length; + vid->w = vid->decoder->w; + vid->h = vid->decoder->h; + vid->guard = CreateMutex ("video guard", SYNC_CLASS_VIDEO); + + return (VIDEO_REF) vid; +} + +BOOLEAN +DestroyVideo (VIDEO_REF vid) +{ + if (!vid) + return FALSE; + + // just some armouring; should already be stopped + TFB_StopVideo (vid); + + VideoDecoder_Free (vid->decoder); + DestroyMutex (vid->guard); + HFree (vid); + + return TRUE; +} diff --git a/src/libs/video/video.h b/src/libs/video/video.h new file mode 100644 index 0000000..5e76aa4 --- /dev/null +++ b/src/libs/video/video.h @@ -0,0 +1,56 @@ +/* + * 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 the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_VIDEO_VIDEO_H_ +#define LIBS_VIDEO_VIDEO_H_ + +#include "libs/vidlib.h" +#include "libs/sndlib.h" +#include "libs/graphics/tfb_draw.h" +#include "types.h" +#include "videodec.h" +#include "libs/sound/sound.h" + + +typedef struct tfb_videoclip +{ + TFB_VideoDecoder *decoder; // decoder to read from + float length; // total length of clip seconds + uint32 w, h; + + // video player data + RECT dst_rect; // destination screen rect + RECT src_rect; // source rect + MUSIC_REF hAudio; + uint32 frame_time; // time when next frame should be rendered + TFB_Image* frame; // frame preped and optimized for rendering + uint32 cur_frame; // index of frame currently displayed + bool playing; + bool own_audio; + uint32 loop_frame; // frame index to loop from + uint32 loop_to; // frame index to loop to + + Mutex guard; + uint32 want_frame; // audio-signaled desired frame index + int lag_cnt; // N of frames video is behind or ahead of audio + + void* data; // user-defined data + +} TFB_VideoClip; + +extern VIDEO_REF _init_video_file(const char *pStr); + +#endif // LIBS_VIDEO_VIDEO_H_ diff --git a/src/libs/video/videodec.c b/src/libs/video/videodec.c new file mode 100644 index 0000000..b99a442 --- /dev/null +++ b/src/libs/video/videodec.c @@ -0,0 +1,363 @@ +/* + * 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 the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <string.h> +#include "video.h" +#include "videodec.h" +#include "dukvid.h" +#include "libs/log.h" +#include "libs/memlib.h" + +#define MAX_REG_DECODERS 31 + +static bool vd_inited = false; +static TFB_PixelFormat vd_vidfmt; +static int vd_flags = 0; + +struct TFB_RegVideoDecoder +{ + bool builtin; + bool used; // ever used indicator + const char* ext; + const TFB_VideoDecoderFuncs* funcs; +}; +static TFB_RegVideoDecoder vd_decoders[MAX_REG_DECODERS + 1] = +{ + {true, true, "duk", &dukv_DecoderVtbl}, + {false, false, NULL, NULL}, // null term +}; + +static void vd_computeMasks (uint32 mask, DWORD* shift, DWORD* loss); + +const char* +VideoDecoder_GetName (TFB_VideoDecoder *decoder) +{ + if (!decoder || !decoder->funcs) + return "(Null)"; + return decoder->funcs->GetName (); +} + +bool +VideoDecoder_Init (int flags, int depth, uint32 Rmask, uint32 Gmask, + uint32 Bmask, uint32 Amask) +{ + TFB_RegVideoDecoder* info; + + vd_inited = false; + + if (depth < 15 || depth > 32) + { + log_add (log_Error, "VideoDecoder_Init: " + "Unsupported video depth %d", depth); + return false; + } + + if ((Rmask & Gmask) || (Rmask & Bmask) || (Rmask & Amask) || + (Gmask & Bmask) || (Gmask & Amask) || (Bmask & Amask)) + { + log_add (log_Error, "VideoDecoder_Init: Invalid channel masks"); + return false; + } + + // BEGIN: adapted from SDL + vd_vidfmt.BitsPerPixel = depth; + vd_vidfmt.BytesPerPixel = (depth + 7) / 8; + vd_vidfmt.Rmask = Rmask; + vd_vidfmt.Gmask = Gmask; + vd_vidfmt.Bmask = Bmask; + vd_vidfmt.Amask = Amask; + vd_computeMasks (Rmask, &vd_vidfmt.Rshift, &vd_vidfmt.Rloss); + vd_computeMasks (Gmask, &vd_vidfmt.Gshift, &vd_vidfmt.Gloss); + vd_computeMasks (Bmask, &vd_vidfmt.Bshift, &vd_vidfmt.Bloss); + vd_computeMasks (Amask, &vd_vidfmt.Ashift, &vd_vidfmt.Aloss); + // END: adapted from SDL + + // init built-in decoders + for (info = vd_decoders; info->ext; info++) + { + if (!info->funcs->InitModule (flags)) + { + log_add (log_Error, "VideoDecoder_Init(): " + "%s video decoder init failed", + info->funcs->GetName ()); + } + } + + vd_flags = flags; + vd_inited = true; + + return true; +} + +void +VideoDecoder_Uninit (void) +{ + TFB_RegVideoDecoder* info; + + // uninit all decoders + // and unregister loaded decoders + for (info = vd_decoders; info->used; info++) + { + if (info->ext) // check if present + info->funcs->TermModule (); + + if (!info->builtin) + { + info->used = false; + info->ext = NULL; + } + } + + vd_inited = false; +} + +TFB_RegVideoDecoder* +VideoDecoder_Register (const char* fileext, TFB_VideoDecoderFuncs* decvtbl) +{ + TFB_RegVideoDecoder* info; + TFB_RegVideoDecoder* newslot = NULL; + + if (!decvtbl) + { + log_add (log_Warning, "VideoDecoder_Register(): Null decoder table"); + return NULL; + } + if (!fileext) + { + log_add (log_Warning, "VideoDecoder_Register(): Bad file type for %s", + decvtbl->GetName ()); + return NULL; + } + + // check if extension already registered + for (info = vd_decoders; info->used && + (!info->ext || strcmp (info->ext, fileext) != 0); + ++info) + { + // and pick up an empty slot (where available) + if (!newslot && !info->ext) + newslot = info; + } + + if (info >= vd_decoders + MAX_REG_DECODERS) + { + log_add (log_Warning, "VideoDecoder_Register(): Decoders limit reached"); + return NULL; + } + else if (info->ext) + { + log_add (log_Warning, "VideoDecoder_Register(): " + "'%s' decoder already registered (%s denied)", + fileext, decvtbl->GetName ()); + return NULL; + } + + if (!decvtbl->InitModule (vd_flags)) + { + log_add (log_Warning, "VideoDecoder_Register(): %s decoder init failed", + decvtbl->GetName ()); + return NULL; + } + + if (!newslot) + { + newslot = info; + newslot->used = true; + // make next one a term + info[1].builtin = false; + info[1].used = false; + info[1].ext = NULL; + } + + newslot->ext = fileext; + newslot->funcs = decvtbl; + + return newslot; +} + +void +VideoDecoder_Unregister (TFB_RegVideoDecoder* regdec) +{ + if (regdec < vd_decoders || regdec >= vd_decoders + MAX_REG_DECODERS || + !regdec->ext || !regdec->funcs) + { + log_add (log_Warning, "VideoDecoder_Unregister(): " + "Invalid or expired decoder passed"); + return; + } + + regdec->funcs->TermModule (); + regdec->ext = NULL; + regdec->funcs = NULL; +} + +const TFB_VideoDecoderFuncs* +VideoDecoder_Lookup (const char* fileext) +{ + TFB_RegVideoDecoder* info; + + for (info = vd_decoders; info->used && + (!info->ext || strcmp (info->ext, fileext) != 0); + ++info) + ; + return info->ext ? info->funcs : NULL; +} + +TFB_VideoDecoder* +VideoDecoder_Load (uio_DirHandle *dir, const char *filename) +{ + const char* pext; + TFB_RegVideoDecoder* info; + TFB_VideoDecoder* decoder; + + + if (!vd_inited) + return NULL; + + pext = strrchr (filename, '.'); + if (!pext) + { + log_add (log_Warning, "VideoDecoder_Load: Unknown file type"); + return NULL; + } + ++pext; + + for (info = vd_decoders; info->used && + (!info->ext || strcmp (info->ext, pext) != 0); + ++info) + ; + if (!info->ext) + { + log_add (log_Warning, "VideoDecoder_Load: Unsupported file type"); + return NULL; + } + + decoder = HCalloc (info->funcs->GetStructSize ()); + decoder->funcs = info->funcs; + if (!decoder->funcs->Init (decoder, &vd_vidfmt)) + { + log_add (log_Warning, "VideoDecoder_Load: " + "Cannot init '%s' decoder, code %d", + decoder->funcs->GetName (), + decoder->funcs->GetError (decoder)); + HFree (decoder); + return NULL; + } + + decoder->dir = dir; + decoder->filename = (char *) HMalloc (strlen (filename) + 1); + strcpy (decoder->filename, filename); + decoder->error = VIDEODECODER_OK; + + if (!decoder->funcs->Open (decoder, dir, filename)) + { + log_add (log_Warning, "VideoDecoder_Load: " + "'%s' decoder did not load %s, code %d", + decoder->funcs->GetName (), filename, + decoder->funcs->GetError (decoder)); + + VideoDecoder_Free (decoder); + return NULL; + } + + return decoder; +} + +// return: >0 = OK, 0 = EOF, <0 = Error +int +VideoDecoder_Decode (TFB_VideoDecoder *decoder) +{ + int ret; + + if (!decoder) + return 0; + + decoder->cur_frame = decoder->funcs->GetFrame (decoder); + decoder->pos = decoder->funcs->GetTime (decoder); + + ret = decoder->funcs->DecodeNext (decoder); + if (ret == 0) + decoder->error = VIDEODECODER_EOF; + else if (ret < 0) + decoder->error = VIDEODECODER_ERROR; + else + decoder->error = VIDEODECODER_OK; + + return ret; +} + +float +VideoDecoder_Seek (TFB_VideoDecoder *decoder, float pos) +{ + if (!decoder) + return 0.0; + + decoder->pos = decoder->funcs->SeekTime (decoder, pos); + decoder->cur_frame = decoder->funcs->GetFrame (decoder); + + return decoder->pos; +} + +uint32 +VideoDecoder_SeekFrame (TFB_VideoDecoder *decoder, uint32 frame) +{ + if (!decoder) + return 0; + + decoder->cur_frame = decoder->funcs->SeekFrame (decoder, frame); + decoder->pos = decoder->funcs->GetTime (decoder); + + return decoder->cur_frame; +} + +void +VideoDecoder_Rewind (TFB_VideoDecoder *decoder) +{ + if (!decoder) + return; + + VideoDecoder_Seek (decoder, 0); +} + +void +VideoDecoder_Free (TFB_VideoDecoder *decoder) +{ + if (!decoder) + return; + + decoder->funcs->Close (decoder); + decoder->funcs->Term (decoder); + + HFree (decoder->filename); + HFree (decoder); +} + +// BEGIN: adapted from SDL +static void +vd_computeMasks (uint32 mask, DWORD* shift, DWORD* loss) +{ + *shift = 0; + *loss = 8; + if (mask) + { + for (; !(mask & 1); mask >>= 1 ) + ++*shift; + + for (; (mask & 1); mask >>= 1 ) + --*loss; + } +} +// END: adapted from SDL diff --git a/src/libs/video/videodec.h b/src/libs/video/videodec.h new file mode 100644 index 0000000..2d75c98 --- /dev/null +++ b/src/libs/video/videodec.h @@ -0,0 +1,124 @@ +/* + * 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 the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_VIDEO_VIDEODEC_H_ +#define LIBS_VIDEO_VIDEODEC_H_ + +#include "libs/vidlib.h" +#include "libs/video/video.h" +#include "libs/reslib.h" + +// forward-declare +typedef struct tfb_videodecoder TFB_VideoDecoder; + +#define THIS_PTR TFB_VideoDecoder* + +typedef struct tfb_videodecoderfunc +{ + const char* (* GetName) (void); + bool (* InitModule) (int flags); + void (* TermModule) (void); + uint32 (* GetStructSize) (void); + int (* GetError) (THIS_PTR); + bool (* Init) (THIS_PTR, TFB_PixelFormat* fmt); + void (* Term) (THIS_PTR); + bool (* Open) (THIS_PTR, uio_DirHandle *dir, const char *filename); + void (* Close) (THIS_PTR); + int (* DecodeNext) (THIS_PTR); + uint32 (* SeekFrame) (THIS_PTR, uint32 frame); + float (* SeekTime) (THIS_PTR, float time); + uint32 (* GetFrame) (THIS_PTR); + float (* GetTime) (THIS_PTR); + +} TFB_VideoDecoderFuncs; + +// decoder will call these to get info +// from the player +typedef struct tfb_videocallbacks +{ + // any decoder calls these + void (* BeginFrame) (THIS_PTR); + void (* EndFrame) (THIS_PTR); + void* (* GetCanvasLine) (THIS_PTR, uint32 line); + // non-audio-driven decoders call this to figure out + // when the next frame should be drawn + uint32 (* GetTicks) (THIS_PTR); + // non-audio-driven decoders call this to inform + // the player when the next frame should be drawn + bool (* SetTimer) (THIS_PTR, uint32 msecs); + +} TFB_VideoCallbacks; + +#undef THIS_PTR + +struct tfb_videodecoder +{ + // decoder virtual funcs - R/O + const TFB_VideoDecoderFuncs *funcs; + // video formats - R/O + const TFB_PixelFormat *format; + // decoder-set data - R/O + uint32 w, h; + float length; // total length in seconds + uint32 frame_count; + uint32 interframe_wait; // nominal interframe delay in msecs + bool audio_synced; + // decoder callbacks + TFB_VideoCallbacks callbacks; + + // other - public + bool looping; + void* data; // user-defined data + // info - public R/O + sint32 error; + float pos; // position in seconds + uint32 cur_frame; + + // semi-private + uio_DirHandle *dir; + char *filename; + +}; + +// return values +enum +{ + VIDEODECODER_OK, + VIDEODECODER_ERROR, + VIDEODECODER_EOF, +}; + +typedef struct TFB_RegVideoDecoder TFB_RegVideoDecoder; + +TFB_RegVideoDecoder* VideoDecoder_Register (const char* fileext, + TFB_VideoDecoderFuncs* decvtbl); +void VideoDecoder_Unregister (TFB_RegVideoDecoder* regdec); +const TFB_VideoDecoderFuncs* VideoDecoder_Lookup (const char* fileext); + +bool VideoDecoder_Init (int flags, int depth, uint32 Rmask, uint32 Gmask, + uint32 Bmask, uint32 Amask); +void VideoDecoder_Uninit (void); +TFB_VideoDecoder* VideoDecoder_Load (uio_DirHandle *dir, + const char *filename); +int VideoDecoder_Decode (TFB_VideoDecoder *decoder); +float VideoDecoder_Seek (TFB_VideoDecoder *decoder, float time_pos); +uint32 VideoDecoder_SeekFrame (TFB_VideoDecoder *decoder, uint32 frame_pos); +void VideoDecoder_Rewind (TFB_VideoDecoder *decoder); +void VideoDecoder_Free (TFB_VideoDecoder *decoder); +const char* VideoDecoder_GetName (TFB_VideoDecoder *decoder); + + +#endif // LIBS_VIDEO_VIDEODEC_H_ diff --git a/src/libs/video/vidintrn.h b/src/libs/video/vidintrn.h new file mode 100644 index 0000000..40a19e4 --- /dev/null +++ b/src/libs/video/vidintrn.h @@ -0,0 +1,41 @@ +// Copyright 2008 Michael Martin + +/* + * 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 the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef VIDINTERN_H_ +#define VIDINTERN_H_ + +#include "types.h" +#include "libs/vidlib.h" +#include "libs/threadlib.h" + +struct legacy_video_desc +{ + char *video, *audio, *speech; + uint32 loop; +}; + +typedef struct legacy_video_desc LEGACY_VIDEO_DESC; + +struct legacy_video_ref +{ + VIDEO_REF vidref; + MUSIC_REF audref; + MUSIC_REF speechref; +}; + +#endif diff --git a/src/libs/video/vidplayer.c b/src/libs/video/vidplayer.c new file mode 100644 index 0000000..09a506d --- /dev/null +++ b/src/libs/video/vidplayer.c @@ -0,0 +1,481 @@ +/* + * 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 the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "vidplayer.h" + +#include "vidintrn.h" +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/tfb_draw.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include "libs/sndlib.h" + +// video callbacks +static void vp_BeginFrame (TFB_VideoDecoder*); +static void vp_EndFrame (TFB_VideoDecoder*); +static void* vp_GetCanvasLine (TFB_VideoDecoder*, uint32 line); +static uint32 vp_GetTicks (TFB_VideoDecoder*); +static bool vp_SetTimer (TFB_VideoDecoder*, uint32 msecs); + + +static const TFB_VideoCallbacks vp_DecoderCBs = +{ + vp_BeginFrame, + vp_EndFrame, + vp_GetCanvasLine, + vp_GetTicks, + vp_SetTimer +}; + +// audio stream callbacks +static bool vp_AudioStart (TFB_SoundSample* sample); +static void vp_AudioEnd (TFB_SoundSample* sample); +static void vp_BufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag); +static void vp_QueueBuffer (TFB_SoundSample* sample, audio_Object buffer); + +static const TFB_SoundCallbacks vp_AudioCBs = +{ + vp_AudioStart, + NULL, + vp_AudioEnd, + vp_BufferTag, + vp_QueueBuffer +}; + + +bool +TFB_InitVideoPlayer (void) +{ + // now just a stub + return true; +} + +void +TFB_UninitVideoPlayer (void) +{ + // now just a stub +} + +static inline sint32 +msecToTimeCount (sint32 msec) +{ + return msec * ONE_SECOND / 1000; +} + +// audio-synced video playback frame function +// the frame rate and timing is dictated by the audio +static bool +processAudioSyncedFrame (VIDEO_REF vid) +{ +#define MAX_FRAME_LAG 8 +#define LAG_FRACTION 6 +#define SYNC_BIAS 1 / 3 + int ret; + uint32 want_frame; + uint32 prev_want_frame; + sint32 wait_msec; + CONTEXT oldContext; + TimeCount Now = GetTimeCounter (); + + if (!vid->playing) + return false; + + if (Now < vid->frame_time) + return true; // not time yet + + LockMutex (vid->guard); + want_frame = vid->want_frame; + UnlockMutex (vid->guard); + + if (want_frame >= vid->decoder->frame_count) + { + vid->playing = false; + return false; + } + + // this works like so (audio-synced): + // 1. you call VideoDecoder_Seek() [when necessary] and + // VideoDecoder_Decode() + // 2. wait till it's time for this frame to be drawn + // the timeout is necessary because the audio signaling is not + // precise (see vp_AudioStart, vp_AudioEnd, vp_BufferTag) + // 3. output the frame; if the audio is behind, the lag counter + // goes up; if the video is behind, the lag counter goes down + // 4. set the next frame timeout; lag counter increases or + // decreases the timeout to allow audio or video to catch up + // 5. on a seek operation, the audio stream is moved to the + // correct position and then the audio signals the frame + // that should be rendered + // The system of timeouts and lag counts should make the video + // *relatively* smooth + // + prev_want_frame = vid->cur_frame - vid->lag_cnt; + if (want_frame > prev_want_frame - MAX_FRAME_LAG + && want_frame <= prev_want_frame + MAX_FRAME_LAG) + { + // we will draw the next frame right now, thus +1 + vid->lag_cnt = vid->cur_frame + 1 - want_frame; + } + else + { // out of sequence frame, let's get it + vid->lag_cnt = 0; + vid->cur_frame = VideoDecoder_SeekFrame (vid->decoder, want_frame); + ret = VideoDecoder_Decode (vid->decoder); + if (ret < 0) + { // decoder returned a failure + vid->playing = false; + return false; + } + } + vid->cur_frame = vid->decoder->cur_frame; + + // draw the frame + // We have the cliprect precalculated and don't need the rest + oldContext = SetContext (NULL); + TFB_DrawScreen_Image (vid->frame, + vid->dst_rect.corner.x, vid->dst_rect.corner.y, 0, 0, + NULL, DRAW_REPLACE_MODE, TFB_SCREEN_MAIN); + SetContext (oldContext); + FlushGraphics (); // needed to prevent half-frame updates + + // increase interframe with positive lag-count to allow audio to catch up + // decrease interframe with negative lag-count to allow video to catch up + wait_msec = vid->decoder->interframe_wait + - (int)vid->decoder->interframe_wait * SYNC_BIAS + + (int)vid->decoder->interframe_wait * vid->lag_cnt / LAG_FRACTION; + vid->frame_time = Now + msecToTimeCount (wait_msec); + + ret = VideoDecoder_Decode (vid->decoder); + if (ret < 0) + { + // TODO: decide what to do on error + } + + return vid->playing; +} + +// audio-independent video playback frame function +// the frame rate and timing is dictated by the video decoder +static bool +processMuteFrame (VIDEO_REF vid) +{ + int ret; + TimeCount Now = GetTimeCounter (); + + if (!vid->playing) + return false; + + // this works like so: + // 1. you call VideoDecoder_Seek() [when necessary] and + // VideoDecoder_Decode() + // 2. the decoder calls back vp_GetTicks() and vp_SetTimer() + // to figure out and tell you when to render the frame + // being decoded + // On a seek operation, the decoder should reset its internal + // clock and call vp_GetTicks() again + // + if (Now >= vid->frame_time) + { + CONTEXT oldContext; + + vid->cur_frame = vid->decoder->cur_frame; + + // We have the cliprect precalculated and don't need the rest + oldContext = SetContext (NULL); + TFB_DrawScreen_Image (vid->frame, + vid->dst_rect.corner.x, vid->dst_rect.corner.y, 0, 0, + NULL, DRAW_REPLACE_MODE, TFB_SCREEN_MAIN); + SetContext (oldContext); + FlushGraphics (); // needed to prevent half-frame updates + + if (vid->cur_frame == vid->loop_frame) + VideoDecoder_SeekFrame (vid->decoder, vid->loop_to); + + ret = VideoDecoder_Decode (vid->decoder); + if (ret <= 0) + vid->playing = false; + } + + return vid->playing; +} + +bool +TFB_PlayVideo (VIDEO_REF vid, uint32 x, uint32 y) +{ + RECT scrn_r; + RECT clip_r = {{0, 0}, {vid->w, vid->h}}; + RECT vid_r = {{0, 0}, {ScreenWidth, ScreenHeight}}; + RECT dr = {{x, y}, {vid->w, vid->h}}; + RECT sr; + bool loop_music = false; + int ret; + + if (!vid) + return false; + + // calculate the frame-source and screen-destination rects + GetContextClipRect (&scrn_r); + if (!BoxIntersect(&scrn_r, &vid_r, &scrn_r)) + return false; // drawing outside visible + + sr = dr; + sr.corner.x = -sr.corner.x; + sr.corner.y = -sr.corner.y; + if (!BoxIntersect (&clip_r, &sr, &sr)) + return false; // drawing outside visible + + dr.corner.x += scrn_r.corner.x; + dr.corner.y += scrn_r.corner.y; + if (!BoxIntersect (&scrn_r, &dr, &vid->dst_rect)) + return false; // drawing outside visible + + vid->src_rect = vid->dst_rect; + vid->src_rect.corner.x = sr.corner.x; + vid->src_rect.corner.y = sr.corner.y; + + vid->decoder->callbacks = vp_DecoderCBs; + vid->decoder->data = vid; + + vid->frame = TFB_DrawImage_CreateForScreen (vid->w, vid->h, FALSE); + vid->cur_frame = -1; + vid->want_frame = -1; + + if (!vid->hAudio) + { + vid->hAudio = LoadMusicFile (vid->decoder->filename); + vid->own_audio = true; + } + + if (vid->decoder->audio_synced) + { + if (!vid->hAudio) + { + log_add (log_Warning, "TFB_PlayVideo: " + "Cannot load sound-track for audio-synced video"); + return false; + } + + TFB_SetSoundSampleCallbacks (*vid->hAudio, &vp_AudioCBs); + TFB_SetSoundSampleData (*vid->hAudio, vid); + } + + // get the first frame + ret = VideoDecoder_Decode (vid->decoder); + if (ret < 0) + return false; + + vid->playing = true; + + loop_music = !vid->decoder->audio_synced && vid->loop_frame != VID_NO_LOOP; + if (vid->hAudio) + PLRPlaySong (vid->hAudio, loop_music, 1); + + if (vid->decoder->audio_synced) + { + // draw the first frame now + vid->frame_time = GetTimeCounter (); + } + + return true; +} + +void +TFB_StopVideo (VIDEO_REF vid) +{ + if (!vid) + return; + + vid->playing = false; + + if (vid->hAudio) + { + PLRStop (vid->hAudio); + if (vid->own_audio) + { + DestroyMusic (vid->hAudio); + vid->hAudio = 0; + vid->own_audio = false; + } + } + if (vid->frame) + { + TFB_DrawScreen_DeleteImage (vid->frame); + vid->frame = NULL; + } +} + +bool +TFB_VideoPlaying (VIDEO_REF vid) +{ + if (!vid) + return false; + + return vid->playing; +} + +bool +TFB_ProcessVideoFrame (VIDEO_REF vid) +{ + if (!vid) + return false; + + if (vid->decoder->audio_synced) + return processAudioSyncedFrame (vid); + else + return processMuteFrame (vid); +} + +uint32 +TFB_GetVideoPosition (VIDEO_REF vid) +{ + uint32 pos; + + if (!TFB_VideoPlaying (vid)) + return 0; + + LockMutex (vid->guard); + pos = (uint32) (vid->decoder->pos * 1000); + UnlockMutex (vid->guard); + + return pos; +} + +bool +TFB_SeekVideo (VIDEO_REF vid, uint32 pos) +{ + if (!TFB_VideoPlaying (vid)) + return false; + + if (vid->decoder->audio_synced) + { + PLRSeek (vid->hAudio, pos); + TaskSwitch (); + return true; + } + else + { // TODO: Non-a/s decoder seeking is not supported yet + // Decide what to do with these. Seeking this kind of + // video is trivial, but we may not want to do it. + // The only non-a/s videos right now are ship spins. + return false; + } +} + +static void +vp_BeginFrame (TFB_VideoDecoder* decoder) +{ + TFB_VideoClip* vid = decoder->data; + + if (vid) + TFB_DrawCanvas_Lock (vid->frame->NormalImg); +} + +static void +vp_EndFrame (TFB_VideoDecoder* decoder) +{ + TFB_VideoClip* vid = decoder->data; + + if (vid) + TFB_DrawCanvas_Unlock (vid->frame->NormalImg); +} + +static void* +vp_GetCanvasLine (TFB_VideoDecoder* decoder, uint32 line) +{ + TFB_VideoClip* vid = decoder->data; + + if (!vid) + return NULL; + + return TFB_DrawCanvas_GetLine (vid->frame->NormalImg, line); +} + +static uint32 +vp_GetTicks (TFB_VideoDecoder* decoder) +{ + uint32 ctr = GetTimeCounter (); + return (ctr / ONE_SECOND) * 1000 + ((ctr % ONE_SECOND) * 1000) / ONE_SECOND; + + (void)decoder; // gobble up compiler warning +} + +static bool +vp_SetTimer (TFB_VideoDecoder* decoder, uint32 msecs) +{ + TFB_VideoClip* vid = decoder->data; + + if (!vid) + return false; + + // time when next frame should be displayed + vid->frame_time = GetTimeCounter () + msecs * ONE_SECOND / 1000; + return true; +} + +static bool +vp_AudioStart (TFB_SoundSample* sample) +{ + TFB_VideoClip* vid = TFB_GetSoundSampleData (sample); + TFB_SoundDecoder *decoder; + + assert (sizeof (intptr_t) >= sizeof (vid)); + assert (vid != NULL); + + decoder = TFB_GetSoundSampleDecoder (sample); + + LockMutex (vid->guard); + vid->want_frame = SoundDecoder_GetFrame (decoder); + UnlockMutex (vid->guard); + + return true; +} + +static void +vp_AudioEnd (TFB_SoundSample* sample) +{ + TFB_VideoClip* vid = TFB_GetSoundSampleData (sample); + + assert (vid != NULL); + + LockMutex (vid->guard); + vid->want_frame = vid->decoder->frame_count; // end it + UnlockMutex (vid->guard); +} + +static void +vp_BufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag) +{ + TFB_VideoClip* vid = TFB_GetSoundSampleData (sample); + uint32 frame = (uint32) tag->data; + + assert (sizeof (tag->data) >= sizeof (frame)); + assert (vid != NULL); + + LockMutex (vid->guard); + vid->want_frame = frame; // let it go! + UnlockMutex (vid->guard); +} + +static void +vp_QueueBuffer (TFB_SoundSample* sample, audio_Object buffer) +{ + //TFB_VideoClip* vid = (TFB_VideoClip*) TFB_GetSoundSampleData (sample); + TFB_SoundDecoder *decoder = TFB_GetSoundSampleDecoder (sample); + + TFB_TagBuffer (sample, buffer, + (intptr_t) SoundDecoder_GetFrame (decoder)); +} + diff --git a/src/libs/video/vidplayer.h b/src/libs/video/vidplayer.h new file mode 100644 index 0000000..78e4edb --- /dev/null +++ b/src/libs/video/vidplayer.h @@ -0,0 +1,31 @@ +/* + * 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 the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_VIDEO_VIDPLAYER_H_ +#define LIBS_VIDEO_VIDPLAYER_H_ + +#include "video.h" + +extern bool TFB_InitVideoPlayer (void); +extern void TFB_UninitVideoPlayer (void); +extern bool TFB_PlayVideo (VIDEO_REF VidRef, uint32 x, uint32 y); +extern void TFB_StopVideo (VIDEO_REF VidRef); +extern bool TFB_VideoPlaying (VIDEO_REF VidRef); +extern bool TFB_ProcessVideoFrame (VIDEO_REF vid); +extern uint32 TFB_GetVideoPosition (VIDEO_REF VidRef); +extern bool TFB_SeekVideo (VIDEO_REF VidRef, uint32 pos); + +#endif // LIBS_VIDEO_VIDPLAYER_H_ diff --git a/src/libs/video/vresins.c b/src/libs/video/vresins.c new file mode 100644 index 0000000..dbb18e0 --- /dev/null +++ b/src/libs/video/vresins.c @@ -0,0 +1,186 @@ +// Copyright 2008 Michael Martin + +/* + * 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 the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdlib.h> +#include <string.h> +#include "vidintrn.h" +#include "libs/log.h" +#include "libs/memlib.h" + + +static BOOLEAN +FreeLegacyVideoData (void *data) +{ + LEGACY_VIDEO pLV; + if (!data) + return FALSE; + + pLV = (LEGACY_VIDEO) data; + if (pLV->video) + HFree (pLV->video); + if (pLV->audio) + HFree (pLV->audio); + if (pLV->speech) + HFree (pLV->speech); + HFree (pLV); + + return TRUE; +} + +static void +GetLegacyVideoData (const char *path, RESOURCE_DATA *resdata) +{ + void *result = NULL; + char paths[1024], *audio_path, *speech_path, *loop_str; + uint32 LoopFrame = VID_NO_LOOP; + + /* Parse out the video components. */ + strncpy (paths, path, 1023); + paths[1023] = '\0'; + audio_path = strchr (paths, ':'); + if (audio_path == NULL) + { + speech_path = NULL; + loop_str = NULL; + } + else + { + *audio_path = '\0'; + audio_path++; + + speech_path = strchr (audio_path, ':'); + if (speech_path == NULL) + { + loop_str = NULL; + } + else + { + *speech_path = '\0'; + speech_path++; + + loop_str = strchr (speech_path, ':'); + if (loop_str != NULL) { + *loop_str = '\0'; + loop_str++; + } + } + } + + log_add (log_Info, "\t'%s' -- video", paths); + if (audio_path) + log_add (log_Info, "\t'%s' -- audio", audio_path); + else + log_add (log_Info, "\tNo associated audio"); + if (speech_path) + log_add (log_Info, "\t'%s' -- speech path", speech_path); + else + log_add (log_Info, "\tNo associated speech"); + if (loop_str) + { + char *end; + LoopFrame = strtol (loop_str, &end, 10); + // We allow whitespace at the end, but nothing printable. + if (*end > 32) { + log_add (log_Warning, "Warning: Unparsable loop frame '%s'. Disabling loop.", loop_str); + LoopFrame = VID_NO_LOOP; + } + log_add (log_Info, "\tLoop frame is %u", LoopFrame); + } + else + log_add (log_Info, "\tNo specified loop frame"); + + result = HMalloc (sizeof (LEGACY_VIDEO_DESC)); + if (result) + { + LEGACY_VIDEO pLV = (LEGACY_VIDEO) result; + int len; + pLV->video = NULL; + pLV->audio = NULL; + pLV->speech = NULL; + pLV->loop = LoopFrame; + + len = strlen(paths)+1; + pLV->video = (char *)HMalloc (len); + if (!pLV->video) + { + log_add (log_Warning, "Warning: Couldn't allocate space for '%s'", paths); + goto err; + } + strncpy(pLV->video, paths, len); + + if (audio_path) + { + len = strlen(audio_path)+1; + pLV->audio = (char *)HMalloc (len); + if (!pLV->audio) + { + log_add (log_Warning, "Warning: Couldn't allocate space for '%s'", audio_path); + goto err; + } + strncpy(pLV->audio, audio_path, len); + } + + if (speech_path) + { + len = strlen(speech_path)+1; + pLV->speech = (char *)HMalloc (len); + if (!pLV->speech) + { + log_add (log_Warning, "Warning: Couldn't allocate space for '%s'", speech_path); + goto err; + } + strncpy(pLV->speech, speech_path, len); + } + + resdata->ptr = result; + } + return; +err: + if (result) + FreeLegacyVideoData ((LEGACY_VIDEO)result); + + resdata->ptr = NULL; + return; +} + +BOOLEAN +InstallVideoResType (void) +{ + InstallResTypeVectors ("3DOVID", GetLegacyVideoData, FreeLegacyVideoData, NULL); + return TRUE; +} + +LEGACY_VIDEO +LoadLegacyVideoInstance (RESOURCE res) +{ + void *data; + + data = res_GetResource (res); + if (data) + { + res_DetachResource (res); + } + + return (LEGACY_VIDEO)data; +} + +BOOLEAN +DestroyLegacyVideo (LEGACY_VIDEO vid) +{ + return FreeLegacyVideoData (vid); +} |