diff options
| author | Eugene Sandulenko | 2008-12-21 21:08:17 +0000 |
|---|---|---|
| committer | Eugene Sandulenko | 2008-12-21 21:08:17 +0000 |
| commit | 829cbc411094bd69052ab97c7846f2f8683796b9 (patch) | |
| tree | 32d04552a0e59745d90bf1b9bc93ced4b3df3b61 /graphics/video | |
| parent | be66a538b44966c52a48cc59421ccd7b7c27dedc (diff) | |
| download | scummvm-rg350-829cbc411094bd69052ab97c7846f2f8683796b9.tar.gz scummvm-rg350-829cbc411094bd69052ab97c7846f2f8683796b9.tar.bz2 scummvm-rg350-829cbc411094bd69052ab97c7846f2f8683796b9.zip | |
Move all video players to separate directory
svn-id: r35470
Diffstat (limited to 'graphics/video')
| -rw-r--r-- | graphics/video/dxa_player.cpp | 595 | ||||
| -rw-r--r-- | graphics/video/dxa_player.h | 141 | ||||
| -rw-r--r-- | graphics/video/flic_player.cpp | 276 | ||||
| -rw-r--r-- | graphics/video/flic_player.h | 102 | ||||
| -rw-r--r-- | graphics/video/mpeg_player.cpp | 629 | ||||
| -rw-r--r-- | graphics/video/mpeg_player.h | 163 | ||||
| -rw-r--r-- | graphics/video/smk_player.cpp | 887 | ||||
| -rw-r--r-- | graphics/video/smk_player.h | 196 |
8 files changed, 2989 insertions, 0 deletions
diff --git a/graphics/video/dxa_player.cpp b/graphics/video/dxa_player.cpp new file mode 100644 index 0000000000..39c44a4770 --- /dev/null +++ b/graphics/video/dxa_player.cpp @@ -0,0 +1,595 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/endian.h" +#include "common/archive.h" +#include "graphics/video/dxa_player.h" +#include "common/util.h" + +#ifdef USE_ZLIB + #include "common/zlib.h" +#endif + +namespace Graphics { + +DXAPlayer::DXAPlayer() { + _fileStream = 0; + + _frameBuffer1 = 0; + _frameBuffer2 = 0; + _scaledBuffer = 0; + _drawBuffer = 0; + + _inBuffer = 0; + _inBufferSize = 0; + + _decompBuffer = 0; + _decompBufferSize = 0; + + _width = 0; + _height = 0; + + _frameSize = 0; + _framesCount = 0; + _frameNum = 0; + _framesPerSec = 0; + _frameTicks = 0; + + _scaleMode = S_NONE; +} + +DXAPlayer::~DXAPlayer() { + closeFile(); +} + +int DXAPlayer::getWidth() { + if (!_fileStream) + return 0; + return _width; +} + +int DXAPlayer::getHeight() { + if (!_fileStream) + return 0; + return _height; +} + +int32 DXAPlayer::getCurFrame() { + if (!_fileStream) + return -1; + return _frameNum; +} + +int32 DXAPlayer::getFrameCount() { + if (!_fileStream) + return 0; + return _framesCount; +} + +int32 DXAPlayer::getFrameRate() { + if (!_fileStream) + return 0; + return _framesPerSec; +} + +int32 DXAPlayer::getFrameDelay() { + if (!_fileStream) + return 0; + + return _frameTicks; +} + +bool DXAPlayer::loadFile(const char *fileName) { + uint32 tag; + int32 frameRate; + + _fileStream = SearchMan.openFile(fileName); + if (!_fileStream) + return false; + + tag = _fileStream->readUint32BE(); + assert(tag == MKID_BE('DEXA')); + + uint8 flags = _fileStream->readByte(); + _framesCount = _fileStream->readUint16BE(); + frameRate = _fileStream->readUint32BE(); + + if (frameRate > 0) + _framesPerSec = 1000 / frameRate; + else if (frameRate < 0) + _framesPerSec = 100000 / (-frameRate); + else + _framesPerSec = 10; + + if (frameRate < 0) + _frameTicks = -frameRate / 100; + else + _frameTicks = frameRate; + + _width = _fileStream->readUint16BE(); + _height = _fileStream->readUint16BE(); + + if (flags & 0x80) { + _scaleMode = S_INTERLACED; + _curHeight = _height / 2; + } else if (flags & 0x40) { + _scaleMode = S_DOUBLE; + _curHeight = _height / 2; + } else { + _scaleMode = S_NONE; + _curHeight = _height; + } + + debug(2, "flags 0x0%x framesCount %d width %d height %d rate %d ticks %d", flags, getFrameCount(), getWidth(), getHeight(), getFrameRate(), getFrameDelay()); + + _frameSize = _width * _height; + _decompBufferSize = _frameSize; + _frameBuffer1 = (uint8 *)malloc(_frameSize); + _frameBuffer2 = (uint8 *)malloc(_frameSize); + if (!_frameBuffer1 || !_frameBuffer2) + error("DXAPlayer: Error allocating frame buffers (size %u)", _frameSize); + + _scaledBuffer = 0; + if (_scaleMode != S_NONE) { + _scaledBuffer = (uint8 *)malloc(_frameSize); + if (!_scaledBuffer) + error("Error allocating scale buffer (size %u)", _frameSize); + } + +#ifdef DXA_EXPERIMENT_MAXD + // Check for an extended header + if (flags & 1) { + uint32 size; + + do { + tag = _fileStream->readUint32BE(); + if (tag != 0) { + size = _fileStream->readUint32BE(); + } + switch (tag) { + case 0: // No more tags + break; + case MKID_BE('MAXD'): + assert(size == 4); + _decompBufferSize = _fileStream->readUint32BE(); + break; + default: // Unknown tag - skip it. + while (size > 0) { + byte dummy = _fileStream->readByte(); + size--; + } + break; + } + } while (tag != 0); + } +#endif + _frameNum = 0; + + return true; +} + +void DXAPlayer::closeFile() { + if (!_fileStream) + return; + + delete _fileStream; + + free(_frameBuffer1); + free(_frameBuffer2); + free(_scaledBuffer); + free(_inBuffer); + free(_decompBuffer); + + _fileStream = 0; + _inBuffer = 0; + _decompBuffer = 0; +} + +void DXAPlayer::copyFrameToBuffer(byte *dst, uint x, uint y, uint pitch) { + uint h = _height; + uint w = _width; + + byte *src = _drawBuffer; + dst += y * pitch + x; + + do { + memcpy(dst, src, w); + dst += pitch; + src += _width; + } while (--h); +} + +void DXAPlayer::decodeZlib(byte *data, int size, int totalSize) { +#ifdef USE_ZLIB + unsigned long dstLen = totalSize; + Common::uncompress(data, &dstLen, _inBuffer, size); +#endif +} + +#define BLOCKW 4 +#define BLOCKH 4 + +void DXAPlayer::decode12(int size) { +#ifdef USE_ZLIB + if (_decompBuffer == NULL) { + _decompBuffer = (byte *)malloc(_decompBufferSize); + if (_decompBuffer == NULL) + error("Error allocating decomp buffer (size %u)", _decompBufferSize); + } + /* decompress the input data */ + decodeZlib(_decompBuffer, size, _decompBufferSize); + + byte *dat = _decompBuffer; + + memcpy(_frameBuffer2, _frameBuffer1, _frameSize); + + for (int by = 0; by < _height; by += BLOCKH) { + for (int bx = 0; bx < _width; bx += BLOCKW) { + byte type = *dat++; + byte *b2 = _frameBuffer1 + bx + by * _width; + + switch (type) { + case 0: + break; + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 1: { + unsigned short diffMap; + if (type >= 10 && type <= 15) { + static const struct { uint8 sh1, sh2; } shiftTbl[6] = { + {0, 0}, {8, 0}, {8, 8}, {8, 4}, {4, 0}, {4, 4} + }; + diffMap = ((*dat & 0xF0) << shiftTbl[type-10].sh1) | + ((*dat & 0x0F) << shiftTbl[type-10].sh2); + dat++; + } else { + diffMap = *(unsigned short*)dat; + dat += 2; + } + + for (int yc = 0; yc < BLOCKH; yc++) { + for (int xc = 0; xc < BLOCKW; xc++) { + if (diffMap & 0x8000) { + b2[xc] = *dat++; + } + diffMap <<= 1; + } + b2 += _width; + } + break; + } + case 2: { + byte color = *dat++; + + for (int yc = 0; yc < BLOCKH; yc++) { + for (int xc = 0; xc < BLOCKW; xc++) { + b2[xc] = color; + } + b2 += _width; + } + break; + } + case 3: { + for (int yc = 0; yc < BLOCKH; yc++) { + for (int xc = 0; xc < BLOCKW; xc++) { + b2[xc] = *dat++; + } + b2 += _width; + } + break; + } + case 4: { + byte mbyte = *dat++; + int mx = (mbyte >> 4) & 0x07; + if (mbyte & 0x80) + mx = -mx; + int my = mbyte & 0x07; + if (mbyte & 0x08) + my = -my; + byte *b1 = _frameBuffer2 + (bx+mx) + (by+my) * _width; + for (int yc = 0; yc < BLOCKH; yc++) { + memcpy(b2, b1, BLOCKW); + b1 += _width; + b2 += _width; + } + break; + } + case 5: + break; + default: + error("decode12: Unknown type %d", type); + } + } + } +#endif +} + +void DXAPlayer::decode13(int size) { +#ifdef USE_ZLIB + uint8 *codeBuf, *dataBuf, *motBuf, *maskBuf; + + if (_decompBuffer == NULL) { + _decompBuffer = (byte *)malloc(_decompBufferSize); + if (_decompBuffer == NULL) + error("Error allocating decomp buffer (size %u)", _decompBufferSize); + } + + /* decompress the input data */ + decodeZlib(_decompBuffer, size, _decompBufferSize); + + memcpy(_frameBuffer2, _frameBuffer1, _frameSize); + + int codeSize = _width * _curHeight / 16; + int dataSize, motSize, maskSize; + + dataSize = READ_BE_UINT32(&_decompBuffer[0]); + motSize = READ_BE_UINT32(&_decompBuffer[4]); + maskSize = READ_BE_UINT32(&_decompBuffer[8]); + + codeBuf = &_decompBuffer[12]; + dataBuf = &codeBuf[codeSize]; + motBuf = &dataBuf[dataSize]; + maskBuf = &motBuf[motSize]; + + for (int by = 0; by < _curHeight; by += BLOCKH) { + for (int bx = 0; bx < _width; bx += BLOCKW) { + uint8 type = *codeBuf++; + uint8 *b2 = (uint8*)_frameBuffer1 + bx + by * _width; + + switch (type) { + case 0: + break; + + case 1: { + uint16 diffMap = READ_BE_UINT16(maskBuf); + maskBuf += 2; + + for (int yc = 0; yc < BLOCKH; yc++) { + for (int xc = 0; xc < BLOCKW; xc++) { + if (diffMap & 0x8000) { + b2[xc] = *dataBuf++; + } + diffMap <<= 1; + } + b2 += _width; + } + break; + } + case 2: { + uint8 color = *dataBuf++; + + for (int yc = 0; yc < BLOCKH; yc++) { + for (int xc = 0; xc < BLOCKW; xc++) { + b2[xc] = color; + } + b2 += _width; + } + break; + } + case 3: { + for (int yc = 0; yc < BLOCKH; yc++) { + for (int xc = 0; xc < BLOCKW; xc++) { + b2[xc] = *dataBuf++; + } + b2 += _width; + } + break; + } + case 4: { + uint8 mbyte = *motBuf++; + + int mx = (mbyte >> 4) & 0x07; + if (mbyte & 0x80) + mx = -mx; + int my = mbyte & 0x07; + if (mbyte & 0x08) + my = -my; + + uint8 *b1 = (uint8*)_frameBuffer2 + (bx+mx) + (by+my) * _width; + for (int yc = 0; yc < BLOCKH; yc++) { + memcpy(b2, b1, BLOCKW); + b1 += _width; + b2 += _width; + } + break; + } + case 8: { + static const int subX[4] = {0, 2, 0, 2}; + static const int subY[4] = {0, 0, 2, 2}; + + uint8 subMask = *maskBuf++; + + for (int subBlock = 0; subBlock < 4; subBlock++) { + int sx = bx + subX[subBlock], sy = by + subY[subBlock]; + b2 = (uint8*)_frameBuffer1 + sx + sy * _width; + switch (subMask & 0xC0) { + // 00: skip + case 0x00: + break; + // 01: solid color + case 0x40: { + uint8 subColor = *dataBuf++; + for (int yc = 0; yc < BLOCKH / 2; yc++) { + for (int xc = 0; xc < BLOCKW / 2; xc++) { + b2[xc] = subColor; + } + b2 += _width; + } + break; + } + // 02: motion vector + case 0x80: { + uint8 mbyte = *motBuf++; + + int mx = (mbyte >> 4) & 0x07; + if (mbyte & 0x80) + mx = -mx; + + int my = mbyte & 0x07; + if (mbyte & 0x08) + my = -my; + + uint8 *b1 = (uint8*)_frameBuffer2 + (sx+mx) + (sy+my) * _width; + for (int yc = 0; yc < BLOCKH / 2; yc++) { + memcpy(b2, b1, BLOCKW / 2); + b1 += _width; + b2 += _width; + } + break; + } + // 03: raw + case 0xC0: + for (int yc = 0; yc < BLOCKH / 2; yc++) { + for (int xc = 0; xc < BLOCKW / 2; xc++) { + b2[xc] = *dataBuf++; + } + b2 += _width; + } + break; + } + subMask <<= 2; + } + break; + } + case 32: + case 33: + case 34: { + int count = type - 30; + uint8 pixels[4]; + + memcpy(pixels, dataBuf, count); + dataBuf += count; + + if (count == 2) { + uint16 code = READ_BE_UINT16(maskBuf); + maskBuf += 2; + for (int yc = 0; yc < BLOCKH; yc++) { + for (int xc = 0; xc < BLOCKW; xc++) { + b2[xc] = pixels[code & 1]; + code >>= 1; + } + b2 += _width; + } + } else { + uint32 code = READ_BE_UINT32(maskBuf); + maskBuf += 4; + for (int yc = 0; yc < BLOCKH; yc++) { + for (int xc = 0; xc < BLOCKW; xc++) { + b2[xc] = pixels[code & 3]; + code >>= 2; + } + b2 += _width; + } + } + break; + } + default: + error("decode13: Unknown type %d", type); + } + } + } +#endif +} + +void DXAPlayer::decodeNextFrame() { + uint32 tag; + + tag = _fileStream->readUint32BE(); + if (tag == MKID_BE('CMAP')) { + byte rgb[768]; + + _fileStream->read(rgb, ARRAYSIZE(rgb)); + setPalette(rgb); + } + + tag = _fileStream->readUint32BE(); + if (tag == MKID_BE('FRAM')) { + byte type = _fileStream->readByte(); + uint32 size = _fileStream->readUint32BE(); + if ((_inBuffer == NULL) || (_inBufferSize < size)) { + free(_inBuffer); + _inBuffer = (byte *)malloc(size); + if (_inBuffer == NULL) + error("Error allocating input buffer (size %u)", size); + _inBufferSize = size; + } + + _fileStream->read(_inBuffer, size); + + switch (type) { + case 2: + decodeZlib(_frameBuffer1, size, _frameSize); + break; + case 3: + decodeZlib(_frameBuffer2, size, _frameSize); + break; + case 12: + decode12(size); + break; + case 13: + decode13(size); + break; + default: + error("decodeFrame: Unknown compression type %d", type); + } + + if (type == 3) { + for (int j = 0; j < _curHeight; ++j) { + for (int i = 0; i < _width; ++i) { + const int offs = j * _width + i; + _frameBuffer1[offs] ^= _frameBuffer2[offs]; + } + } + } + } + + switch (_scaleMode) { + case S_INTERLACED: + for (int cy = 0; cy < _curHeight; cy++) { + memcpy(&_scaledBuffer[2 * cy * _width], &_frameBuffer1[cy * _width], _width); + memset(&_scaledBuffer[((2 * cy) + 1) * _width], 0, _width); + } + _drawBuffer = _scaledBuffer; + break; + case S_DOUBLE: + for (int cy = 0; cy < _curHeight; cy++) { + memcpy(&_scaledBuffer[2 * cy * _width], &_frameBuffer1[cy * _width], _width); + memcpy(&_scaledBuffer[((2 * cy) + 1) * _width], &_frameBuffer1[cy * _width], _width); + } + _drawBuffer = _scaledBuffer; + break; + case S_NONE: + _drawBuffer = _frameBuffer1; + break; + } + + _frameNum++; +} + +} // End of namespace Graphics diff --git a/graphics/video/dxa_player.h b/graphics/video/dxa_player.h new file mode 100644 index 0000000000..8ead07f2ae --- /dev/null +++ b/graphics/video/dxa_player.h @@ -0,0 +1,141 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef GRAPHICS_VIDEO_DXA_PLAYER_H +#define GRAPHICS_VIDEO_DXA_PLAYER_H + +#include "common/scummsys.h" +#include "common/stream.h" + +namespace Graphics { + +enum ScaleMode { + S_NONE, + S_INTERLACED, + S_DOUBLE +}; + +class DXAPlayer { +protected: + byte *_frameBuffer1; + byte *_frameBuffer2; + byte *_scaledBuffer; + byte *_drawBuffer; + byte *_inBuffer; + uint32 _inBufferSize; + byte *_decompBuffer; + uint32 _decompBufferSize; + uint16 _width; + uint16 _height, _curHeight; + uint16 _framesCount; + uint32 _framesPerSec; + uint16 _frameNum; + uint32 _frameSize; + uint32 _frameTicks; + ScaleMode _scaleMode; + +public: + DXAPlayer(); + virtual ~DXAPlayer(); + + /** + * Returns the width of the video + * @return the width of the video + */ + int getWidth(); + + /** + * Returns the height of the video + * @return the height of the video + */ + int getHeight(); + + /** + * Returns the current frame number of the video + * @return the current frame number of the video + */ + int32 getCurFrame(); + + /** + * Returns the amount of frames in the video + * @return the amount of frames in the video + */ + int32 getFrameCount(); + + /** + * Returns the frame rate of the video + * @return the frame rate of the video + */ + int32 getFrameRate(); + + /** + * Returns the time to wait for each frame in 1/100 ms + * @return the time to wait for each frame in 1/100 ms + */ + int32 getFrameDelay(); + + /** + * Load a DXA encoded video file + * @param filename the filename to load + */ + bool loadFile(const char *fileName); + + /** + * Close a DXA encoded video file + */ + void closeFile(); + +protected: + /** + * Set palette, based on current frame + * @param pal the palette data + */ + virtual void setPalette(byte *pal) = 0; + + /** + * Copy current frame into the specified position of the destination + * buffer. + * @param dst the buffer + * @param x the x position of the buffer + * @param y the y position of the buffer + * @param pitch the pitch of buffer + */ + void copyFrameToBuffer(byte *dst, uint x, uint y, uint pitch); + + /** + * Decode the next frame + */ + void decodeNextFrame(); + + void decodeZlib(byte *data, int size, int totalSize); + void decode12(int size); + void decode13(int size); + + Common::SeekableReadStream *_fileStream; +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/video/flic_player.cpp b/graphics/video/flic_player.cpp new file mode 100644 index 0000000000..d4a8ede4f8 --- /dev/null +++ b/graphics/video/flic_player.cpp @@ -0,0 +1,276 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "graphics/video/flic_player.h" + +namespace Graphics { + +FlicPlayer::FlicPlayer() + : _paletteDirty(false), _offscreen(0), _currFrame(0) { + memset(&_flicInfo, 0, sizeof(_flicInfo)); +} + +FlicPlayer::~FlicPlayer() { + closeFile(); +} + +bool FlicPlayer::loadFile(const char *fileName) { + closeFile(); + + if (!_fileStream.open(fileName)) { + return false; + } + + _flicInfo.size = _fileStream.readUint32LE(); + _flicInfo.type = _fileStream.readUint16LE(); + _flicInfo.numFrames = _fileStream.readUint16LE(); + _flicInfo.width = _fileStream.readUint16LE(); + _flicInfo.height = _fileStream.readUint16LE(); + _fileStream.skip(4); + _flicInfo.speed = _fileStream.readUint32LE(); + + _fileStream.seek(80); + _flicInfo.offsetFrame1 = _fileStream.readUint32LE(); + _flicInfo.offsetFrame2 = _fileStream.readUint32LE(); + + // Check FLC magic number + if (_flicInfo.type != 0xAF12) { + error("FlicPlayer::FlicPlayer(): attempted to load non-FLC data (type = 0x%04X)", _flicInfo.type); + } + + _offscreen = new uint8[_flicInfo.width * _flicInfo.height]; + memset(_palette, 0, sizeof(_palette)); + _paletteDirty = false; + + // Seek to the first frame + _currFrame = 0; + _fileStream.seek(_flicInfo.offsetFrame1); + return true; +} + +void FlicPlayer::closeFile() { + memset(&_flicInfo, 0, sizeof(_flicInfo)); + _fileStream.close(); + delete[] _offscreen; + _offscreen = 0; +} + +void FlicPlayer::redraw() { + _dirtyRects.clear(); + _dirtyRects.push_back(Common::Rect(0, 0, _flicInfo.width, _flicInfo.height)); +} + +ChunkHeader FlicPlayer::readChunkHeader() { + ChunkHeader head; + + head.size = _fileStream.readUint32LE(); + head.type = _fileStream.readUint16LE(); + + return head; +} + +FrameTypeChunkHeader FlicPlayer::readFrameTypeChunkHeader(ChunkHeader chunkHead) { + FrameTypeChunkHeader head; + + head.header = chunkHead; + head.numChunks = _fileStream.readUint16LE(); + head.delay = _fileStream.readUint16LE(); + head.reserved = _fileStream.readUint16LE(); + head.widthOverride = _fileStream.readUint16LE(); + head.heightOverride = _fileStream.readUint16LE(); + + return head; +} + +void FlicPlayer::decodeByteRun(uint8 *data) { + uint8 *ptr = (uint8 *)_offscreen; + while ((ptr - _offscreen) < (_flicInfo.width * _flicInfo.height)) { + int chunks = *data++; + while (chunks--) { + int count = (int8)*data++; + if (count > 0) { + memset(ptr, *data++, count); + } else { + count = -count; + memcpy(ptr, data, count); + data += count; + } + ptr += count; + } + } + + redraw(); +} + +#define OP_PACKETCOUNT 0 +#define OP_UNDEFINED 1 +#define OP_LASTPIXEL 2 +#define OP_LINESKIPCOUNT 3 + +void FlicPlayer::decodeDeltaFLC(uint8 *data) { + uint16 linesInChunk = READ_LE_UINT16(data); data += 2; + uint16 currentLine = 0; + uint16 packetCount = 0; + + while (linesInChunk--) { + uint16 opcode; + + // First process all the opcodes. + do { + opcode = READ_LE_UINT16(data); data += 2; + + switch ((opcode >> 14) & 3) { + case OP_PACKETCOUNT: + packetCount = opcode; + break; + case OP_UNDEFINED: + break; + case OP_LASTPIXEL: + _offscreen[currentLine * _flicInfo.width + _flicInfo.width - 1] = (opcode & 0xFF); + _dirtyRects.push_back(Common::Rect(_flicInfo.width - 1, currentLine, _flicInfo.width, currentLine + 1)); + break; + case OP_LINESKIPCOUNT: + currentLine += -(int16)opcode; + break; + } + } while (((opcode >> 14) & 3) != OP_PACKETCOUNT); + + uint16 column = 0; + + // Now interpret the RLE data + while (packetCount--) { + column += *data++; + int rleCount = (int8)*data++; + if (rleCount > 0) { + memcpy(_offscreen + (currentLine * _flicInfo.width) + column, data, rleCount * 2); + data += rleCount * 2; + _dirtyRects.push_back(Common::Rect(column, currentLine, column + rleCount * 2, currentLine + 1)); + } else if (rleCount < 0) { + rleCount = -rleCount; + uint16 dataWord = READ_UINT16(data); data += 2; + for (int i = 0; i < rleCount; ++i) { + WRITE_UINT16(_offscreen + currentLine * _flicInfo.width + column + i * 2, dataWord); + } + _dirtyRects.push_back(Common::Rect(column, currentLine, column + rleCount * 2, currentLine + 1)); + } else { // End of cutscene ? + return; + } + column += rleCount * 2; + } + + currentLine++; + } +} + +#define COLOR_256 4 +#define FLI_SS2 7 +#define FLI_BRUN 15 +#define PSTAMP 18 +#define FRAME_TYPE 0xF1FA + +void FlicPlayer::decodeFrame() { + FrameTypeChunkHeader frameHeader; + + // Read chunk + ChunkHeader cHeader = readChunkHeader(); + switch (cHeader.type) { + case FRAME_TYPE: + frameHeader = readFrameTypeChunkHeader(cHeader); + _currFrame++; + break; + default: + error("FlicPlayer::decodeFrame(): unknown main chunk type (type = 0x%02X)", cHeader.type); + break; + } + + // Read subchunks + if (cHeader.type == FRAME_TYPE) { + for (int i = 0; i < frameHeader.numChunks; ++i) { + cHeader = readChunkHeader(); + uint8 *data = new uint8[cHeader.size - 6]; + _fileStream.read(data, cHeader.size - 6); + switch (cHeader.type) { + case COLOR_256: + setPalette(data); + _paletteDirty = true; + break; + case FLI_SS2: + decodeDeltaFLC(data); + break; + case FLI_BRUN: + decodeByteRun(data); + break; + case PSTAMP: + /* PSTAMP - skip for now */ + break; + default: + error("FlicPlayer::decodeFrame(): unknown subchunk type (type = 0x%02X)", cHeader.type); + break; + } + + delete[] data; + } + } + + // If we just processed the ring frame, set the next frame + if (_currFrame == _flicInfo.numFrames + 1) { + _currFrame = 1; + _fileStream.seek(_flicInfo.offsetFrame2); + } +} + +void FlicPlayer::reset() { + _currFrame = 0; + _fileStream.seek(_flicInfo.offsetFrame1); +} + +void FlicPlayer::setPalette(uint8 *mem) { + uint16 numPackets = READ_LE_UINT16(mem); mem += 2; + + if (0 == READ_LE_UINT16(mem)) { //special case + mem += 2; + for (int i = 0; i < 256; ++i) { + memcpy(_palette + i * 4, mem + i * 3, 3); + _palette[i * 4 + 3] = 0; + } + } else { + uint8 palPos = 0; + + while (numPackets--) { + palPos += *mem++; + uint8 change = *mem++; + + for (int i = 0; i < change; ++i) { + memcpy(_palette + (palPos + i) * 4, mem + i * 3, 3); + _palette[(palPos + i) * 4 + 3] = 0; + } + + palPos += change; + mem += (change * 3); + } + } +} + +} // End of namespace Graphics diff --git a/graphics/video/flic_player.h b/graphics/video/flic_player.h new file mode 100644 index 0000000000..ef2b6d67ba --- /dev/null +++ b/graphics/video/flic_player.h @@ -0,0 +1,102 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef GRAPHICS_VIDEO_FLICPLAYER_H +#define GRAPHICS_VIDEO_FLICPLAYER_H + +#include "common/endian.h" +#include "common/list.h" +#include "common/rect.h" +#include "common/file.h" + +namespace Graphics { + +struct FlicHeader { + uint32 size; + uint16 type; + uint16 numFrames; + uint16 width; + uint16 height; + uint32 speed; + uint16 offsetFrame1; + uint16 offsetFrame2; +}; + +struct ChunkHeader { + uint32 size; + uint16 type; +}; + +struct FrameTypeChunkHeader { + ChunkHeader header; + uint16 numChunks; + uint16 delay; + uint16 reserved; // always zero + uint16 widthOverride; + uint16 heightOverride; +}; + +class FlicPlayer { +public: + FlicPlayer(); + ~FlicPlayer(); + + bool loadFile(const char *fileName); + void closeFile(); + void decodeFrame(); + int getWidth() const { return _flicInfo.width; } + int getHeight() const { return _flicInfo.height; } + bool hasFrames() const { return _flicInfo.numFrames > 0; } + int getCurFrame() const { return _currFrame; } + int getFrameCount() const { return _flicInfo.numFrames; } + bool isLastFrame() const { return _currFrame == _flicInfo.numFrames; } + uint32 getSpeed() const { return _flicInfo.speed; } + bool isPaletteDirty() const { return _paletteDirty; } + const uint8 *getPalette() { _paletteDirty = false; return _palette; } + const uint8 *getOffscreen() const { return _offscreen; } + const Common::List<Common::Rect> *getDirtyRects() const { return &_dirtyRects; } + void clearDirtyRects() { _dirtyRects.clear(); } + void redraw(); + void reset(); + +protected: + ChunkHeader readChunkHeader(); + FrameTypeChunkHeader readFrameTypeChunkHeader(ChunkHeader chunkHead); + void decodeByteRun(uint8 *data); + void decodeDeltaFLC(uint8 *data); + void setPalette(uint8 *mem); + + Common::File _fileStream; + bool _paletteDirty; + uint8 *_offscreen; + uint8 _palette[256 * 4]; + FlicHeader _flicInfo; + uint16 _currFrame; + Common::List<Common::Rect> _dirtyRects; +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/video/mpeg_player.cpp b/graphics/video/mpeg_player.cpp new file mode 100644 index 0000000000..964f4cd94d --- /dev/null +++ b/graphics/video/mpeg_player.cpp @@ -0,0 +1,629 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "graphics/video/mpeg_player.h" +#include "common/file.h" +#include "common/system.h" +#include "common/util.h" +#include "graphics/scaler/intern.h" + +namespace Graphics { + +BaseAnimationState::BaseAnimationState(OSystem *sys, int width, int height) + : _movieWidth(width), _movieHeight(height), _frameWidth(width), _frameHeight(height), _sys(sys) { +#ifndef BACKEND_8BIT + const int screenW = _sys->getOverlayWidth(); + const int screenH = _sys->getOverlayHeight(); + + _movieScale = MIN(screenW / _movieWidth, screenH / _movieHeight); + + assert(_movieScale >= 1); + if (_movieScale > 3) + _movieScale = 3; + + _colorTab = NULL; + _rgbToPix = NULL; + _bitFormat = 0; +#endif +} + +BaseAnimationState::~BaseAnimationState() { +#ifdef USE_MPEG2 + if (_mpegDecoder) + mpeg2_close(_mpegDecoder); + delete _mpegFile; +#ifndef BACKEND_8BIT + _sys->hideOverlay(); + free(_overlay); + free(_colorTab); + free(_rgbToPix); +#endif +#endif +} + + +bool BaseAnimationState::init(const char *name) { +#ifdef USE_MPEG2 + char tempFile[512]; + + _mpegDecoder = NULL; + _mpegFile = NULL; + +#ifdef BACKEND_8BIT + + uint i, p; + + // Load lookup palettes + sprintf(tempFile, "%s.pal", name); + + Common::File f; + + if (!f.open(tempFile)) { + warning("Cutscene: %s palette missing", tempFile); + return false; + } + + p = 0; + while (1) { + _palettes[p].end = f.readUint16LE(); + _palettes[p].cnt = f.readUint16LE(); + + if (f.ioFailed()) + break; + + for (i = 0; i < _palettes[p].cnt; i++) { + _palettes[p].pal[4 * i] = f.readByte(); + _palettes[p].pal[4 * i + 1] = f.readByte(); + _palettes[p].pal[4 * i + 2] = f.readByte(); + _palettes[p].pal[4 * i + 3] = 0; + } + for (; i < 256; i++) { + _palettes[p].pal[4 * i] = 0; + _palettes[p].pal[4 * i + 1] = 0; + _palettes[p].pal[4 * i + 2] = 0; + _palettes[p].pal[4 * i + 3] = 0; + } + + p++; + } + + f.close(); + + _palNum = 0; + _maxPalNum = p; + setPalette(_palettes[_palNum].pal); + _lut = _lut2 = _yuvLookup[0]; + _curPal = -1; + _cr = 0; + buildLookup(_palNum, 256); + _lut2 = _yuvLookup[1]; + _lutCalcNum = (BITDEPTH + _palettes[_palNum].end + 2) / (_palettes[_palNum].end + 2); +#else + buildLookup(); + _overlay = (OverlayColor *)calloc(_movieScale * _movieWidth * _movieScale * _movieHeight, sizeof(OverlayColor)); + _sys->showOverlay(); +#endif + + // Open MPEG2 stream + _mpegFile = new Common::File(); + sprintf(tempFile, "%s.mp2", name); + if (!_mpegFile->open(tempFile)) { + warning("Cutscene: Could not open %s", tempFile); + return false; + } + + // Load and configure decoder + _mpegDecoder = mpeg2_init(); + if (_mpegDecoder == NULL) { + warning("Cutscene: Could not allocate an MPEG2 decoder"); + return false; + } + + _mpegInfo = mpeg2_info(_mpegDecoder); + _frameNum = 0; + + return true; +#else /* USE_MPEG2 */ + return false; +#endif +} + +bool BaseAnimationState::decodeFrame() { +#ifdef USE_MPEG2 + mpeg2_state_t state; + const mpeg2_sequence_t *sequence_i; + size_t size = (size_t) -1; + static byte buf[BUFFER_SIZE]; + + do { + state = mpeg2_parse(_mpegDecoder); + sequence_i = _mpegInfo->sequence; + + switch (state) { + case STATE_BUFFER: + size = _mpegFile->read(buf, BUFFER_SIZE); + mpeg2_buffer(_mpegDecoder, buf, buf + size); + break; + + case STATE_SLICE: + case STATE_END: + if (_mpegInfo->display_fbuf) { + checkPaletteSwitch(); + drawYUV(sequence_i->width, sequence_i->height, _mpegInfo->display_fbuf->buf); +#ifdef BACKEND_8BIT + buildLookup(_palNum + 1, _lutCalcNum); +#endif + + _frameNum++; + return true; + } + break; + + default: + break; + } + } while (size); +#endif + return false; +} + +bool BaseAnimationState::checkPaletteSwitch() { +#ifdef BACKEND_8BIT + // if we have reached the last image with this palette, switch to new one + if (_frameNum == _palettes[_palNum].end) { + unsigned char *l = _lut2; + _palNum++; + setPalette(_palettes[_palNum].pal); + _lutCalcNum = (BITDEPTH + _palettes[_palNum].end - (_frameNum + 1) + 2) / (_palettes[_palNum].end - (_frameNum + 1) + 2); + _lut2 = _lut; + _lut = l; + return true; + } +#endif + + return false; +} + +void BaseAnimationState::handleScreenChanged() { +#ifndef BACKEND_8BIT + const int screenW = _sys->getOverlayWidth(); + const int screenH = _sys->getOverlayHeight(); + + int newScale = MIN(screenW / _movieWidth, screenH / _movieHeight); + + assert(newScale >= 1); + if (newScale > 3) + newScale = 3; + + if (newScale != _movieScale) { + // HACK: Since frames generally do not cover the entire screen, + // We need to undraw the old frame. This is a very hacky + // way of doing that. + OverlayColor *buf = (OverlayColor *)calloc(screenW * screenH, sizeof(OverlayColor)); + _sys->copyRectToOverlay(buf, screenW, 0, 0, screenW, screenH); + free(buf); + + free(_overlay); + _movieScale = newScale; + _overlay = (OverlayColor *)calloc(_movieScale * _movieWidth * _movieScale * _movieHeight, sizeof(OverlayColor)); + } + + buildLookup(); +#endif +} + +#ifdef BACKEND_8BIT + +/** + * Build 'Best-Match' RGB lookup table + */ +void BaseAnimationState::buildLookup(int p, int lines) { + int y, cb; + int r, g, b, ii; + + if (p >= _maxPalNum) + return; + + if (p != _curPal) { + _curPal = p; + _cr = 0; + _pos = 0; + } + + if (_cr > BITDEPTH) + return; + + for (ii = 0; ii < lines; ii++) { + r = (-16 * 256 + (int) (256 * 1.596) * ((_cr << SHIFT) - 128)) / 256; + for (cb = 0; cb <= BITDEPTH; cb++) { + g = (-16 * 256 - (int) (0.813 * 256) * ((_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 - _palettes[p].pal[0]) + 4 * SQR(g - _palettes[p].pal[1]) + SQR(b - _palettes[p].pal[2]); + + for (idx = 1; idx < 256; idx++) { + long d2 = 2 * SQR(r - _palettes[p].pal[4 * idx]) + 4 * SQR(g - _palettes[p].pal[4 * idx + 1]) + SQR(b - _palettes[p].pal[4 * idx + 2]); + if (d2 < dis) { + bst = idx; + dis = d2; + } + } + _lut2[_pos++] = bst; + + r += (1 << SHIFT); + g += (1 << SHIFT); + b += (1 << SHIFT); + } + r -= (BITDEPTH + 1) * (1 << SHIFT); + } + _cr++; + if (_cr > BITDEPTH) + return; + } +} + +#else + +// The YUV to RGB conversion code is derived from SDL's YUV overlay code, which +// in turn appears to be derived from mpeg_play. The following copyright +// notices have been included in accordance with the original license. Please +// note that the term "software" in this context only applies to the two +// functions buildLookup() and plotYUV() below. + +// Copyright (c) 1995 The Regents of the University of California. +// All rights reserved. +// +// Permission to use, copy, modify, and distribute this software and its +// documentation for any purpose, without fee, and without written agreement is +// hereby granted, provided that the above copyright notice and the following +// two paragraphs appear in all copies of this software. +// +// IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR +// DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT +// OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF +// CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +// ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO +// PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + +// Copyright (c) 1995 Erik Corry +// All rights reserved. +// +// Permission to use, copy, modify, and distribute this software and its +// documentation for any purpose, without fee, and without written agreement is +// hereby granted, provided that the above copyright notice and the following +// two paragraphs appear in all copies of this software. +// +// IN NO EVENT SHALL ERIK CORRY BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF +// THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF ERIK CORRY HAS BEEN ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ERIK CORRY SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +// PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" +// BASIS, AND ERIK CORRY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, +// UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + +// Portions of this software Copyright (c) 1995 Brown University. +// All rights reserved. +// +// Permission to use, copy, modify, and distribute this software and its +// documentation for any purpose, without fee, and without written agreement +// is hereby granted, provided that the above copyright notice and the +// following two paragraphs appear in all copies of this software. +// +// IN NO EVENT SHALL BROWN UNIVERSITY BE LIABLE TO ANY PARTY FOR +// DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT +// OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF BROWN +// UNIVERSITY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// BROWN UNIVERSITY SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +// PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" +// BASIS, AND BROWN UNIVERSITY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, +// SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + +void BaseAnimationState::buildLookup() { + // Do we already have lookup tables for this bit format? + if (gBitFormat == _bitFormat && _colorTab && _rgbToPix) + return; + + free(_colorTab); + free(_rgbToPix); + + _colorTab = (int16 *)malloc(4 * 256 * sizeof(int16)); + + int16 *Cr_r_tab = &_colorTab[0 * 256]; + int16 *Cr_g_tab = &_colorTab[1 * 256]; + int16 *Cb_g_tab = &_colorTab[2 * 256]; + int16 *Cb_b_tab = &_colorTab[3 * 256]; + + _rgbToPix = (OverlayColor *)malloc(3 * 768 * sizeof(OverlayColor)); + + OverlayColor *r_2_pix_alloc = &_rgbToPix[0 * 768]; + OverlayColor *g_2_pix_alloc = &_rgbToPix[1 * 768]; + OverlayColor *b_2_pix_alloc = &_rgbToPix[2 * 768]; + + int16 CR, CB; + int i; + + // Generate the tables for the display surface + + for (i = 0; i < 256; i++) { + // Gamma correction (luminescence table) and chroma correction + // would be done here. See the Berkeley mpeg_play sources. + + CR = CB = (i - 128); + Cr_r_tab[i] = (int16) ( (0.419 / 0.299) * CR) + 0 * 768 + 256; + Cr_g_tab[i] = (int16) (-(0.299 / 0.419) * CR) + 1 * 768 + 256; + Cb_g_tab[i] = (int16) (-(0.114 / 0.331) * CB); + Cb_b_tab[i] = (int16) ( (0.587 / 0.331) * CB) + 2 * 768 + 256; + } + + // Set up entries 0-255 in rgb-to-pixel value tables. + Graphics::PixelFormat format = _sys->getOverlayFormat(); + for (i = 0; i < 256; i++) { + r_2_pix_alloc[i + 256] = Graphics::RGBToColor(i, 0, 0, format); + g_2_pix_alloc[i + 256] = Graphics::RGBToColor(0, i, 0, format); + b_2_pix_alloc[i + 256] = Graphics::RGBToColor(0, 0, i, format); + } + + // Spread out the values we have to the rest of the array so that we do + // not need to check for overflow. + for (i = 0; i < 256; i++) { + r_2_pix_alloc[i] = r_2_pix_alloc[256]; + r_2_pix_alloc[i + 512] = r_2_pix_alloc[511]; + g_2_pix_alloc[i] = g_2_pix_alloc[256]; + g_2_pix_alloc[i + 512] = g_2_pix_alloc[511]; + b_2_pix_alloc[i] = b_2_pix_alloc[256]; + b_2_pix_alloc[i + 512] = b_2_pix_alloc[511]; + } + + _bitFormat = gBitFormat; +} + +void BaseAnimationState::plotYUV(int width, int height, byte *const *dat) { + switch (_movieScale) { + case 1: + plotYUV1x(width, height, dat); + break; + case 2: + plotYUV2x(width, height, dat); + break; + case 3: + plotYUV3x(width, height, dat); + break; + } +} + +void BaseAnimationState::plotYUV1x(int width, int height, byte *const *dat) { + byte *lum = dat[0]; + byte *cr = dat[2]; + byte *cb = dat[1]; + + byte *lum2 = lum + width; + + int16 cr_r; + int16 crb_g; + int16 cb_b; + + OverlayColor *row1 = _overlay; + OverlayColor *row2 = row1 + _movieWidth; + + int x; + + for (; height > 0; height -= 2) { + OverlayColor *r1 = row1; + OverlayColor *r2 = row2; + + for (x = width; x > 0; x -= 2) { + register OverlayColor *L; + + cr_r = _colorTab[*cr + 0 * 256]; + crb_g = _colorTab[*cr + 1 * 256] + _colorTab[*cb + 2 * 256]; + cb_b = _colorTab[*cb + 3 * 256]; + ++cr; + ++cb; + + L = &_rgbToPix[*lum++]; + *r1++ = L[cr_r] | L[crb_g] | L[cb_b]; + + L = &_rgbToPix[*lum++]; + *r1++ = L[cr_r] | L[crb_g] | L[cb_b]; + + // Now, do second row. + + L = &_rgbToPix[*lum2++]; + *r2++ = L[cr_r] | L[crb_g] | L[cb_b]; + + L = &_rgbToPix[*lum2++]; + *r2++ = L[cr_r] | L[crb_g] | L[cb_b]; + } + + lum += width; + lum2 += width; + row1 += 2 * _movieWidth; + row2 += 2 * _movieWidth; + } +} + +void BaseAnimationState::plotYUV2x(int width, int height, byte *const *dat) { + byte *lum = dat[0]; + byte *cr = dat[2]; + byte *cb = dat[1]; + + byte *lum2 = lum + width; + + int16 cr_r; + int16 crb_g; + int16 cb_b; + + OverlayColor *row1 = _overlay; + OverlayColor *row2 = row1 + 2 * 2 * _movieWidth; + + int x; + + for (; height > 0; height -= 2) { + OverlayColor *r1 = row1; + OverlayColor *r2 = row2; + + for (x = width; x > 0; x -= 2) { + register OverlayColor *L; + register OverlayColor C; + + cr_r = _colorTab[*cr + 0 * 256]; + crb_g = _colorTab[*cr + 1 * 256] + _colorTab[*cb + 2 * 256]; + cb_b = _colorTab[*cb + 3 * 256]; + ++cr; + ++cb; + + L = &_rgbToPix[*lum++]; + C = L[cr_r] | L[crb_g] | L[cb_b]; + *r1++ = C; + *r1++ = C; + + L = &_rgbToPix[*lum++]; + C = L[cr_r] | L[crb_g] | L[cb_b]; + *r1++ = C; + *r1++ = C; + + // Now, do second row. + + L = &_rgbToPix[*lum2++]; + C = L[cr_r] | L[crb_g] | L[cb_b]; + *r2++ = C; + *r2++ = C; + + L = &_rgbToPix[*lum2++]; + C = L[cr_r] | L[crb_g] | L[cb_b]; + *r2++ = C; + *r2++ = C; + } + + memcpy(row1 + 2 * _movieWidth, row1, 2 * _movieWidth * sizeof(OverlayColor)); + memcpy(row2 + 2 * _movieWidth, row2, 2 * _movieWidth * sizeof(OverlayColor)); + + lum += width; + lum2 += width; + row1 += 4 * 2 * _movieWidth; + row2 += 4 * 2 * _movieWidth; + } +} + +void BaseAnimationState::plotYUV3x(int width, int height, byte *const *dat) { + byte *lum = dat[0]; + byte *cr = dat[2]; + byte *cb = dat[1]; + + byte *lum2 = lum + width; + + int16 cr_r; + int16 crb_g; + int16 cb_b; + + OverlayColor *row1 = _overlay; + OverlayColor *row2 = row1 + 3 * 3 * _movieWidth; + + int x; + + for (; height > 0; height -= 2) { + OverlayColor *r1 = row1; + OverlayColor *r2 = row2; + + for (x = width; x > 0; x -= 2) { + register OverlayColor *L; + register OverlayColor C; + + cr_r = _colorTab[*cr + 0 * 256]; + crb_g = _colorTab[*cr + 1 * 256] + _colorTab[*cb + 2 * 256]; + cb_b = _colorTab[*cb + 3 * 256]; + ++cr; + ++cb; + + L = &_rgbToPix[*lum++]; + C = L[cr_r] | L[crb_g] | L[cb_b]; + *r1++ = C; + *r1++ = C; + *r1++ = C; + + L = &_rgbToPix[*lum++]; + C = L[cr_r] | L[crb_g] | L[cb_b]; + *r1++ = C; + *r1++ = C; + *r1++ = C; + + // Now, do second row. + + L = &_rgbToPix[*lum2++]; + C = L[cr_r] | L[crb_g] | L[cb_b]; + *r2++ = C; + *r2++ = C; + *r2++ = C; + + L = &_rgbToPix[*lum2++]; + C = L[cr_r] | L[crb_g] | L[cb_b]; + *r2++ = C; + *r2++ = C; + *r2++ = C; + } + + memcpy(row1 + 3 * _movieWidth, row1, 3 * _movieWidth * sizeof(OverlayColor)); + memcpy(row1 + 2 * 3 * _movieWidth, row1, 3 * _movieWidth * sizeof(OverlayColor)); + memcpy(row2 + 3 * _movieWidth, row2, 3 * _movieWidth * sizeof(OverlayColor)); + memcpy(row2 + 2 * 3 * _movieWidth, row2, 3 * _movieWidth * sizeof(OverlayColor)); + + lum += width; + lum2 += width; + row1 += 6 * 3 * _movieWidth; + row2 += 6 * 3 * _movieWidth; + } +} + +#endif + +void BaseAnimationState::updateScreen() { +#ifndef BACKEND_8BIT + int width = _movieScale * _frameWidth; + int height = _movieScale * _frameHeight; + int pitch = _movieScale * _movieWidth; + + const int screenW = _sys->getOverlayWidth(); + const int screenH = _sys->getOverlayHeight(); + + int x = (screenW - _movieScale * _frameWidth) / 2; + int y = (screenH - _movieScale * _frameHeight) / 2; + + _sys->copyRectToOverlay(_overlay, pitch, x, y, width, height); +#endif + _sys->updateScreen(); +} + +} // End of namespace Graphics diff --git a/graphics/video/mpeg_player.h b/graphics/video/mpeg_player.h new file mode 100644 index 0000000000..7d42618926 --- /dev/null +++ b/graphics/video/mpeg_player.h @@ -0,0 +1,163 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef GRAPHICS_VIDEO_MPEG_PLAYER_H +#define GRAPHICS_VIDEO_MPEG_PLAYER_H + +#include "common/scummsys.h" + +// Uncomment this if you are using libmpeg2 0.3.1. +// #define USE_MPEG2_0_3_1 + +#ifdef USE_MPEG2 + +#ifdef __PLAYSTATION2__ +typedef uint8 uint8_t; +typedef uint16 uint16_t; +typedef uint32 uint32_t; +#endif + +#if !defined(_MSC_VER) +#ifndef PALMOS_MODE +# include <inttypes.h> +#else +# include <stdint.h> +#endif +#endif + +extern "C" { + #include <mpeg2dec/mpeg2.h> +} + +#ifdef USE_MPEG2_0_3_1 +typedef int mpeg2_state_t; +typedef sequence_t mpeg2_sequence_t; +#define STATE_BUFFER -1 +#endif + +#endif + +#ifdef BACKEND_8BIT +#define SQR(x) ((x) * (x)) +#define SHIFT 3 +#else +#define SHIFT 1 +#endif + +#define BITDEPTH (1 << (8 - SHIFT)) +#define ROUNDADD (1 << (SHIFT - 1)) + +#define BUFFER_SIZE 4096 + +namespace Common { + class File; +} + +class OSystem; + +namespace Graphics { + +class BaseAnimationState { +protected: + const int _movieWidth; + const int _movieHeight; + + int _frameWidth; + int _frameHeight; + +#ifndef BACKEND_8BIT + int _movieScale; +#endif + + OSystem *_sys; + + uint _frameNum; + +#ifdef USE_MPEG2 + mpeg2dec_t *_mpegDecoder; + const mpeg2_info_t *_mpegInfo; +#endif + + Common::File *_mpegFile; + +#ifdef BACKEND_8BIT + int _palNum; + int _maxPalNum; + + byte _yuvLookup[2][(BITDEPTH+1) * (BITDEPTH+1) * (BITDEPTH+1)]; + byte *_lut; + byte *_lut2; + int _lutCalcNum; + + int _curPal; + int _cr; + int _pos; + + struct { + uint cnt; + uint end; + byte pal[4 * 256]; + } _palettes[50]; +#else + OverlayColor *_overlay; + int _bitFormat; + int16 *_colorTab; + OverlayColor *_rgbToPix; +#endif + +public: + BaseAnimationState(OSystem *sys, int width, int height); + virtual ~BaseAnimationState(); + + bool init(const char *name); + bool decodeFrame(); + void handleScreenChanged(); + void updateScreen(); + +#ifndef BACKEND_8BIT + void buildLookup(); +#endif + + int getFrameWidth() { return _frameWidth; } + int getFrameHeight() { return _frameHeight; } + +protected: + bool checkPaletteSwitch(); + virtual void drawYUV(int width, int height, byte *const *dat) = 0; + +#ifdef BACKEND_8BIT + void buildLookup(int p, int lines); + virtual void setPalette(byte *pal) = 0; +#else + void plotYUV(int width, int height, byte *const *dat); + void plotYUV1x(int width, int height, byte *const *dat); + void plotYUV2x(int width, int height, byte *const *dat); + void plotYUV3x(int width, int height, byte *const *dat); +#endif +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/video/smk_player.cpp b/graphics/video/smk_player.cpp new file mode 100644 index 0000000000..458a8c0d12 --- /dev/null +++ b/graphics/video/smk_player.cpp @@ -0,0 +1,887 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// Based on http://wiki.multimedia.cx/index.php?title=Smacker +// and the FFmpeg Smacker decoder (libavcodec/smacker.c), revision 16143 +// http://svn.ffmpeg.org/ffmpeg/trunk/libavcodec/smacker.c?revision=16143&view=markup + +#include "graphics/video/smk_player.h" +#include "common/archive.h" +#include "common/system.h" +#include "common/util.h" +#include "common/array.h" +#include "common/endian.h" +#include "sound/mixer.h" +#include "sound/audiostream.h" + +namespace Graphics { + +enum SmkBlockTypes { + SMK_BLOCK_MONO = 0, + SMK_BLOCK_FULL = 1, + SMK_BLOCK_SKIP = 2, + SMK_BLOCK_FILL = 3 +}; + +/* + * class BitStream + * Keeps a two-byte lookahead, so overallocate buf by 2 bytes if + * you want to avoid OOB reads. + */ + +class BitStream { +public: + BitStream(byte *buf, uint32 length) + : _buf(buf), _end(buf+length), _curBit(8) { + _curBytes = *_buf++; + _curBytes |= *_buf++ << 8; + } + + bool getBit(); + byte getBits8(); + + byte peek8() const; + void skip(int n); + +private: + byte *_buf; + byte *_end; + uint16 _curBytes; + byte _curBit; +}; + +bool BitStream::getBit() { + bool v = _curBytes & 1; + + _curBytes >>= 1; + + if (--_curBit == 0) { + _curBytes |= *_buf++ << 8; + _curBit = 8; + } + + return v; +} + +byte BitStream::getBits8() { + byte v = _curBytes & 0xff; + _curBytes >>= 8; + _curBytes |= *_buf++ << _curBit; + return v; +} + +byte BitStream::peek8() const { + return _curBytes & 0xff; +} + +void BitStream::skip(int n) { + assert(n <= 8); + _curBytes >>= n; + + if (_curBit > n) { + _curBit -= n; + } else { + _curBit = _curBit + 8 - n; + _curBytes |= *_buf++ << _curBit; + } +} + +/* + * class SmallHuffmanTree + * A Huffman-tree to hold 8-bit values. + * Unoptimized since it's only used during smk initialization. + */ + +class SmallHuffmanTree { +public: + SmallHuffmanTree(BitStream &bs) : _bs(bs) { + uint32 bit = _bs.getBit(); + assert(bit); + + decodeTree(0); + + bit = _bs.getBit(); + assert(!bit); + } + + uint16 getCode(BitStream &bs); +private: + enum { + SMK_NODE = 0x8000 + }; + + int decodeTree(int length); + + Common::Array<uint16> _tree; + BitStream &_bs; +}; + +int SmallHuffmanTree::decodeTree(int length) { + if (!_bs.getBit()) { // Leaf + uint16 v = _bs.getBits8(); + + _tree.push_back(v); + return 1; + } + + _tree.push_back(0); // placeholder for r1 + int t = _tree.size() - 1; + + int r1 = decodeTree(length + 1); + + _tree[t] = (SMK_NODE | r1); + + int r2 = decodeTree(length + 1); + + return r1+r2+1; +} + +uint16 SmallHuffmanTree::getCode(BitStream &bs) { + uint16 *p = &_tree[0]; + + while (*p & SMK_NODE) { + if (bs.getBit()) + p += *p & ~SMK_NODE; + p++; + } + + return *p; +} + +/* + * class BigHuffmanTree + * A Huffman-tree to hold 16-bit values. + * Contains the beginnings of an optimization. + */ + +class BigHuffmanTree { +public: + BigHuffmanTree(BitStream &bs); + + void reset(); + uint32 getCode(BitStream &bs); +private: + enum { + SMK_NODE = 0x80000000 + }; + + int decodeTree(uint32 prefix, int length); + + Common::Array<uint32> _tree; + uint32 _last[3]; + + int _prefixtree[256]; + int _prefixlength[256]; + + /* Used during construction */ + BitStream &_bs; + uint32 _markers[3]; + SmallHuffmanTree *_loBytes; + SmallHuffmanTree *_hiBytes; +}; + +BigHuffmanTree::BigHuffmanTree(BitStream &bs) + : _bs(bs) { + uint32 bit = _bs.getBit(); + if (!bit) { + _tree.push_back(0); + _last[0] = _last[1] = _last[2] = 0; + return; + } + + int i; + for (i = 0; i < 256; ++i) + _prefixtree[i] = 0; + + _loBytes = new SmallHuffmanTree(_bs); + _hiBytes = new SmallHuffmanTree(_bs); + + _markers[0] = _bs.getBits8() | (_bs.getBits8() << 8); + _markers[1] = _bs.getBits8() | (_bs.getBits8() << 8); + _markers[2] = _bs.getBits8() | (_bs.getBits8() << 8); + + _last[0] = _last[1] = _last[2] = 0xffffffff; + + decodeTree(0, 0); + bit = _bs.getBit(); + assert(!bit); + + for (i = 0; i < 3; ++i) { + if (_last[i] == 0xffffffff) { + _tree.push_back(0); + _last[i] = _tree.size() - 1; + } + } + + delete _loBytes; + delete _hiBytes; +} + +void BigHuffmanTree::reset() { + _tree[_last[0]] = _tree[_last[1]] = _tree[_last[2]] = 0; +} + +int BigHuffmanTree::decodeTree(uint32 prefix, int length) { + uint32 bit = _bs.getBit(); + + if (!bit) { // Leaf + uint32 lo = _loBytes->getCode(_bs); + uint32 hi = _hiBytes->getCode(_bs); + + uint32 v = (hi << 8) | lo; + _tree.push_back(v); + + int t = _tree.size() - 1; + + if (length <= 8) { + uint32 i; + for (i = 0; i < 256; i += (1 << length)) { + _prefixtree[prefix | i] = t; + _prefixlength[prefix | i] = length; + } + } + + int i; + for (i = 0; i < 3; ++i) { + if (_markers[i] == v) { + _last[i] = t; + _tree[t] = 0; + } + } + + return 1; + } + + _tree.push_back(0); // placeholder for r1 + int t = _tree.size() - 1; + + if (length == 8) { + _prefixtree[prefix] = t; + _prefixlength[prefix] = 8; + } + + int r1 = decodeTree(prefix, length + 1); + + _tree[t] = SMK_NODE | r1; + + int r2 = decodeTree(prefix | (1 << length), length + 1); + return r1+r2+1; +} + +uint32 BigHuffmanTree::getCode(BitStream &bs) { + uint32 *p = &_tree[0]; + + byte peek = bs.peek8(); + p = &_tree[_prefixtree[peek]]; + bs.skip(_prefixlength[peek]); + + while (*p & SMK_NODE) { + if (bs.getBit()) + p += (*p) & ~SMK_NODE; + p++; + } + + uint32 v = *p; + if (v != _tree[_last[0]]) { + _tree[_last[2]] = _tree[_last[1]]; + _tree[_last[1]] = _tree[_last[0]]; + _tree[_last[0]] = v; + } + + return v; +} + +SMKPlayer::SMKPlayer(Audio::Mixer *mixer) + : _currentSMKFrame(0), _fileStream(0), _audioStarted(false), _audioStream(0), _mixer(mixer) { +} + +SMKPlayer::~SMKPlayer() { + closeFile(); +} + +int SMKPlayer::getWidth() { + if (!_fileStream) + return 0; + return _header.width; +} + +int SMKPlayer::getHeight() { + if (!_fileStream) + return 0; + return (_header.flags ? 2 : 1) * _header.height; +} + +int32 SMKPlayer::getCurFrame() { + if (!_fileStream) + return -1; + return _currentSMKFrame; +} + +int32 SMKPlayer::getFrameCount() { + if (!_fileStream) + return 0; + return _header.frames; +} + +int32 SMKPlayer::getFrameRate() { + if (!_fileStream) + return 0; + + if (_header.frameRate > 0) + return 1000 / _header.frameRate; + else if (_header.frameRate < 0) + return 100000 / (-_header.frameRate); + else + return 10; +} + +int32 SMKPlayer::getFrameDelay() { + if (!_fileStream) + return 0; + + if (_header.frameRate > 0) + return _header.frameRate * 100; + if (_header.frameRate < 0) + return -_header.frameRate; + + return 10000; +} + +int32 SMKPlayer::getAudioLag() { + if (!_fileStream || !_audioStream) + return 0; + + int32 frameDelay = getFrameDelay(); + int32 videoTime = _currentSMKFrame * frameDelay; + int32 audioTime = (((int32) _mixer->getSoundElapsedTime(_audioHandle)) * 100); + + return videoTime - audioTime; +} + +uint32 SMKPlayer::getFrameWaitTime() { + int32 waitTime = (getFrameDelay() + getAudioLag()) / 100; + + if (waitTime < 0) + return 0; + + return waitTime; +} + +bool SMKPlayer::loadFile(const char *fileName) { + closeFile(); + + _fileStream = SearchMan.openFile(fileName); + if (!_fileStream) + return false; + + // Seek to the first frame + _currentSMKFrame = 0; + _header.signature = _fileStream->readUint32BE(); + + // No BINK support available + if (_header.signature == MKID_BE('BIKi')) { + delete _fileStream; + _fileStream = 0; + return false; + } + + assert(_header.signature == MKID_BE('SMK2') || _header.signature == MKID_BE('SMK4')); + + _header.width = _fileStream->readUint32LE(); + _header.height = _fileStream->readUint32LE(); + _header.frames = _fileStream->readUint32LE(); + _header.frameRate = (int32)_fileStream->readUint32LE(); + // Flags are determined by which bit is set, which can be one of the following: + // 0 - set to 1 if file contains a ring frame. + // 1 - set to 1 if file is Y-interlaced + // 2 - set to 1 if file is Y-doubled + // If bits 1 or 2 are set, the frame should be scaled to twice its height + // before it is displayed. + _header.flags = _fileStream->readUint32LE(); + + // TODO: should we do any extra processing for Smacker files with ring frames? + + // TODO: should we do any extra processing for Y-doubled videos? Are they the + // same as Y-interlaced videos? + + uint32 i; + for (i = 0; i < 7; ++i) + _header.audioSize[i] = _fileStream->readUint32LE(); + + _header.treesSize = _fileStream->readUint32LE(); + _header.mMapSize = _fileStream->readUint32LE(); + _header.mClrSize = _fileStream->readUint32LE(); + _header.fullSize = _fileStream->readUint32LE(); + _header.typeSize = _fileStream->readUint32LE(); + + uint32 audioInfo; + for (i = 0; i < 7; ++i) { + // AudioRate - Frequency and format information for each sound track, up to 7 audio tracks. + // The 32 constituent bits have the following meaning: + // * bit 31 - data is compressed + // * bit 30 - indicates that audio data is present for this track + // * bit 29 - 1 = 16-bit audio; 0 = 8-bit audio + // * bit 28 - 1 = stereo audio; 0 = mono audio + // * bits 27-26 - if both set to zero - use v2 sound decompression + // * bits 25-24 - unused + // * bits 23-0 - audio sample rate + audioInfo = _fileStream->readUint32LE(); + _header.audioInfo[i].isCompressed = audioInfo & 0x80000000; + _header.audioInfo[i].hasAudio = audioInfo & 0x40000000; + _header.audioInfo[i].is16Bits = audioInfo & 0x20000000; + _header.audioInfo[i].isStereo = audioInfo & 0x10000000; + _header.audioInfo[i].hasV2Compression = !(audioInfo & 0x8000000) && + !(audioInfo & 0x4000000); + _header.audioInfo[i].sampleRate = audioInfo & 0xFFFFFF; + + if (_header.audioInfo[i].hasAudio && i == 0) { + byte flags = 0; + + if (_header.audioInfo[i].is16Bits) + flags = flags | Audio::Mixer::FLAG_16BITS; + + if (_header.audioInfo[i].isStereo) + flags = flags | Audio::Mixer::FLAG_STEREO; + + _audioStream = Audio::makeAppendableAudioStream(_header.audioInfo[i].sampleRate, flags); + } + } + + _header.dummy = _fileStream->readUint32LE(); + + _frameSizes = (uint32 *)malloc(_header.frames * sizeof(uint32)); + for (i = 0; i < _header.frames; ++i) + _frameSizes[i] = _fileStream->readUint32LE(); + + _frameTypes = (byte *)malloc(_header.frames); + for (i = 0; i < _header.frames; ++i) + _frameTypes[i] = _fileStream->readByte(); + + Common::Array<byte> huffmanTrees; + huffmanTrees.resize(_header.treesSize + 2); + _fileStream->read(&huffmanTrees[0], _header.treesSize); + + BitStream bs(&huffmanTrees[0], _header.treesSize + 2); + + _MMapTree = new BigHuffmanTree(bs); + _MClrTree = new BigHuffmanTree(bs); + _FullTree = new BigHuffmanTree(bs); + _TypeTree = new BigHuffmanTree(bs); + + _image = (byte *)malloc(2 * _header.width * _header.height); + memset(_image, 0, 2 * _header.width * _header.height); + _palette = (byte *)malloc(3 * 256); + memset(_palette, 0, 3 * 256); + + return true; +} + +void SMKPlayer::closeFile() { + if (!_fileStream) + return; + + if (_audioStarted && _audioStream) { + _mixer->stopHandle(_audioHandle); + _audioStream = 0; + _audioStarted = false; + } + + delete _fileStream; + + delete _MMapTree; + delete _MClrTree; + delete _FullTree; + delete _TypeTree; + + free(_frameSizes); + free(_frameTypes); + free(_image); + free(_palette); + + _fileStream = 0; +} + +void SMKPlayer::copyFrameToBuffer(byte *dst, uint x, uint y, uint pitch) { + uint h = (_header.flags ? 2 : 1) * _header.height; + uint w = _header.width; + + byte *src = _image; + dst += y * pitch + x; + + do { + memcpy(dst, src, w); + dst += pitch; + src += _header.width; + } while (--h); +} + +bool SMKPlayer::decodeNextFrame() { + uint i; + uint32 chunkSize = 0; + uint32 dataSizeUnpacked = 0; + + uint32 startPos = _fileStream->pos(); + + // Check if we got a frame with palette data, and + // call back the virtual setPalette function to set + // the current palette + if (_frameTypes[_currentSMKFrame] & 1) { + unpackPalette(); + setPalette(_palette); + } + + // Load audio tracks + for (i = 0; i < 7; ++i) { + if (!(_frameTypes[_currentSMKFrame] & (2 << i))) + continue; + + chunkSize = _fileStream->readUint32LE(); + chunkSize -= 4; // subtract the first 4 bytes (chunk size) + + if (_header.audioInfo[i].isCompressed) { + dataSizeUnpacked = _fileStream->readUint32LE(); + chunkSize -= 4; // subtract the next 4 bytes (unpacked data size) + } else { + dataSizeUnpacked = 0; + } + + if (_header.audioInfo[i].hasAudio && chunkSize > 0 && i == 0) { + // If it's track 0, play the audio data + byte *soundBuffer = new byte[chunkSize]; + + _fileStream->read(soundBuffer, chunkSize); + + if (_header.audioInfo[i].isCompressed) { + // Compressed audio (Huffman DPCM encoded) + queueCompressedBuffer(soundBuffer, chunkSize, dataSizeUnpacked, i); + delete[] soundBuffer; + } else { + // Uncompressed audio (PCM) + _audioStream->queueBuffer(soundBuffer, chunkSize); + // The sound buffer will be deleted by AppendableAudioStream + } + + if (!_audioStarted) { + _mixer->playInputStream(Audio::Mixer::kPlainSoundType, &_audioHandle, _audioStream, -1, 255); + _audioStarted = true; + } + } else { + // Ignore the rest of the audio tracks, if they exist + // TODO: Are there any Smacker videos with more than one audio stream? + // If yes, we should play the rest of the audio streams as well + if (chunkSize > 0) + _fileStream->skip(chunkSize); + } + } + + uint32 frameSize = _frameSizes[_currentSMKFrame] & ~3; + + if (_fileStream->pos() - startPos > frameSize) + exit(1); + + uint32 frameDataSize = frameSize - (_fileStream->pos() - startPos); + + _frameData = (byte *)malloc(frameDataSize + 2); + _fileStream->read(_frameData, frameDataSize); + + BitStream bs(_frameData, frameDataSize + 2); + + _MMapTree->reset(); + _MClrTree->reset(); + _FullTree->reset(); + _TypeTree->reset(); + + uint bw = _header.width / 4; + uint bh = _header.height / 4; + uint stride = _header.width; + uint block = 0, blocks = bw*bh; + + uint doubleY = _header.flags ? 2 : 1; + + byte *out; + uint type, run, j, mode; + uint32 p1, p2, clr, map; + byte hi, lo; + + while (block < blocks) { + type = _TypeTree->getCode(bs); + run = getBlockRun((type >> 2) & 0x3f); + + switch (type & 3) { + case SMK_BLOCK_MONO: + while (run-- && block < blocks) { + clr = _MClrTree->getCode(bs); + map = _MMapTree->getCode(bs); + out = _image + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4; + hi = clr >> 8; + lo = clr & 0xff; + for (i = 0; i < 4; i++) { + for (j = 0; j < doubleY; j++) { + out[0] = (map & 1) ? hi : lo; + out[1] = (map & 2) ? hi : lo; + out[2] = (map & 4) ? hi : lo; + out[3] = (map & 8) ? hi : lo; + out += stride; + } + map >>= 4; + } + ++block; + } + break; + case SMK_BLOCK_FULL: + // Smacker v2 has one mode, Smacker v4 has three + if (_header.signature == MKID_BE('SMK2')) { + mode = 0; + } else { + // 00 - mode 0 + // 10 - mode 1 + // 01 - mode 2 + mode = 0; + if (bs.getBit()) { + mode = 1; + } else if (bs.getBit()) { + mode = 2; + } + } + + while (run-- && block < blocks) { + out = _image + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4; + switch (mode) { + case 0: + for (i = 0; i < 4; ++i) { + p1 = _FullTree->getCode(bs); + p2 = _FullTree->getCode(bs); + for (j = 0; j < doubleY; ++j) { + out[2] = p1 & 0xff; + out[3] = p1 >> 8; + out[0] = p2 & 0xff; + out[1] = p2 >> 8; + out += stride; + } + } + break; + case 1: + p1 = _FullTree->getCode(bs); + out[0] = out[1] = p1 & 0xFF; + out[2] = out[3] = p1 >> 8; + out += stride; + out[0] = out[1] = p1 & 0xFF; + out[2] = out[3] = p1 >> 8; + out += stride; + p2 = _FullTree->getCode(bs); + out[0] = out[1] = p2 & 0xFF; + out[2] = out[3] = p2 >> 8; + out += stride; + out[0] = out[1] = p2 & 0xFF; + out[2] = out[3] = p2 >> 8; + out += stride; + break; + case 2: + for(i = 0; i < 2; i++) { + // We first get p2 and then p1 + // Check ffmpeg thread "[PATCH] Smacker video decoder bug fix" + // http://article.gmane.org/gmane.comp.video.ffmpeg.devel/78768 + p2 = _FullTree->getCode(bs); + p1 = _FullTree->getCode(bs); + for (j = 0; j < doubleY; ++j) { + out[0] = p1 & 0xff; + out[1] = p1 >> 8; + out[2] = p2 & 0xff; + out[3] = p2 >> 8; + out += stride; + } + for (j = 0; j < doubleY; ++j) { + out[0] = p1 & 0xff; + out[1] = p1 >> 8; + out[2] = p2 & 0xff; + out[3] = p2 >> 8; + out += stride; + } + } + break; + } + ++block; + } + break; + case SMK_BLOCK_SKIP: + while (run-- && block < blocks) + block++; + break; + case SMK_BLOCK_FILL: + uint32 col; + mode = type >> 8; + while (run-- && block < blocks) { + out = _image + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4; + col = mode * 0x01010101; + for (i = 0; i < 4 * doubleY; ++i) { + out[0] = out[1] = out[2] = out[3] = col; + out += stride; + } + ++block; + } + break; + } + } + + _fileStream->seek(startPos + frameSize); + + free(_frameData); + + return ++_currentSMKFrame < _header.frames; +} + +void SMKPlayer::queueCompressedBuffer(byte *buffer, uint32 bufferSize, + uint32 unpackedSize, int streamNum) { + + BitStream audioBS(buffer, bufferSize); + bool dataPresent = audioBS.getBit(); + + if (!dataPresent) + return; + + bool isStereo = audioBS.getBit(); + assert(isStereo == _header.audioInfo[streamNum].isStereo); + bool is16Bits = audioBS.getBit(); + assert(is16Bits == _header.audioInfo[streamNum].is16Bits); + + int numBytes = 1 * (isStereo ? 2 : 1) * (is16Bits ? 2 : 1); + + byte *unpackedBuffer = new byte[unpackedSize]; + byte *curPointer = unpackedBuffer; + uint32 curPos = 0; + + SmallHuffmanTree *audioTrees[4]; + for (int k = 0; k < numBytes; k++) + audioTrees[k] = new SmallHuffmanTree(audioBS); + + // Base values, stored as big endian + + int32 bases[2]; + + if (isStereo) + bases[1] = (!is16Bits) ? audioBS.getBits8() : + ((int16) (((audioBS.getBits8() << 8) | audioBS.getBits8()))); + + bases[0] = (!is16Bits) ? audioBS.getBits8() : + ((int16) (((audioBS.getBits8() << 8) | audioBS.getBits8()))); + + + // The bases are the first samples, too + for (int i = 0; i < (isStereo ? 2 : 1); i++, curPointer += (is16Bits ? 2 : 1), curPos += (is16Bits ? 2 : 1)) { + if (is16Bits) + WRITE_BE_UINT16(curPointer, bases[i]); + else + *curPointer = (bases[i] & 0xFF) ^ 0x80; + } + + // Next follow the deltas, which are added to the corresponding base values and + // are stored as little endian + // We store the unpacked bytes in big endian format + + while (curPos < unpackedSize) { + // If the sample is stereo, the data is stored for the left and right channel, respectively + // (the exact opposite to the base values) + if (!is16Bits) { + + for (int k = 0; k < (isStereo ? 2 : 1); k++) { + bases[k] += (int8) ((int16) audioTrees[k]->getCode(audioBS)); + *curPointer++ = CLIP<int>(bases[k], 0, 255) ^ 0x80; + curPos++; + } + + } else { + + for (int k = 0; k < (isStereo ? 2 : 1); k++) { + bases[k] += (int16) (audioTrees[k * 2]->getCode(audioBS) | + (audioTrees[k * 2 + 1]->getCode(audioBS) << 8)); + + WRITE_BE_UINT16(curPointer, CLIP<int32>(bases[k], -32768, 32767)); + curPointer += 2; + curPos += 2; + } + } + + } + + for (int k = 0; k < numBytes; k++) + delete audioTrees[k]; + + _audioStream->queueBuffer(unpackedBuffer, unpackedSize); + // unpackedBuffer will be deleted by AppendableAudioStream +} + +void SMKPlayer::unpackPalette() { + uint startPos = _fileStream->pos(); + uint32 len = 4 * _fileStream->readByte(); + + byte *chunk = (byte *)malloc(len); + _fileStream->read(&chunk[0], len); + byte *p = &chunk[0]; + + byte oldPalette[3*256]; + memcpy(oldPalette, _palette, 3 * 256); + + byte *pal = _palette; + + int sz = 0; + byte b0; + while (sz < 256) { + b0 = *p++; + if (b0 & 0x80) { // if top bit is 1 (0x80 = 10000000) + sz += (b0 & 0x7f) + 1; // get lower 7 bits + 1 (0x7f = 01111111) + pal += 3 * ((b0 & 0x7f) + 1); + } else if (b0 & 0x40) { // if top 2 bits are 01 (0x40 = 01000000) + byte c = (b0 & 0x3f) + 1; // get lower 6 bits + 1 (0x3f = 00111111) + uint s = 3 * *p++; + sz += c; + + while (c--) { + *pal++ = oldPalette[s + 0]; + *pal++ = oldPalette[s + 1]; + *pal++ = oldPalette[s + 2]; + s += 3; + } + } else { // top 2 bits are 00 + sz++; + // get the lower 6 bits for each component (0x3f = 00111111) + byte b = b0 & 0x3f; + byte g = (*p++) & 0x3f; + byte r = (*p++) & 0x3f; + + assert(g < 0xc0 && b < 0xc0); + + // upscale to full 8-bit color values by multiplying by 4 + *pal++ = b * 4; + *pal++ = g * 4; + *pal++ = r * 4; + } + } + + _fileStream->seek(startPos + len); + + free(chunk); +} + +} // End of namespace Graphics diff --git a/graphics/video/smk_player.h b/graphics/video/smk_player.h new file mode 100644 index 0000000000..e07412b2a5 --- /dev/null +++ b/graphics/video/smk_player.h @@ -0,0 +1,196 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// Based on http://wiki.multimedia.cx/index.php?title=Smacker +// and the FFmpeg Smacker decoder (libavcodec/smacker.c), revision 16143 +// http://svn.ffmpeg.org/ffmpeg/trunk/libavcodec/smacker.c?revision=16143&view=markup + +#ifndef GRAPHICS_VIDEO_SMK_PLAYER_H +#define GRAPHICS_VIDEO_SMK_PLAYER_H + +#include "common/scummsys.h" +#include "common/stream.h" +#include "sound/mixer.h" +#include "sound/audiostream.h" + +class OSystem; + +namespace Graphics { + +class BigHuffmanTree; + +/** + * Implementation of a Smacker v2/v4 video decoder + */ +class SMKPlayer { +public: + SMKPlayer(Audio::Mixer *mixer); + virtual ~SMKPlayer(); + + /** + * Returns the width of the video + * @return the width of the video + */ + int getWidth(); + + /** + * Returns the height of the video + * @return the height of the video + */ + int getHeight(); + + /** + * Returns the current frame number of the video + * @return the current frame number of the video + */ + int32 getCurFrame(); + + /** + * Returns the amount of frames in the video + * @return the amount of frames in the video + */ + int32 getFrameCount(); + + /** + * Returns the frame rate of the video + * @return the frame rate of the video + */ + int32 getFrameRate(); + + /** + * Returns the time to wait for each frame in 1/100 ms + * @return the time to wait for each frame in 1/100 ms + */ + int32 getFrameDelay(); + + /** + * Returns the current A/V lag in 1/100 ms + * If > 0, audio lags behind + * If < 0, video lags behind + * @return the current A/V lag in 1/100 ms + */ + int32 getAudioLag(); + + /** + * Returns the time to wait until the next frame in ms, minding any lag + * @return the time to wait until the next frame in ms + */ + uint32 getFrameWaitTime(); + + /** + * Load an SMK encoded video file + * @param filename the filename to load + */ + bool loadFile(const char *filename); + + /** + * Close an SMK encoded video file + */ + void closeFile(); + +protected: + /** + * Set palette, based on current frame + * @param pal the palette data + */ + virtual void setPalette(byte *pal) = 0; + + /** + * Copy current frame into the specified position of the destination + * buffer. + * @param dst the buffer + * @param x the x position of the buffer + * @param y the y position of the buffer + * @param pitch the pitch of buffer + */ + void copyFrameToBuffer(byte *dst, uint x, uint y, uint pitch); + + /** + * Decode the next frame + */ + bool decodeNextFrame(); + + Common::SeekableReadStream *_fileStream; + + byte *_image; + +private: + void unpackPalette(); + // Possible runs of blocks + uint getBlockRun(int index) { return (index <= 58) ? index + 1 : 128 << (index - 59); } + void queueCompressedBuffer(byte *buffer, uint32 bufferSize, uint32 unpackedSize, int streamNum); + + struct AudioInfo { + bool isCompressed; + bool hasAudio; + bool is16Bits; + bool isStereo; + bool hasV2Compression; + uint32 sampleRate; + }; + + struct { + uint32 signature; + uint32 width; + uint32 height; + uint32 frames; + int32 frameRate; + uint32 flags; + uint32 audioSize[7]; + uint32 treesSize; + uint32 mMapSize; + uint32 mClrSize; + uint32 fullSize; + uint32 typeSize; + AudioInfo audioInfo[7]; + uint32 dummy; + } _header; + + uint32 *_frameSizes; + // The FrameTypes section of a Smacker file contains an array of bytes, where + // the 8 bits of each byte describe the contents of the corresponding frame. + // The highest 7 bits correspond to audio frames (bit 7 is track 6, bit 6 track 5 + // and so on), so there can be up to 7 different audio tracks. When the lowest bit + // (bit 0) is set, it denotes a frame that contains a palette record + byte *_frameTypes; + byte *_frameData; + byte *_palette; + + Audio::Mixer *_mixer; + bool _audioStarted; + Audio::AppendableAudioStream *_audioStream; + Audio::SoundHandle _audioHandle; + + uint32 _currentSMKFrame; + + BigHuffmanTree *_MMapTree; + BigHuffmanTree *_MClrTree; + BigHuffmanTree *_FullTree; + BigHuffmanTree *_TypeTree; +}; + +} // End of namespace Graphics + +#endif |
