diff options
-rwxr-xr-x | configure | 23 | ||||
-rw-r--r-- | sword2/driver/animation.cpp | 199 | ||||
-rw-r--r-- | sword2/driver/animation.h | 58 | ||||
-rw-r--r-- | sword2/driver/d_draw.cpp | 128 | ||||
-rw-r--r-- | sword2/driver/d_draw.h | 12 | ||||
-rw-r--r-- | sword2/driver/render.cpp | 27 | ||||
-rw-r--r-- | sword2/module.mk | 3 |
7 files changed, 437 insertions, 13 deletions
@@ -29,6 +29,7 @@ _vorbis=auto _mad=auto _alsa=auto _zlib=auto +_mpeg2=no # default option behaviour yes/no _build_scumm=yes _build_simon=yes @@ -186,8 +187,9 @@ Optional Libraries: --disable-vorbis disable Ogg Vorbis support [autodetect] --with-mad-prefix=PFX Prefix where libmad is installed (optional) --disable-mad disable libmad (MP3) support [autodetect] - --with-zlib-prefix=PFX Prefix where zlib is installed (optional) - --disable-zlib disable zlib (compression) support [autodetect] + --with-zlib-prefix=PFX Prefix where zlib is installed (optional) + --disable-zlib disable zlib (compression) support [autodetect] + --enable-mpeg2 enable mpeg2 codec for cutscenes [disabled] EOF exit 0 @@ -211,7 +213,8 @@ for ac_option in $@; do --enable-mad) _mad=yes ;; --disable-mad) _mad=no ;; --enable-zlib) _zlib=yes ;; - --disable-zlib) _zlib=no ;; + --disable-zlib) _zlib=no ;; + --enable-mpeg2) _mpeg2=yes ;; --with-alsa-prefix=*) _prefix=`echo $ac_option | cut -d '=' -f 2` ALSA_CFLAGS="-I$_prefix/include" @@ -558,6 +561,12 @@ else fi echo "$_zlib" +echocheck "mpeg2" +if test "$_mpeg2" = yes ; then + _def_mpeg2='#define USE_MPEG2' + LIBS="$LIBS -lmpeg2" +fi +echo "$_mpeg2" rm -f $TMPC $TMPO @@ -576,7 +585,12 @@ if test "$_build_bs1" = yes ; then echo " Broken Sword I" fi if test "$_build_bs2" = yes ; then - echo " Broken Sword II" + echo -n " Broken Sword II" + if test "$_mpeg2" = yes ; then + echo " (w/ mpeg2 cutscenes)" + else + echo " (without mpeg2 cutscenes)" + fi fi if test "$_build_queen" = yes ; then echo " Flight of the Amazon Queen" @@ -648,6 +662,7 @@ $_def_vorbis $_def_mad $_def_alsa $_def_zlib +$_def_mpeg2 /* Subsystems */ #define INSANE diff --git a/sword2/driver/animation.cpp b/sword2/driver/animation.cpp new file mode 100644 index 0000000000..6426624218 --- /dev/null +++ b/sword2/driver/animation.cpp @@ -0,0 +1,199 @@ +#include "common/stdafx.h" +#include "sword2/sword2.h" +#include "sword2/driver/menu.h" +#include "sword2/driver/render.h" + +#include "common/file.h" + +namespace Sword2 { + +// Build 'Best-Match' RGB lookup table +void MoviePlayer::buildlookup(AnimationState * st, int p, int lines) { + int y, cb; + int r, g, b, ii; + + + if (p != st->curpal) { + st->curpal = p; + st->cr = 0; + st->pos = 0; + } + + if (st->cr >= BITDEPTH) + return; + + for (ii = 0; ii < lines; ii++) { + r = (-16*256 + (int)(256*1.596) * ((st->cr<<SHIFT)-128)) / 256; + for (cb = 0; cb < BITDEPTH; cb++) { + g = (-16*256 - (int)(0.813*256) * ((st->cr<<SHIFT)-128) - (int)(0.391*256) * ((cb<<SHIFT)-128)) / 256; + b = (-16*256 + (int)(2.018*256) * ((cb<<SHIFT)-128)) / 256; + + for (y = 0; y < BITDEPTH; y++) { + int idx, bst = 0; + int dis = 2*SQR(r-st->palettes[p].pal[0])+4*SQR(g-st->palettes[p].pal[1])+SQR(b-st->palettes[p].pal[2]); + + for (idx = 1; idx < 256; idx++) { + long d2 = 2*SQR(r-st->palettes[p].pal[4*idx])+4*SQR(g-st->palettes[p].pal[4*idx+1])+SQR(b-st->palettes[p].pal[4*idx+2]); + if (d2 < dis) { + bst = idx; + dis = d2; + } + } + st->lut2[st->pos++] = bst; + + r += (1 << SHIFT); + g += (1 << SHIFT); + b += (1 << SHIFT); + } + r -= 256; + } + st->cr++; + if (st->cr >= BITDEPTH) + return; + } +} + +void MoviePlayer::checkPaletteSwitch(AnimationState * st) { + // if we have reached the last image with this palette, switch to new one + if (st->framenum == st->palettes[st->palnum].end) { + unsigned char *l = st->lut2; + st->palnum++; + _vm->_graphics->setPalette(0, 256, st->palettes[st->palnum].pal, RDPAL_INSTANT); + st->lutcalcnum = (BITDEPTH + st->palettes[st->palnum].end - (st->framenum + 1) + 2) / (st->palettes[st->palnum].end - (st->framenum + 1) + 2); + st->lut2 = st->lut; + st->lut = l; + } +} + +#ifndef USE_MPEG2 +bool MoviePlayer::pic(AnimationState * st) { + // Dummy for MPEG2-less builds + return false; +} +#else +bool MoviePlayer::pic(AnimationState * st) { + mpeg2_state_t state; + const mpeg2_sequence_t *sequence_i; + size_t size = (size_t)-1; + + do { + state = mpeg2_parse (st->decoder); + sequence_i = st->info->sequence; + + switch (state) { + case STATE_BUFFER: + size = st->mpgfile->read(st->buffer, BUFFER_SIZE); + mpeg2_buffer (st->decoder, st->buffer, st->buffer + size); + break; + + case STATE_SLICE: + case STATE_END: + if (st->info->display_fbuf) { + checkPaletteSwitch(st); + _vm->_graphics->plotYUV(st->lut, sequence_i->width, sequence_i->height, st->info->display_fbuf->buf); + st->framenum++; + buildlookup(st, st->palnum+1, st->lutcalcnum); + return true; + } + break; + + default: + break; + } + } while (size); + + return false; +} +#endif + +#ifndef USE_MPEG2 +AnimationState *MoviePlayer::initanimation(char *name) { + return 0; +} +#else +AnimationState *MoviePlayer::initanimation(char *name) { + char basename[512], tempFile[512]; + AnimationState *st = new AnimationState; + int i, p; + + strcpy(basename, name); + basename[strlen(basename)-4] = 0; + + // Load lookup palettes + // TODO: Binary format so we can use File class + sprintf(tempFile, "%s/%s.pal", _vm->getGameDataPath(), basename); + FILE *f = fopen(tempFile, "r"); + + if (!f) { + warning("Cutscene: %s.pal palette missing", basename); + return 0; + } + + p = 0; + while (!feof(f)) { + fscanf(f, "%i %i", &st->palettes[p].end, &st->palettes[p].cnt); + for (i = 0; i < st->palettes[p].cnt; i++) { + fscanf(f, "%i", &st->palettes[p].pal[4*i]); + fscanf(f, "%i", &st->palettes[p].pal[4*i+1]); + fscanf(f, "%i", &st->palettes[p].pal[4*i+2]); + } + p++; + } + fclose(f); + + st->palnum = 0; + _vm->_graphics->setPalette(0, 256, st->palettes[st->palnum].pal, RDPAL_INSTANT); + st->lut = st->lut2 = st->lookup[0]; + st->curpal = -1; + st->cr = 0; + buildlookup(st, st->palnum, 256); + st->lut2 = st->lookup[1]; + + // Open MPEG2 stream + st->mpgfile = new File(); + sprintf(tempFile, "%s.mp2", basename); + if (!st->mpgfile->open(tempFile)) { + warning("Cutscene: Could not open %s", tempFile); + delete st; + return 0; + } + + // Load and configure decoder + st->decoder = mpeg2_init (); + if (st->decoder == NULL) { + warning("Cutscene: Could not allocate an MPEG2 decoder"); + delete st; + return 0; + } + + st->info = mpeg2_info(st->decoder); + st->framenum = 0; + + // Load in palette data + st->lutcalcnum = (BITDEPTH + st->palettes[st->palnum].end + 2) / (st->palettes[st->palnum].end + 2); + + + /* Play audio - TODO: Sync with video?*/ + File *sndFile = new File; + sprintf(tempFile, "%s.ogg", basename); + if (sndFile->open(tempFile)) + _vm->_mixer->playVorbis(&st->bgSound, sndFile, 100000000); + + return st; +} +#endif + +#ifndef USE_MPEG2 +void MoviePlayer::doneanimation(AnimationState *st) { +} +#else +void MoviePlayer::doneanimation(AnimationState *st) { + _vm->_mixer->stopHandle(st->bgSound); + + mpeg2_close (st->decoder); + st->mpgfile->close(); + delete st->mpgfile; + delete st; +} +#endif +} // End of namespace Sword2 diff --git a/sword2/driver/animation.h b/sword2/driver/animation.h new file mode 100644 index 0000000000..889a68d9fd --- /dev/null +++ b/sword2/driver/animation.h @@ -0,0 +1,58 @@ +#ifndef ANIMATION_H +#define ANIMATION_H + +#include <inttypes.h> +#ifdef USE_MPEG2 +extern "C" { + #include <mpeg2dec/mpeg2.h> +} +#endif + +namespace Sword2 { + +#define SQR(x) ((x)*(x)) + +#define SHIFT 3 +#define BITDEPTH (1<<(8-SHIFT)) +#define ROUNDADD (1<<(SHIFT-1)) + +#define BUFFER_SIZE 4096 + + +typedef struct { + + + int palnum; + + unsigned char lookup[2][BITDEPTH*BITDEPTH*BITDEPTH]; + unsigned char * lut; + unsigned char * lut2; + int lutcalcnum; + + int framenum; + + #ifdef USE_MPEG2 + mpeg2dec_t * decoder; + const mpeg2_info_t * info; + #endif + File * mpgfile; + + int curpal; + int cr; + int pos; + + struct { + int cnt; + int end; + unsigned char pal[4*256]; + } palettes[50]; + + unsigned char buffer[BUFFER_SIZE]; + + PlayingSoundHandle bgSound; + +} AnimationState; + +} // End of namespace Sword2 + +#endif diff --git a/sword2/driver/d_draw.cpp b/sword2/driver/d_draw.cpp index 52226f794e..f7f0f51add 100644 --- a/sword2/driver/d_draw.cpp +++ b/sword2/driver/d_draw.cpp @@ -141,11 +141,125 @@ void MoviePlayer::drawTextObject(MovieTextObject *obj) { */ int32 MoviePlayer::play(char *filename, MovieTextObject *text[], uint8 *musicOut) { - warning("semi-stub PlaySmacker %s", filename); +#ifdef USE_MPEG2 + int frameCounter = 0, textCounter = 0; + PlayingSoundHandle handle; + bool skipCutscene = false, textVisible = false; + uint32 flags = SoundMixer::FLAG_16BITS, ticks = _vm->_system->get_msecs() + 83; + + uint8 oldPal[1024]; + memcpy(oldPal, _vm->_graphics->_palCopy, 1024); + + AnimationState * anim = initanimation(filename); + if (!anim) { + // Missing Files? Use the old 'Narration Only' hack + playDummy(filename, text, musicOut); + return RD_OK; + } + + _vm->_graphics->clearScene(); + memset(_vm->_graphics->_buffer, 0, _vm->_graphics->_screenWide * MENUDEEP); + + +#ifndef SCUMM_BIG_ENDIAN + flags |= SoundMixer::FLAG_LITTLE_ENDIAN; +#endif + + while (1) { + if (!pic(anim)) break; + _vm->_graphics->setNeedFullRedraw(); + + if (text && text[textCounter]) { + if (frameCounter == text[textCounter]->startFrame) { + openTextObject(text[textCounter]); + textVisible = true; + if (text[textCounter]->speech) { + _vm->_mixer->playRaw(&handle, text[textCounter]->speech, text[textCounter]->speechBufferSize, 22050, flags); + } + } + + if (frameCounter == text[textCounter]->endFrame) { + closeTextObject(text[textCounter]); + textCounter++; + textVisible = false; + } + if (textVisible) + drawTextObject(text[textCounter]); + } + + frameCounter++; + + _vm->_graphics->updateDisplay(true); + + KeyboardEvent ke; + + if ((_vm->_input->readKey(&ke) == RD_OK && ke.keycode == 27) || _vm->_quit) { + _vm->_mixer->stopHandle(handle); + skipCutscene = true; + break; + } - // WORKAROUND: For now, we just do the voice-over parts of the - // movies, since they're separate from the actual smacker files. + // Simulate ~12 frames per second. I don't know what + // frame rate the original movies had, or even if it + // was constant, but this seems to work reasonably. + while (_vm->_system->get_msecs() < ticks); + ticks += 82; + + } + + // Wait for the voice to stop playing. This is to make sure + // that we don't cut off the speech in mid-sentence, and - even + // more importantly - that we don't free the sound buffer while + // it's in use. + + while (handle.isActive()) { + _vm->_graphics->updateDisplay(false); + _vm->_system->delay_msecs(100); + } + + if (text) + closeTextObject(text[textCounter]); + + _vm->_graphics->clearScene(); + _vm->_graphics->setNeedFullRedraw(); + + // HACK: Remove the instructions created above + Common::Rect r; + + memset(_vm->_graphics->_buffer, 0, _vm->_graphics->_screenWide * MENUDEEP); + r.left = r.top = 0; + r.right = _vm->_graphics->_screenWide; + r.bottom = MENUDEEP; + _vm->_graphics->updateRect(&r); + + // FIXME: For now, only play the lead-out music for cutscenes + // that have subtitles. + if (!skipCutscene) + _vm->_sound->playLeadOut(musicOut); + + _vm->_graphics->setPalette(0, 256, oldPal, RDPAL_INSTANT); + + doneanimation(anim); + + // Lead-in and lead-out music are, as far as I can tell, only used for + // the animated cut-scenes, so this seems like a good place to close + // both of them. + + _vm->_sound->closeFx(-1); + _vm->_sound->closeFx(-2); + + return RD_OK; +#else + // No MPEG2? Use the old 'Narration Only' hack + playDummy(filename, text, musicOut); + return RD_OK; +#endif +} + +// This just plays the cutscene with voiceovers / subtitles, in case the files are missing +int32 MoviePlayer::playDummy(char *filename, MovieTextObject *text[], uint8 *musicOut) { + int frameCounter = 0, textCounter = 0; if (text) { uint8 oldPal[1024]; uint8 tmpPal[1024]; @@ -159,7 +273,7 @@ int32 MoviePlayer::play(char *filename, MovieTextObject *text[], uint8 *musicOut memset(_vm->_graphics->_buffer, 0, _vm->_graphics->_screenWide * MENUDEEP); - uint8 msg[] = "Cutscene - Press ESC to exit"; + uint8 msg[] = "Cutscene - Narration Only: Press ESC to exit, or visit www.scummvm.org to download cutscene videos"; Memory *data = _vm->_fontRenderer->makeTextSprite(msg, 640, 255, _vm->_speechFontId); FrameHeader *frame = (FrameHeader *) data->ad; SpriteInfo msgSprite; @@ -180,8 +294,7 @@ int32 MoviePlayer::play(char *filename, MovieTextObject *text[], uint8 *musicOut // In case the cutscene has a long lead-in, start just before // the first line of text. - int frameCounter = text[0]->startFrame - 12; - int textCounter = 0; + frameCounter = text[0]->startFrame - 12; // Fake a palette that will hopefully make the text visible. // In the opening cutscene it seems to use colours 1 (black?) @@ -197,7 +310,7 @@ int32 MoviePlayer::play(char *filename, MovieTextObject *text[], uint8 *musicOut tmpPal[255 * 4 + 2] = 255; _vm->_graphics->setPalette(0, 256, tmpPal, RDPAL_INSTANT); - PlayingSoundHandle handle; +PlayingSoundHandle handle; bool skipCutscene = false; @@ -219,7 +332,6 @@ int32 MoviePlayer::play(char *filename, MovieTextObject *text[], uint8 *musicOut _vm->_mixer->playRaw(&handle, text[textCounter]->speech, text[textCounter]->speechBufferSize, 22050, flags); } } - if (frameCounter == text[textCounter]->endFrame) { closeTextObject(text[textCounter]); _vm->_graphics->clearScene(); diff --git a/sword2/driver/d_draw.h b/sword2/driver/d_draw.h index 8b107164a3..3b29ccf02b 100644 --- a/sword2/driver/d_draw.h +++ b/sword2/driver/d_draw.h @@ -22,6 +22,8 @@ #include "common/rect.h" +#include "animation.h" + namespace Sword2 { // This is the maximum mouse cursor size in the SDL backend @@ -72,9 +74,17 @@ private: void closeTextObject(MovieTextObject *obj); void drawTextObject(MovieTextObject *obj); + void buildlookup(AnimationState * st, int p, int lines); + void checkPaletteSwitch(AnimationState * st); + + AnimationState * initanimation(char *name); + void doneanimation(AnimationState * st); + bool pic(AnimationState * st); + public: MoviePlayer(Sword2Engine *vm) : _vm(vm), _textSurface(NULL) {} int32 play(char *filename, MovieTextObject *text[], uint8 *musicOut); + int32 playDummy(char *filename, MovieTextObject *text[], uint8 *musicOut); }; struct BlockSurface { @@ -185,6 +195,7 @@ private: void unwindRaw16(uint8 *dest, uint8 *source, uint8 blockSize, uint8 *colTable); int32 decompressRLE16(uint8 *dest, uint8 *source, int32 decompSize, uint8 *colTable); + public: Graphics(Sword2Engine *vm, int16 width, int16 height); ~Graphics(); @@ -238,6 +249,7 @@ public: void plotPoint(uint16 x, uint16 y, uint8 colour); void drawLine(int16 x1, int16 y1, int16 x2, int16 y2, uint8 colour); + void plotYUV(unsigned char * lut, int width, int height, uint8_t * const * buf); int32 createSurface(SpriteInfo *s, uint8 **surface); void drawSurface(SpriteInfo *s, uint8 *surface, Common::Rect *clipRect = NULL); diff --git a/sword2/driver/render.cpp b/sword2/driver/render.cpp index 5e7d2961f0..7e05fa9919 100644 --- a/sword2/driver/render.cpp +++ b/sword2/driver/render.cpp @@ -820,4 +820,31 @@ void Graphics::closeBackgroundLayer(void) { _layer = 0; } + +void Graphics::plotYUV(unsigned char * lut, int width, int height, uint8_t * const * dat) +{ + uint8 *buf = _buffer + (40+(400-height)/2) * RENDERWIDE + (640-width)/2; + + int x, y; + + int ypos = 0; + int cpos = 0; + int linepos = 0; + + for (y = 0; y < height; y+=2) { + for (x = 0; x < width; x+=2) { + int i = ((((dat[2][cpos]+ROUNDADD)>>SHIFT) * BITDEPTH) + ((dat[1][cpos]+ROUNDADD)>>SHIFT)) * BITDEPTH; + cpos++; + + buf[linepos ] = lut[i+((dat[0][ ypos ]+ROUNDADD)>>SHIFT)]; + buf[RENDERWIDE + linepos++] = lut[i+((dat[0][width+ypos++]+ROUNDADD)>>SHIFT)]; + buf[linepos ] = lut[i+((dat[0][ ypos ]+ROUNDADD)>>SHIFT)]; + buf[RENDERWIDE + linepos++] = lut[i+((dat[0][width+ypos++]+ROUNDADD)>>SHIFT)]; + } + linepos += (2*RENDERWIDE-width); + ypos += width; + } +} + + } // End of namespace Sword2 diff --git a/sword2/module.mk b/sword2/module.mk index 2b4d805f7f..e552165c23 100644 --- a/sword2/module.mk +++ b/sword2/module.mk @@ -35,7 +35,8 @@ MODULE_OBJS := \ sword2/driver/palette.o \ sword2/driver/rdwin.o \ sword2/driver/render.o \ - sword2/driver/sprite.o + sword2/driver/sprite.o \ + sword2/driver/animation.o MODULE_DIRS += \ sword2 \ |