/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * The movie player. */ #include "tinsel/tinsel.h" #include "tinsel/background.h" #include "tinsel/bmv.h" #include "tinsel/cliprect.h" #include "tinsel/config.h" #include "tinsel/dw.h" #include "tinsel/events.h" #include "tinsel/font.h" #include "tinsel/graphics.h" #include "tinsel/handle.h" #include "tinsel/multiobj.h" #include "tinsel/sched.h" #include "tinsel/strres.h" #include "tinsel/text.h" #include "tinsel/timers.h" #include "tinsel/tinlib.h" #include "tinsel/tinsel.h" #include "audio/audiostream.h" #include "audio/decoders/raw.h" #include "common/textconsole.h" namespace Tinsel { //----------------- LOCAL DEFINES ---------------------------- #define BMOVIE_EXTENSION ".bmv" #define SZ_C_BLOB 65 #define SZ_U_BLOB 128 #define BLANK_SOUND 0x0 // for 16 bit silence #define PT_A 20 // Number of times PT_B may be reached #define PT_B 6 #define SLOT_SIZE (25*1024) //#define NUM_SLOTS 168 #define NUM_SLOTS 122 // -> ~ 3MB #define PREFETCH (NUM_SLOTS/2) // For initial test #define ADVANCE_SOUND 18 // 1 1/2 seconds #define SUBSEQUENT_SOUND 6 // 1/2 second // PACKET TYPE IDs & FLAGS #define CD_SLOT_NOP 0x00 // Skip to next slot #define CD_LE_FIN 0x01 // End of movie #define CD_PDELTA 0x02 // Image compressed to previous one #define CD_SDELTA 0x03 // Image self-compressed #define BIT0 0x01 #define CD_XSCR 0x04 // Screen has a scroll offset #define CD_CMAP 0x08 // Color map is included #define CD_CMND 0x10 // Command is included #define CD_AUDIO 0x20 // Audio data is included #define CD_EXTEND 0x40 // Extended modes "A"-"z" #define CD_PRINT 0x80 // goes in conjunction with CD_CMD // Data field sizes #define sz_XSCR_pkt 2 #define sz_CMAP_pkt 0x300 #define sz_CMD_TALK_pkt 10 #define sz_CMD_PRINT_pkt 8 #define sz_AUDIO_pkt 3675 struct TALK_CMD { short x; short y; short stringId; unsigned char duration; char r; // may be b! char g; char b; // may be r! }; struct PRINT_CMD { int16 x; int16 y; int16 stringId; unsigned char duration; unsigned char fontId; }; //----------------- LOCAL GLOBAL DATA ------------------------ static const uint16 Au_DecTable[16] = {16512, 8256, 4128, 2064, 1032, 516, 258, 192, 129, 88, 64, 56, 48, 40, 36, 32}; //---------------- DECOMPRESSOR FUNCTIONS -------------------- #define SCREEN_WIDE 640 #define SCREEN_HIGH 429 #define SAM_P_BLOB (32 * 2) #define ROR(x,v) x = ((x >> (v%32)) | (x << (32 - (v%32)))) #define ROL(x,v) x = ((x << (v%32)) | (x >> (32 - (v%32)))) #define NEXT_BYTE(v) v = (forwardDirection ? v + 1 : v - 1) static void PrepBMV(byte *ScreenBeg, const byte *sourceData, int length, short deltaFetchDisp) { uint8 NibbleHi = 0; uint32 edx = length; int32 ebx = deltaFetchDisp; const byte *src; byte *dst, *endDst; const bool forwardDirection = (deltaFetchDisp <= -SCREEN_WIDE) || (deltaFetchDisp >= 0); if (forwardDirection) { // Forward decompression src = sourceData; dst = ScreenBeg; endDst = ScreenBeg + SCREEN_WIDE * SCREEN_HIGH; } else { src = sourceData + length - 1; dst = ScreenBeg + SCREEN_WIDE * SCREEN_HIGH - 1; endDst = ScreenBeg - 1; } bool firstLoop, flag; int loopCtr = 0; do { uint32 eax = 0; uint32 bitshift = 0; flag = false; if ((loopCtr == 0) || (edx == 4)) { // Get the next hi,lo nibble eax = (eax & 0xffffff00) | *src; firstLoop = true; } else { // Get the high nibble eax = (eax & 0xffffff00) | NibbleHi; firstLoop = false; } // Is lo nibble '00xx'? if ((eax & 0xC) == 0) { for (;;) { //@_rDN_Lp_1: // Only execute this bit first the first time into the loop if (!firstLoop) { ROR(eax, 2); bitshift += 2; eax = (eax & 0xffffff00) | *src; if ((eax & 0xC) != 0) break; } firstLoop = false; //@_rD2nd_1: ROR(eax, 2); // Save bi-bit into hi 2 bits bitshift += 2; // and increase bit-shifter // Shift another 2 bits to get hi nibble eax = (eax & 0xffffff00) | ((eax & 0xff) >> 2); NEXT_BYTE(src); if ((eax & 0xC) != 0) { flag = true; ROL(eax, bitshift); break; } } } else if (loopCtr != 0) { flag = edx != 4; } if (flag) { //@_rdNum__1: edx = 4; // offset rDNum_Lo ; Next nibble is a 'lo' } else { // @_rDNum_1 NibbleHi = ((uint8)eax) >> 4; edx = 0; // offset rDNum_Hi ; Next nibble is a 'hi' (reserved) eax &= 0xffffff0f; NEXT_BYTE(src); ROL(eax, bitshift); } //rDN_1: //@_rD_or_R: bool actionFlag = (eax & 1) != 0; eax >>= 1; int byteLen = eax - 1; // Move to next loop index ++loopCtr; if (loopCtr == 4) loopCtr = 1; if (actionFlag) { // Adjust loopCtr to fall into the correct processing case loopCtr = loopCtr % 3 + 1; } switch (loopCtr) { case 1: { // @_rDelta: const byte *saved_src = src; // Save the source pointer src = dst + ebx; // Point it to existing data while (byteLen > 0) { *dst = *src; NEXT_BYTE(src); NEXT_BYTE(dst); --byteLen; } src = saved_src; break; } case 2: // @_rRaw // Copy data from source to dest while (byteLen > 0) { *dst = *src; NEXT_BYTE(src); NEXT_BYTE(dst); --byteLen; } break; case 3: // @_rRun // Repeating run of data eax = forwardDirection ? *(dst - 1) : *(dst + 1); while (byteLen > 0) { *dst = (uint8)eax; NEXT_BYTE(dst); --byteLen; } break; default: break; } } while (dst != endDst); } void BMVPlayer::InitBMV(byte *memoryBuffer) { // Clear the two extra 'off-screen' rows memset(memoryBuffer, 0, SCREEN_WIDE); memset(memoryBuffer + SCREEN_WIDE * (SCREEN_HIGH + 1), 0, SCREEN_WIDE); if (_audioStream) { _vm->_mixer->stopHandle(_audioHandle); delete _audioStream; _audioStream = 0; } // Set the screen beginning to the second line (ie. past the off-screen line) ScreenBeg = memoryBuffer + SCREEN_WIDTH; Au_Prev1 = Au_Prev2 = 0; } void BMVPlayer::PrepAudio(const byte *sourceData, int blobCount, byte *destPtr) { uint16 dx1 = Au_Prev1; uint16 dx2 = Au_Prev2; uint16 *destP = (uint16 *)destPtr; const int8 *srcP = (const int8 *)sourceData; // Blob Loop while (blobCount-- > 0) { uint32 ebx = (uint8) *srcP++; uint32 ebp = ebx & 0x1E; int blobSize = SAM_P_BLOB / 2; ebx = (((ebx & 0x0F) << 4) | ((ebx & 0xF0) >> 4)) & 0x1E; ebp = Au_DecTable[ebp >> 1]; ebx = Au_DecTable[ebx >> 1]; // Inner loop while (blobSize-- > 0) { uint32 s1 = (((int32) *srcP++) * ((int32) ebp)) >> 5; uint32 s2 = (((int32) *srcP++) * ((int32) ebx)) >> 5; dx1 += s1 & 0xFFFF; dx2 += s2 & 0xFFFF; *destP++ = TO_BE_16(dx1); *destP++ = TO_BE_16(dx2); } } Au_Prev1 = dx1; Au_Prev2 = dx2; } //----------------- BMV FUNCTIONS ---------------------------- BMVPlayer::BMVPlayer() { bOldAudio = 0; bMovieOn = 0; bAbort = 0; bmvEscape = 0; memset(szMovieFile, 0, sizeof(szMovieFile)); bigBuffer = 0; nextUseOffset = 0; nextSoundOffset = 0; wrapUseOffset = 0; mostFutureOffset = 0; currentFrame = 0; currentSoundFrame = 0; numAdvancePackets = 0; nextReadSlot = 0; bFileEnd = 0; memset(moviePal, 0, sizeof(moviePal)); blobsInBuffer = 0; memset(texts, 0, sizeof(texts)); talkColor = 0; bigProblemCount = 0; bIsText = 0; movieTick = 0; startTick = 0; nextMovieTime = 0; Au_Prev1 = 0; Au_Prev2 = 0; ScreenBeg = 0; screenBuffer = 0; audioStarted = 0; _audioStream = 0; nextMaintain = 0; } /** * Called when a packet contains a palette field. * Build a COLORREF array and queue it to the DAC. */ void BMVPlayer::MoviePalette(int paletteOffset) { int i; byte *r; r = bigBuffer + paletteOffset; for (i = 0; i < 256; i++, r += 3) { moviePal[i] = TINSEL_RGB(*r, *(r + 1), *(r + 2)); } UpdateDACqueue(1, 255, &moviePal[1]); // Don't clobber talk if (talkColor != 0) SetTextPal(talkColor); } void BMVPlayer::InitializeMovieSound() { _audioStream = Audio::makeQueuingAudioStream(22050, true); audioStarted = false; } void BMVPlayer::StartMovieSound() { } void BMVPlayer::FinishMovieSound() { if (_audioStream) { _vm->_mixer->stopHandle(_audioHandle); delete _audioStream; _audioStream = 0; } } /** * Called when a packet contains an audio field. */ void BMVPlayer::MovieAudio(int audioOffset, int blobs) { if (audioOffset == 0 && blobs == 0) blobs = 57; byte *data = (byte *)malloc(blobs * 128); if (audioOffset != 0) PrepAudio(bigBuffer+audioOffset, blobs, data); else memset(data, 0, blobs * 128); _audioStream->queueBuffer(data, blobs * 128, DisposeAfterUse::YES, Audio::FLAG_16BITS | Audio::FLAG_STEREO); if (currentSoundFrame == ADVANCE_SOUND) { if (!audioStarted) { _vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_audioHandle, _audioStream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO); audioStarted = true; } } } /*-----------------------------------------------------*\ |-------------------------------------------------------| \*-----------------------------------------------------*/ void BMVPlayer::FettleMovieText() { int i; bIsText = false; for (i = 0; i < 2; i++) { if (texts[i].pText) { if (currentFrame > texts[i].dieFrame) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), texts[i].pText); texts[i].pText = NULL; } else { MultiForceRedraw(texts[i].pText); bIsText = true; } } } } /*-----------------------------------------------------*\ |-------------------------------------------------------| \*-----------------------------------------------------*/ void BMVPlayer::BmvDrawText(bool bDraw) { int w, h, x, y; for (int i = 0; i < 2; i++) { if (texts[i].pText) { x = MultiLeftmost(texts[i].pText); y = MultiHighest(texts[i].pText); w = MIN(MultiRightmost(texts[i].pText) + 1, (int)SCREEN_WIDTH) - x; h = MIN(MultiLowest(texts[i].pText) + 1, SCREEN_HIGH) - y; const byte *src = ScreenBeg + (y * SCREEN_WIDTH) + x; byte *dest = (byte *)_vm->screen().getBasePtr(x, y); for (int j = 0; j < h; j++, dest += SCREEN_WIDTH, src += SCREEN_WIDTH) { memcpy(dest, src, w); } if (bDraw) { Common::Point ptWin; Common::Rect rcPlayClip; ptWin.x = ptWin.y = 0; rcPlayClip.left = x; rcPlayClip.top = y; rcPlayClip.right = x+w; rcPlayClip.bottom = y+h; UpdateClipRect(GetPlayfieldList(FIELD_STATUS), &ptWin, &rcPlayClip); } } } } /*-----------------------------------------------------*\ |-------------------------------------------------------| \*-----------------------------------------------------*/ void BMVPlayer::MovieText(CORO_PARAM, int stringId, int x, int y, int fontId, COLORREF *pTalkColor, int duration) { SCNHANDLE hFont; int index; if (fontId == 1) { // It's a 'print' hFont = GetTagFontHandle(); index = 0; } else { // It's a 'talk' if (pTalkColor != NULL) SetTextPal(*pTalkColor); hFont = GetTalkFontHandle(); index = 1; } if (texts[index].pText) MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), texts[index].pText); LoadSubString(stringId, 0, TextBufferAddr(), TBUFSZ); texts[index].dieFrame = currentFrame + duration; texts[index].pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), 0, x, y, hFont, TXT_CENTER, 0); KeepOnScreen(texts[index].pText, &x, &y); } /** * Called when a packet contains a command field. */ int BMVPlayer::MovieCommand(char cmd, int commandOffset) { if (cmd & CD_PRINT) { PRINT_CMD *pCmd = (PRINT_CMD *)(bigBuffer + commandOffset); MovieText(Common::nullContext, (int16)READ_16(&pCmd->stringId), (int16)READ_16(&pCmd->x), (int16)READ_16(&pCmd->y), pCmd->fontId, NULL, pCmd->duration); return sz_CMD_PRINT_pkt; } else { if (_vm->_config->_useSubtitles) { TALK_CMD *pCmd = (TALK_CMD *)(bigBuffer + commandOffset); talkColor = TINSEL_RGB(pCmd->r, pCmd->g, pCmd->b); MovieText(Common::nullContext, (int16)READ_16(&pCmd->stringId), (int16)READ_16(&pCmd->x), (int16)READ_16(&pCmd->y), 0, &talkColor, pCmd->duration); } return sz_CMD_TALK_pkt; } } /** * Called from PlayMovie() in tinlib.cpp * Kicks off the playback of a movie, and waits around * until it's finished. */ void BMVPlayer::PlayBMV(CORO_PARAM, SCNHANDLE hFileStem, int myEscape) { CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); assert(!bMovieOn); Common::strlcpy(szMovieFile, (char *)LockMem(hFileStem), 14); Common::strlcat(szMovieFile, BMOVIE_EXTENSION, 14); assert(strlen(szMovieFile) <= 12); bMovieOn = true; bAbort = false; bmvEscape = myEscape; do { CORO_SLEEP(1); } while (bMovieOn); CORO_END_CODE; } /** * Given a packet offset, calculates the offset of the * next packet. The packet may not yet exist, and the *return value may be off the end of bigBuffer. */ int BMVPlayer::FollowingPacket(int thisPacket, bool bReallyImportant) { unsigned char *data; int nextSlot, length; // Set pointer to thisPacket's data data = bigBuffer + thisPacket; switch (*data) { case CD_SLOT_NOP: nextSlot = thisPacket/SLOT_SIZE; if (thisPacket%SLOT_SIZE) nextSlot++; return nextSlot * SLOT_SIZE; case CD_LE_FIN: return -1; default: // Following 3 bytes are the length if (bReallyImportant) { // wrapped round or at least 3 bytes assert(((nextReadSlot * SLOT_SIZE) < thisPacket) || ((thisPacket + 3) < (nextReadSlot * SLOT_SIZE))); if ((nextReadSlot * SLOT_SIZE >= thisPacket) && ((thisPacket + 3) >= nextReadSlot*SLOT_SIZE)) { // MaintainBuffer calls this back, but with false MaintainBuffer(); } } else { // not wrapped and not 3 bytes if (nextReadSlot*SLOT_SIZE >= thisPacket && thisPacket+3 >= nextReadSlot*SLOT_SIZE) return thisPacket + 3; } length = (int32)READ_32(bigBuffer + thisPacket + 1); length &= 0x00ffffff; return thisPacket + length + 4; } } /** * Called from the foreground when starting playback of a movie. */ void BMVPlayer::LoadSlots(int number) { int nextOffset; assert(number + nextReadSlot < NUM_SLOTS); if (stream.read(bigBuffer + nextReadSlot*SLOT_SIZE, number * SLOT_SIZE) != (uint32)(number * SLOT_SIZE)) { int possibleSlots; // May be a short file possibleSlots = stream.size() / SLOT_SIZE; if ((number + nextReadSlot) > possibleSlots) { bFileEnd = true; nextReadSlot = possibleSlots; } else error(FILE_IS_CORRUPT, szMovieFile); } nextReadSlot += number; nextOffset = FollowingPacket(nextUseOffset, true); while (nextOffset < nextReadSlot*SLOT_SIZE && nextOffset != -1) { numAdvancePackets++; mostFutureOffset = nextOffset; nextOffset = FollowingPacket(mostFutureOffset, false); } } /** * Called from the foreground when starting playback of a movie. */ void BMVPlayer::InitializeBMV() { if (!stream.open(szMovieFile)) error(CANNOT_FIND_FILE, szMovieFile); // Grab the data buffer bigBuffer = (byte *)malloc(NUM_SLOTS * SLOT_SIZE); if (bigBuffer == NULL) error(NO_MEM, "FMV data buffer"); // Screen buffer (2 lines more than screen screenBuffer = (byte *)malloc(SCREEN_WIDTH * (SCREEN_HIGH + 2)); if (screenBuffer == NULL) error(NO_MEM, "FMV screen buffer"); // Pass the sceen buffer to the decompresser InitBMV(screenBuffer); // Initialize some stuff nextUseOffset = 0; nextSoundOffset = 0; wrapUseOffset = -1; mostFutureOffset = 0; currentFrame = 0; currentSoundFrame = 0; numAdvancePackets = 0; nextReadSlot = 0; bFileEnd = false; blobsInBuffer = 0; memset(texts, 0, sizeof(texts)); talkColor = 0; bigProblemCount = 0; movieTick = 0; bIsText = false; // Prefetch data LoadSlots(PREFETCH); while (numAdvancePackets < ADVANCE_SOUND) LoadSlots(1); // Initialize the sound channel InitializeMovieSound(); } /** * Called from the foreground when ending playback of a movie. */ void BMVPlayer::FinishBMV() { int i; // Notify the sound channel FinishMovieSound(); // Close the file stream if (stream.isOpen()) stream.close(); // Release the data buffer free(bigBuffer); bigBuffer = NULL; // Release the screen buffer free(screenBuffer); screenBuffer = NULL; // Ditch any text objects for (i = 0; i < 2; i++) { if (texts[i].pText) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), texts[i].pText); texts[i].pText = NULL; } } bMovieOn = false; nextMovieTime = 0; // Test for 'twixt-movie glitch ClearScreen(); } /** * MaintainBuffer() */ bool BMVPlayer::MaintainBuffer() { int nextOffset; // No action if the file is all read if (bFileEnd == true) return false; // See if next complete packet exists // and if so, if it will fit in the top of the buffer nextOffset = FollowingPacket(mostFutureOffset, false); if (nextOffset == -1) { // No following packets return false; } else if (nextOffset > NUM_SLOTS * SLOT_SIZE) { // The current unfinished packet will not fit // Copy this slot to slot 0 // Not if we're still using it!!! // Or, indeed, if the player is still lagging if (nextUseOffset < SLOT_SIZE || nextUseOffset > mostFutureOffset) { // Slot 0 is still in use, buffer is full! return false; } // Tell data player where to make the jump wrapUseOffset = mostFutureOffset; // mostFuture Offset is now in slot 0 mostFutureOffset %= SLOT_SIZE; // Copy the data we already have for unfinished packet memcpy(bigBuffer + mostFutureOffset, bigBuffer + wrapUseOffset, SLOT_SIZE - mostFutureOffset); // Next read is into slot 1 nextReadSlot = 1; } if (nextReadSlot == NUM_SLOTS) { // Want to go to slot zero, wait if still in use if (nextUseOffset < SLOT_SIZE) { // Slot 0 is still in use, buffer is full! return false; } // nextOffset must be the buffer size assert(nextOffset == NUM_SLOTS*SLOT_SIZE); // wrapUseOffset must not be set assert(wrapUseOffset == -1); wrapUseOffset = nextOffset; nextReadSlot = 0; mostFutureOffset = 0; } // Don't overwrite unused data if (nextUseOffset / SLOT_SIZE == nextReadSlot) { // Buffer is full! return false; } if (stream.read(bigBuffer + nextReadSlot * SLOT_SIZE, SLOT_SIZE) != SLOT_SIZE) { bFileEnd = true; } // Read next slot next time nextReadSlot++; // Find new mostFutureOffset nextOffset = FollowingPacket(mostFutureOffset, false); while (nextOffset < nextReadSlot*SLOT_SIZE && nextOffset != -1) { numAdvancePackets++; mostFutureOffset = nextOffset; nextOffset = FollowingPacket(mostFutureOffset, false); } // New test feature for e.g. short files if (bFileEnd && *(bigBuffer+mostFutureOffset) != CD_LE_FIN) bAbort = true; return true; } /** * DoBMVFrame */ bool BMVPlayer::DoBMVFrame() { unsigned char *data; int graphOffset, length; signed short xscr; if (nextUseOffset == wrapUseOffset) { nextUseOffset %= SLOT_SIZE; } while (nextUseOffset == mostFutureOffset) { data = bigBuffer + nextUseOffset; if (*data != CD_LE_FIN) { // Don't get stuck in an infinite loop if (!MaintainBuffer()) { FinishBMV(); return false; } if (nextUseOffset == wrapUseOffset) { nextUseOffset %= SLOT_SIZE; } } else break; } // Set pointer to data data = bigBuffer + nextUseOffset; // If still at most Future, it must be last if (nextUseOffset == mostFutureOffset) { assert(*data == CD_LE_FIN); } switch (*data) { case CD_SLOT_NOP: nextUseOffset = FollowingPacket(nextUseOffset, true); if (nextUseOffset == wrapUseOffset) { nextUseOffset %= SLOT_SIZE; wrapUseOffset = -1; } numAdvancePackets--; return false; case CD_LE_FIN: FinishBMV(); numAdvancePackets--; return true; default: length = (int32)READ_32(data + 1); length &= 0x00ffffff; graphOffset = nextUseOffset + 4; // Skip command byte and length if (*data & CD_AUDIO) { if (bOldAudio) { graphOffset += sz_AUDIO_pkt; // Skip audio data length -= sz_AUDIO_pkt; } else { int blobs; blobs = *(bigBuffer + graphOffset); blobs *= SZ_C_BLOB; graphOffset += (blobs + 1); length -= (blobs + 1); } } if (*data & CD_CMND) { int cmdLen; // Process command and skip data cmdLen = MovieCommand(*data, graphOffset); graphOffset += cmdLen; length -= cmdLen; } if (*data & CD_CMAP) { MoviePalette(graphOffset); graphOffset += sz_CMAP_pkt; // Skip palette data length -= sz_CMAP_pkt; } if (*data & CD_XSCR) { xscr = (int16)READ_16(bigBuffer + graphOffset); graphOffset += sz_XSCR_pkt; // Skip scroll offset length -= sz_XSCR_pkt; } else if (*data & BIT0) xscr = -640; else xscr = 0; PrepBMV(ScreenBeg, bigBuffer + graphOffset, length, xscr); currentFrame++; numAdvancePackets--; nextUseOffset = FollowingPacket(nextUseOffset, true); if (nextUseOffset == wrapUseOffset) { nextUseOffset %= SLOT_SIZE; wrapUseOffset = -1; } return true; } } /** * DoSoundFrame */ bool BMVPlayer::DoSoundFrame() { unsigned char *data; int graphOffset; if (nextSoundOffset == wrapUseOffset) { nextSoundOffset %= SLOT_SIZE; } // Make sure the full slot is here while (nextSoundOffset == mostFutureOffset) { data = bigBuffer + nextSoundOffset; if (*data != CD_LE_FIN) { // Don't get stuck in an infinite loop if (!MaintainBuffer()) { if (!bOldAudio) MovieAudio(0, 0); currentSoundFrame++; return false; } if (nextSoundOffset == wrapUseOffset) { nextSoundOffset %= SLOT_SIZE; } } else break; } // Set pointer to data data = bigBuffer + nextSoundOffset; // If still at most Future, it must be last if (nextSoundOffset == mostFutureOffset) { assert(*data == CD_LE_FIN); } switch (*data) { case CD_SLOT_NOP: nextSoundOffset = FollowingPacket(nextSoundOffset, true); if (nextSoundOffset == wrapUseOffset) { nextSoundOffset %= SLOT_SIZE; } return false; case CD_LE_FIN: if (!bOldAudio) MovieAudio(0, 0); currentSoundFrame++; return true; default: if (*data & CD_AUDIO) { graphOffset = nextSoundOffset + 4; // Skip command byte and length if (!bOldAudio) { int blobs = *(bigBuffer + graphOffset); MovieAudio(graphOffset+1, blobs); } } else { if (!bOldAudio) MovieAudio(0, 0); } nextSoundOffset = FollowingPacket(nextSoundOffset, false); if (nextSoundOffset == wrapUseOffset) { nextSoundOffset %= SLOT_SIZE; } currentSoundFrame++; return true; } } /** * CopyMovieToScreen */ void BMVPlayer::CopyMovieToScreen() { // Not if not up and running yet! if (!screenBuffer || (currentFrame == 0)) { DrawBackgnd(); return; } // The movie surface is slightly less high than the output screen (429 rows versus 432). // Because of this, there's some extra line clearing above and below the displayed area int yStart = (SCREEN_HEIGHT - SCREEN_HIGH) / 2; memset(_vm->screen().getPixels(), 0, yStart * SCREEN_WIDTH); memcpy(_vm->screen().getBasePtr(0, yStart), ScreenBeg, SCREEN_WIDTH * SCREEN_HIGH); memset(_vm->screen().getBasePtr(0, yStart + SCREEN_HIGH), 0, (SCREEN_HEIGHT - SCREEN_HIGH - yStart) * SCREEN_WIDTH); BmvDrawText(true); PalettesToVideoDAC(); // Keep palette up-to-date UpdateScreenRect(Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)); g_system->updateScreen(); BmvDrawText(false); } /** * Handles playback of any active movie. Called from the foreground 24 times a second. */ void BMVPlayer::FettleBMV() { int refFrame; // Tick counter needs to be incrementing at a 24Hz rate int tick = movieTick++; if (!bMovieOn) return; // Escape the rest if appropriate if (bAbort || (bmvEscape && bmvEscape != GetEscEvents())) { FinishBMV(); return; } if (!stream.isOpen()) { int i; // First time in with this movie InitializeBMV(); for (i = 0; i < ADVANCE_SOUND;) { if (DoSoundFrame()) i++; } startTick = -ONE_SECOND / 4; // 1/4 second return; } if (startTick < 0) { startTick++; return; } if (startTick == 0) { startTick = tick; nextMaintain = startTick + 1; StartMovieSound(); } nextMovieTime = g_system->getMillis() + 41; FettleMovieText(); if (bigProblemCount < PT_A) { refFrame = currentSoundFrame; while (currentSoundFrame < ((tick+1-startTick)/2 + ADVANCE_SOUND) && bMovieOn) { if (currentSoundFrame == refFrame+PT_B) break; DoSoundFrame(); } } // Time to process a frame (or maybe more) if (bigProblemCount < PT_A) { refFrame = currentFrame; while ((currentFrame < (tick-startTick)/2) && bMovieOn) { DoBMVFrame(); if (currentFrame == refFrame+PT_B) { bigProblemCount++; if (bigProblemCount == PT_A) { startTick = tick-(2*currentFrame); bigProblemCount = 0; } break; } } if (currentFrame == refFrame || currentFrame <= refFrame+3) { bigProblemCount = 0; } } else { while (currentFrame < (tick-startTick)/2 && bMovieOn) { DoBMVFrame(); } } if (tick >= nextMaintain || numAdvancePackets < SUBSEQUENT_SOUND) { MaintainBuffer(); nextMaintain = tick + 2; } } /** * Returns true if a movie is playing. */ bool BMVPlayer::MoviePlaying() { return bMovieOn; } /** * Returns the audio lag in ms */ int32 BMVPlayer::MovieAudioLag() { if (!bMovieOn || !_audioStream) return 0; // Calculate lag int32 playLength = (movieTick - startTick - 1) * ((((uint32) 1000) << 10) / 24); return (playLength - (((int32) _vm->_mixer->getSoundElapsedTime(_audioHandle)) << 10)) >> 10; } uint32 BMVPlayer::NextMovieTime() { return nextMovieTime; } void BMVPlayer::AbortMovie() { bAbort = true; } } // End of namespace Tinsel