diff options
author | Matthew Hoops | 2010-05-23 18:33:55 +0000 |
---|---|---|
committer | Matthew Hoops | 2010-05-23 18:33:55 +0000 |
commit | 2f31b05651baf87ea07bc00cced798955a5fa6be (patch) | |
tree | eedade6bd746422fe45e8c48c76d1dd667a45b20 /graphics/video | |
parent | e2a388e2f59f689cd89995013236e90a1b004264 (diff) | |
download | scummvm-rg350-2f31b05651baf87ea07bc00cced798955a5fa6be.tar.gz scummvm-rg350-2f31b05651baf87ea07bc00cced798955a5fa6be.tar.bz2 scummvm-rg350-2f31b05651baf87ea07bc00cced798955a5fa6be.zip |
Move Mohawk's QuickTime code to graphics/ (and QDM2 to sound, disabled when Mohawk is not enabled) so SCI can use the code.
svn-id: r49165
Diffstat (limited to 'graphics/video')
-rw-r--r-- | graphics/video/avi_decoder.cpp | 3 | ||||
-rw-r--r-- | graphics/video/codecs/cinepak.cpp | 287 | ||||
-rw-r--r-- | graphics/video/codecs/cinepak.h | 81 | ||||
-rw-r--r-- | graphics/video/codecs/mjpeg.cpp | 73 | ||||
-rw-r--r-- | graphics/video/codecs/mjpeg.h | 58 | ||||
-rw-r--r-- | graphics/video/codecs/qtrle.cpp | 420 | ||||
-rw-r--r-- | graphics/video/codecs/qtrle.h | 58 | ||||
-rw-r--r-- | graphics/video/codecs/rpza.cpp | 208 | ||||
-rw-r--r-- | graphics/video/codecs/rpza.h | 49 | ||||
-rw-r--r-- | graphics/video/codecs/smc.cpp | 385 | ||||
-rw-r--r-- | graphics/video/codecs/smc.h | 59 | ||||
-rw-r--r-- | graphics/video/qt_decoder.cpp | 1279 | ||||
-rw-r--r-- | graphics/video/qt_decoder.h | 282 |
13 files changed, 3242 insertions, 0 deletions
diff --git a/graphics/video/avi_decoder.cpp b/graphics/video/avi_decoder.cpp index 944c9700bd..f19ca4aa28 100644 --- a/graphics/video/avi_decoder.cpp +++ b/graphics/video/avi_decoder.cpp @@ -35,6 +35,7 @@ #include "graphics/video/avi_decoder.h" // Codecs +#include "graphics/video/codecs/cinepak.h" #include "graphics/video/codecs/msvideo1.h" #include "graphics/video/codecs/msrle.h" @@ -379,6 +380,8 @@ Codec *AviDecoder::createCodec() { return new MSVideo1Decoder(_bmInfo.width, _bmInfo.height, _bmInfo.bitCount); case ID_RLE : return new MSRLEDecoder(_bmInfo.width, _bmInfo.height, _bmInfo.bitCount); + case ID_CVID: + return new CinepakDecoder(); default: warning ("Unknown/Unhandled compression format \'%s\'", tag2str(_vidsHeader.streamHandler)); } diff --git a/graphics/video/codecs/cinepak.cpp b/graphics/video/codecs/cinepak.cpp new file mode 100644 index 0000000000..d3448bb8f7 --- /dev/null +++ b/graphics/video/codecs/cinepak.cpp @@ -0,0 +1,287 @@ +/* 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/codecs/cinepak.h" + +#include "common/system.h" + +// Code here partially based off of ffmpeg ;) + +namespace Graphics { + +// Convert a color from YUV to RGB colorspace, Cinepak style. +inline static void CPYUV2RGB(byte y, byte u, byte v, byte &r, byte &g, byte &b) { + r = CLIP<int>(y + 2 * (v - 128), 0, 255); + g = CLIP<int>(y - (u - 128) / 2 - (v - 128), 0, 255); + b = CLIP<int>(y + 2 * (u - 128), 0, 255); +} + +#define PUT_PIXEL(offset, lum, u, v) \ + CPYUV2RGB(lum, u, v, r, g, b); \ + if (_pixelFormat.bytesPerPixel == 2) \ + *((uint16 *)_curFrame.surface->pixels + offset) = _pixelFormat.RGBToColor(r, g, b); \ + else \ + *((uint32 *)_curFrame.surface->pixels + offset) = _pixelFormat.RGBToColor(r, g, b) + +CinepakDecoder::CinepakDecoder() : Codec() { + _curFrame.surface = NULL; + _curFrame.strips = NULL; + _y = 0; + _pixelFormat = g_system->getScreenFormat(); +} + +CinepakDecoder::~CinepakDecoder() { + if (_curFrame.surface) + _curFrame.surface->free(); + delete[] _curFrame.strips; +} + +Surface *CinepakDecoder::decodeImage(Common::SeekableReadStream *stream) { + _curFrame.flags = stream->readByte(); + _curFrame.length = (stream->readByte() << 16) + stream->readUint16BE(); + _curFrame.width = stream->readUint16BE(); + _curFrame.height = stream->readUint16BE(); + _curFrame.stripCount = stream->readUint16BE(); + + if (_curFrame.strips == NULL) + _curFrame.strips = new CinepakStrip[_curFrame.stripCount]; + + debug (4, "Cinepak Frame: Width = %d, Height = %d, Strip Count = %d", _curFrame.width, _curFrame.height, _curFrame.stripCount); + +#if 0 + // Borrowed from FFMPEG. This should cut out the extra data Cinepak for Sega has (which is useless). + // The theory behind this is that this is here to confuse standard Cinepak decoders. But, we won't let that happen! ;) + if (_curFrame.length != (uint32)stream->size()) { + if (stream->readUint16BE() == 0xFE00) + stream->readUint32BE(); + } +#endif + + if (!_curFrame.surface) { + _curFrame.surface = new Surface(); + _curFrame.surface->create(_curFrame.width, _curFrame.height, _pixelFormat.bytesPerPixel); + } + + // Reset the y variable. + _y = 0; + + for (uint16 i = 0; i < _curFrame.stripCount; i++) { + if (i > 0 && !(_curFrame.flags & 1)) { // Use codebooks from last strip + for (uint16 j = 0; j < 256; j++) { + _curFrame.strips[i].v1_codebook[j] = _curFrame.strips[i - 1].v1_codebook[j]; + _curFrame.strips[i].v4_codebook[j] = _curFrame.strips[i - 1].v4_codebook[j]; + } + } + + _curFrame.strips[i].id = stream->readUint16BE(); + _curFrame.strips[i].length = stream->readUint16BE() - 12; // Subtract the 12 byte header + _curFrame.strips[i].rect.top = _y; stream->readUint16BE(); // Ignore, substitute with our own. + _curFrame.strips[i].rect.left = 0; stream->readUint16BE(); // Ignore, substitute with our own + _curFrame.strips[i].rect.bottom = _y + stream->readUint16BE(); + _curFrame.strips[i].rect.right = _curFrame.width; stream->readUint16BE(); // Ignore, substitute with our own + + //printf ("Left = %d, Top = %d, Right = %d, Bottom = %d\n", _curFrame.strips[i].rect.left, _curFrame.strips[i].rect.top, _curFrame.strips[i].rect.right, _curFrame.strips[i].rect.bottom); + + // Sanity check. Because Cinepak is based on 4x4 blocks, the width and height of each strip needs to be divisible by 4. + assert(!(_curFrame.strips[i].rect.width() % 4) && !(_curFrame.strips[i].rect.height() % 4)); + + uint32 pos = stream->pos(); + + while ((uint32)stream->pos() < (pos + _curFrame.strips[i].length) && !stream->eos()) { + byte chunkID = stream->readByte(); + + if (stream->eos()) + break; + + // Chunk Size is 24-bit, ignore the first 4 bytes + uint32 chunkSize = stream->readByte() << 16; + chunkSize += stream->readUint16BE() - 4; + + int32 startPos = stream->pos(); + + switch (chunkID) { + case 0x20: + case 0x21: + case 0x24: + case 0x25: + loadCodebook(stream, i, 4, chunkID, chunkSize); + break; + case 0x22: + case 0x23: + case 0x26: + case 0x27: + loadCodebook(stream, i, 1, chunkID, chunkSize); + break; + case 0x30: + case 0x31: + case 0x32: + decodeVectors(stream, i, chunkID, chunkSize); + break; + default: + warning("Unknown Cinepak chunk ID %02x", chunkID); + return _curFrame.surface; + } + + if (stream->pos() != startPos + (int32)chunkSize) + stream->seek(startPos + chunkSize); + } + + _y = _curFrame.strips[i].rect.bottom; + } + + return _curFrame.surface; +} + +void CinepakDecoder::loadCodebook(Common::SeekableReadStream *stream, uint16 strip, byte codebookType, byte chunkID, uint32 chunkSize) { + CinepakCodebook *codebook = (codebookType == 1) ? _curFrame.strips[strip].v1_codebook : _curFrame.strips[strip].v4_codebook; + + int32 startPos = stream->pos(); + uint32 flag = 0, mask = 0; + + for (uint16 i = 0; i < 256; i++) { + if ((chunkID & 0x01) && !(mask >>= 1)) { + if ((stream->pos() - startPos + 4) > (int32)chunkSize) + break; + + flag = stream->readUint32BE(); + mask = 0x80000000; + } + + if (!(chunkID & 0x01) || (flag & mask)) { + byte n = (chunkID & 0x04) ? 4 : 6; + if ((stream->pos() - startPos + n) > (int32)chunkSize) + break; + + for (byte j = 0; j < 4; j++) + codebook[i].y[j] = stream->readByte(); + + if (n == 6) { + codebook[i].u = stream->readByte() + 128; + codebook[i].v = stream->readByte() + 128; + } else { + /* this codebook type indicates either greyscale or + * palettized video; if palettized, U & V components will + * not be used so it is safe to set them to 128 for the + * benefit of greyscale rendering in YUV420P */ + codebook[i].u = 128; + codebook[i].v = 128; + } + } + } +} + +void CinepakDecoder::decodeVectors(Common::SeekableReadStream *stream, uint16 strip, byte chunkID, uint32 chunkSize) { + uint32 flag = 0, mask = 0; + uint32 iy[4]; + int32 startPos = stream->pos(); + byte r = 0, g = 0, b = 0; + + for (uint16 y = _curFrame.strips[strip].rect.top; y < _curFrame.strips[strip].rect.bottom; y += 4) { + iy[0] = _curFrame.strips[strip].rect.left + y * _curFrame.width; + iy[1] = iy[0] + _curFrame.width; + iy[2] = iy[1] + _curFrame.width; + iy[3] = iy[2] + _curFrame.width; + + for (uint16 x = _curFrame.strips[strip].rect.left; x < _curFrame.strips[strip].rect.right; x += 4) { + if ((chunkID & 0x01) && !(mask >>= 1)) { + if ((stream->pos() - startPos + 4) > (int32)chunkSize) + return; + + flag = stream->readUint32BE(); + mask = 0x80000000; + } + + if (!(chunkID & 0x01) || (flag & mask)) { + if (!(chunkID & 0x02) && !(mask >>= 1)) { + if ((stream->pos() - startPos + 4) > (int32)chunkSize) + return; + + flag = stream->readUint32BE(); + mask = 0x80000000; + } + + if ((chunkID & 0x02) || (~flag & mask)) { + if ((stream->pos() - startPos + 1) > (int32)chunkSize) + return; + + // Get the codebook + CinepakCodebook codebook = _curFrame.strips[strip].v1_codebook[stream->readByte()]; + + PUT_PIXEL(iy[0] + 0, codebook.y[0], codebook.u, codebook.v); + PUT_PIXEL(iy[0] + 1, codebook.y[0], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 0, codebook.y[0], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 1, codebook.y[0], codebook.u, codebook.v); + + PUT_PIXEL(iy[0] + 2, codebook.y[1], codebook.u, codebook.v); + PUT_PIXEL(iy[0] + 3, codebook.y[1], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 2, codebook.y[1], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 3, codebook.y[1], codebook.u, codebook.v); + + PUT_PIXEL(iy[2] + 0, codebook.y[2], codebook.u, codebook.v); + PUT_PIXEL(iy[2] + 1, codebook.y[2], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 0, codebook.y[2], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 1, codebook.y[2], codebook.u, codebook.v); + + PUT_PIXEL(iy[2] + 2, codebook.y[3], codebook.u, codebook.v); + PUT_PIXEL(iy[2] + 3, codebook.y[3], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 2, codebook.y[3], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 3, codebook.y[3], codebook.u, codebook.v); + } else if (flag & mask) { + if ((stream->pos() - startPos + 4) > (int32)chunkSize) + return; + + CinepakCodebook codebook = _curFrame.strips[strip].v4_codebook[stream->readByte()]; + PUT_PIXEL(iy[0] + 0, codebook.y[0], codebook.u, codebook.v); + PUT_PIXEL(iy[0] + 1, codebook.y[1], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 0, codebook.y[2], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 1, codebook.y[3], codebook.u, codebook.v); + + codebook = _curFrame.strips[strip].v4_codebook[stream->readByte()]; + PUT_PIXEL(iy[0] + 2, codebook.y[0], codebook.u, codebook.v); + PUT_PIXEL(iy[0] + 3, codebook.y[1], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 2, codebook.y[2], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 3, codebook.y[3], codebook.u, codebook.v); + + codebook = _curFrame.strips[strip].v4_codebook[stream->readByte()]; + PUT_PIXEL(iy[2] + 0, codebook.y[0], codebook.u, codebook.v); + PUT_PIXEL(iy[2] + 1, codebook.y[1], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 0, codebook.y[2], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 1, codebook.y[3], codebook.u, codebook.v); + + codebook = _curFrame.strips[strip].v4_codebook[stream->readByte()]; + PUT_PIXEL(iy[2] + 2, codebook.y[0], codebook.u, codebook.v); + PUT_PIXEL(iy[2] + 3, codebook.y[1], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 2, codebook.y[2], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 3, codebook.y[3], codebook.u, codebook.v); + } + } + + for (byte i = 0; i < 4; i++) + iy[i] += 4; + } + } +} + +} // End of namespace Graphics diff --git a/graphics/video/codecs/cinepak.h b/graphics/video/codecs/cinepak.h new file mode 100644 index 0000000000..92351cdba8 --- /dev/null +++ b/graphics/video/codecs/cinepak.h @@ -0,0 +1,81 @@ +/* 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_CINEPAK_H +#define GRAPHICS_CINEPAK_H + +#include "common/scummsys.h" +#include "common/stream.h" +#include "common/rect.h" +#include "graphics/surface.h" +#include "graphics/pixelformat.h" + +#include "graphics/video/codecs/codec.h" + +namespace Graphics { + +struct CinepakCodebook { + byte y[4]; + byte u, v; +}; + +struct CinepakStrip { + uint16 id; + uint16 length; + Common::Rect rect; + CinepakCodebook v1_codebook[256], v4_codebook[256]; +}; + +struct CinepakFrame { + byte flags; + uint32 length; + uint16 width; + uint16 height; + uint16 stripCount; + CinepakStrip *strips; + + Surface *surface; +}; + +class CinepakDecoder : public Codec { +public: + CinepakDecoder(); + ~CinepakDecoder(); + + Surface *decodeImage(Common::SeekableReadStream *stream); + PixelFormat getPixelFormat() const { return _pixelFormat; } + +private: + CinepakFrame _curFrame; + int32 _y; + PixelFormat _pixelFormat; + + void loadCodebook(Common::SeekableReadStream *stream, uint16 strip, byte codebookType, byte chunkID, uint32 chunkSize); + void decodeVectors(Common::SeekableReadStream *stream, uint16 strip, byte chunkID, uint32 chunkSize); +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/video/codecs/mjpeg.cpp b/graphics/video/codecs/mjpeg.cpp new file mode 100644 index 0000000000..76363036ee --- /dev/null +++ b/graphics/video/codecs/mjpeg.cpp @@ -0,0 +1,73 @@ +/* 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/system.h" +#include "graphics/conversion.h" // For YUV2RGB + +#include "graphics/video/codecs/mjpeg.h" + +namespace Graphics { + +JPEGDecoder::JPEGDecoder() : Codec() { + _jpeg = new JPEG(); + _pixelFormat = g_system->getScreenFormat(); + _surface = NULL; +} + +JPEGDecoder::~JPEGDecoder() { + delete _jpeg; + + if (_surface) { + _surface->free(); + delete _surface; + } +} + +Surface *JPEGDecoder::decodeImage(Common::SeekableReadStream* stream) { + _jpeg->read(stream); + Surface *ySurface = _jpeg->getComponent(1); + Surface *uSurface = _jpeg->getComponent(2); + Surface *vSurface = _jpeg->getComponent(3); + + if (!_surface) { + _surface = new Surface(); + _surface->create(ySurface->w, ySurface->h, _pixelFormat.bytesPerPixel); + } + + for (uint16 i = 0; i < _surface->h; i++) { + for (uint16 j = 0; j < _surface->w; j++) { + byte r = 0, g = 0, b = 0; + YUV2RGB(*((byte *)ySurface->getBasePtr(j, i)), *((byte *)uSurface->getBasePtr(j, i)), *((byte *)vSurface->getBasePtr(j, i)), r, g, b); + if (_pixelFormat.bytesPerPixel == 2) + *((uint16 *)_surface->getBasePtr(j, i)) = _pixelFormat.RGBToColor(r, g, b); + else + *((uint32 *)_surface->getBasePtr(j, i)) = _pixelFormat.RGBToColor(r, g, b); + } + } + + return _surface; +} + +} // End of namespace Graphics diff --git a/graphics/video/codecs/mjpeg.h b/graphics/video/codecs/mjpeg.h new file mode 100644 index 0000000000..ab364fb5be --- /dev/null +++ b/graphics/video/codecs/mjpeg.h @@ -0,0 +1,58 @@ +/* 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_MJPEG_H +#define GRAPHICS_MJPEG_H + +#include "common/scummsys.h" +#include "common/stream.h" + +#include "graphics/video/codecs/codec.h" +#include "graphics/jpeg.h" +#include "graphics/pixelformat.h" + +namespace Graphics { + +// Motion JPEG Decoder +// Basically a wrapper around JPEG which converts to RGB and also functions +// as a Codec. + +class JPEGDecoder : public Codec { +public: + JPEGDecoder(); + ~JPEGDecoder(); + + Surface *decodeImage(Common::SeekableReadStream *stream); + PixelFormat getPixelFormat() const { return _pixelFormat; } + +private: + PixelFormat _pixelFormat; + JPEG *_jpeg; + Surface *_surface; +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/video/codecs/qtrle.cpp b/graphics/video/codecs/qtrle.cpp new file mode 100644 index 0000000000..3e3fd4cfce --- /dev/null +++ b/graphics/video/codecs/qtrle.cpp @@ -0,0 +1,420 @@ +/* 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$ + * + */ + +// QuickTime RLE Decoder +// Based off ffmpeg's QuickTime RLE decoder (written by Mike Melanson) + +#include "graphics/video/codecs/qtrle.h" + +#include "common/scummsys.h" +#include "common/stream.h" +#include "common/system.h" +#include "graphics/colormasks.h" +#include "graphics/surface.h" + +namespace Graphics { + +QTRLEDecoder::QTRLEDecoder(uint16 width, uint16 height, byte bitsPerPixel) : Codec() { + _bitsPerPixel = bitsPerPixel; + _pixelFormat = g_system->getScreenFormat(); + + // We need to increase the surface size to a multiple of 4 + uint16 wMod = width % 4; + if(wMod != 0) + width += 4 - wMod; + + debug(2, "QTRLE corrected width: %d", width); + + _surface = new Surface(); + _surface->create(width, height, _bitsPerPixel <= 8 ? 1 : _pixelFormat.bytesPerPixel); +} + +#define CHECK_STREAM_PTR(n) \ + if ((stream->pos() + n) > stream->size()) { \ + warning ("Problem: stream out of bounds (%d >= %d)", stream->pos() + n, stream->size()); \ + return; \ + } + +#define CHECK_PIXEL_PTR(n) \ + if ((int32)pixelPtr + n > _surface->w * _surface->h) { \ + warning ("Problem: pixel ptr = %d, pixel limit = %d", pixelPtr + n, _surface->w * _surface->h); \ + return; \ + } \ + +void QTRLEDecoder::decode1(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange) { + uint32 pixelPtr = 0; + byte *rgb = (byte *)_surface->pixels; + + while (linesToChange) { + CHECK_STREAM_PTR(2); + byte skip = stream->readByte(); + int8 rleCode = stream->readSByte(); + + if (rleCode == 0) + break; + + if (skip & 0x80) { + linesToChange--; + rowPtr += _surface->w; + pixelPtr = rowPtr + 2 * (skip & 0x7f); + } else + pixelPtr += 2 * skip; + + if (rleCode < 0) { + // decode the run length code + rleCode = -rleCode; + // get the next 2 bytes from the stream, treat them as groups of 8 pixels, and output them rleCode times */ + CHECK_STREAM_PTR(2); + byte pi0 = stream->readByte(); + byte pi1 = stream->readByte(); + CHECK_PIXEL_PTR(rleCode * 2); + + while (rleCode--) { + rgb[pixelPtr++] = pi0; + rgb[pixelPtr++] = pi1; + } + } else { + // copy the same pixel directly to output 2 times + rleCode *= 2; + CHECK_STREAM_PTR(rleCode); + CHECK_PIXEL_PTR(rleCode); + + while (rleCode--) + rgb[pixelPtr++] = stream->readByte(); + } + } +} + +void QTRLEDecoder::decode2_4(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange, byte bpp) { + uint32 pixelPtr = 0; + byte *rgb = (byte *)_surface->pixels; + byte numPixels = (bpp == 4) ? 8 : 16; + + while (linesToChange--) { + CHECK_STREAM_PTR(2); + pixelPtr = rowPtr + (numPixels * (stream->readByte() - 1)); + + for (int8 rleCode = stream->readSByte(); rleCode != -1; rleCode = stream->readSByte()) { + if (rleCode == 0) { + // there's another skip code in the stream + CHECK_STREAM_PTR(1); + pixelPtr += (numPixels * (stream->readByte() - 1)); + } else if (rleCode < 0) { + // decode the run length code + rleCode = -rleCode; + + // get the next 4 bytes from the stream, treat them as palette indices, and output them rleCode times */ + CHECK_STREAM_PTR(4); + + byte pi[16]; // 16 palette indices + + for (int8 i = numPixels - 1; i >= 0; i--) { + pi[numPixels - 1 - i] = (stream->readByte() >> ((i * bpp) & 0x07)) & ((1 << bpp) - 1); + + // FIXME: Is this right? + //stream_ptr += ((i & ((num_pixels>>2)-1)) == 0); + if ((i & ((numPixels >> 2) - 1)) == 0) + stream->readByte(); + } + + CHECK_PIXEL_PTR(rleCode * numPixels); + + while (rleCode--) + for (byte i = 0; i < numPixels; i++) + rgb[pixelPtr++] = pi[i]; + } else { + // copy the same pixel directly to output 4 times + rleCode *= 4; + CHECK_STREAM_PTR(rleCode); + CHECK_PIXEL_PTR(rleCode * (numPixels >> 2)); + + while (rleCode--) { + byte temp = stream->readByte(); + if (bpp == 4) { + rgb[pixelPtr++] = (temp >> 4) & 0x0f; + rgb[pixelPtr++] = temp & 0x0f; + } else { + rgb[pixelPtr++] = (temp >> 6) & 0x03; + rgb[pixelPtr++] = (temp >> 4) & 0x03; + rgb[pixelPtr++] = (temp >> 2) & 0x03; + rgb[pixelPtr++] = temp & 0x03; + } + } + } + } + + rowPtr += _surface->w; + } +} + +void QTRLEDecoder::decode8(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange) { + uint32 pixelPtr = 0; + byte *rgb = (byte *)_surface->pixels; + + while (linesToChange--) { + CHECK_STREAM_PTR(2); + pixelPtr = rowPtr + 4 * (stream->readByte() - 1); + + for (int8 rleCode = stream->readSByte(); rleCode != -1; rleCode = stream->readSByte()) { + if (rleCode == 0) { + // there's another skip code in the stream + CHECK_STREAM_PTR(1); + pixelPtr += 4 * (stream->readByte() - 1); + } else if (rleCode < 0) { + // decode the run length code + rleCode = -rleCode; + + // get the next 4 bytes from the stream, treat them as palette indices, and output them rleCode times + CHECK_STREAM_PTR(4); + + byte pi[4]; // 4 palette indexes + + for (byte i = 0; i < 4; i++) + pi[i] = stream->readByte(); + + CHECK_PIXEL_PTR(rleCode * 4); + + while (rleCode--) + for (byte i = 0; i < 4; i++) + rgb[pixelPtr++] = pi[i]; + } else { + // copy the same pixel directly to output 4 times + rleCode *= 4; + CHECK_STREAM_PTR(rleCode); + CHECK_PIXEL_PTR(rleCode); + + while (rleCode--) + rgb[pixelPtr++] = stream->readByte(); + } + } + + rowPtr += _surface->w; + } +} + +void QTRLEDecoder::decode16(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange) { + uint32 pixelPtr = 0; + OverlayColor *rgb = (OverlayColor *)_surface->pixels; + + while (linesToChange--) { + CHECK_STREAM_PTR(2); + pixelPtr = rowPtr + stream->readByte() - 1; + + for (int8 rleCode = stream->readSByte(); rleCode != -1; rleCode = stream->readSByte()) { + if (rleCode == 0) { + // there's another skip code in the stream + CHECK_STREAM_PTR(1); + pixelPtr += stream->readByte() - 1; + } else if (rleCode < 0) { + // decode the run length code + rleCode = -rleCode; + CHECK_STREAM_PTR(2); + + uint16 rgb16 = stream->readUint16BE(); + + CHECK_PIXEL_PTR(rleCode); + + while (rleCode--) { + // Convert from RGB555 to the format specified by the Overlay + byte r = 0, g = 0, b = 0; + colorToRGB<ColorMasks<555> >(rgb16, r, g, b); + rgb[pixelPtr++] = _pixelFormat.RGBToColor(r, g, b); + } + } else { + CHECK_STREAM_PTR(rleCode * 2); + CHECK_PIXEL_PTR(rleCode); + + // copy pixels directly to output + while (rleCode--) { + uint16 rgb16 = stream->readUint16BE(); + + // Convert from RGB555 to the format specified by the Overlay + byte r = 0, g = 0, b = 0; + colorToRGB<ColorMasks<555> >(rgb16, r, g, b); + rgb[pixelPtr++] = _pixelFormat.RGBToColor(r, g, b); + } + } + } + + rowPtr += _surface->w; + } +} + +void QTRLEDecoder::decode24(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange) { + uint32 pixelPtr = 0; + OverlayColor *rgb = (OverlayColor *)_surface->pixels; + + while (linesToChange--) { + CHECK_STREAM_PTR(2); + pixelPtr = rowPtr + stream->readByte() - 1; + + for (int8 rleCode = stream->readSByte(); rleCode != -1; rleCode = stream->readSByte()) { + if (rleCode == 0) { + // there's another skip code in the stream + CHECK_STREAM_PTR(1); + pixelPtr += stream->readByte() - 1; + } else if (rleCode < 0) { + // decode the run length code + rleCode = -rleCode; + + CHECK_STREAM_PTR(3); + + byte r = stream->readByte(); + byte g = stream->readByte(); + byte b = stream->readByte(); + + CHECK_PIXEL_PTR(rleCode); + + while (rleCode--) + rgb[pixelPtr++] = _pixelFormat.RGBToColor(r, g, b); + } else { + CHECK_STREAM_PTR(rleCode * 3); + CHECK_PIXEL_PTR(rleCode); + + // copy pixels directly to output + while (rleCode--) { + byte r = stream->readByte(); + byte g = stream->readByte(); + byte b = stream->readByte(); + rgb[pixelPtr++] = _pixelFormat.RGBToColor(r, g, b); + } + } + } + + rowPtr += _surface->w; + } +} + +void QTRLEDecoder::decode32(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange) { + uint32 pixelPtr = 0; + OverlayColor *rgb = (OverlayColor *)_surface->pixels; + + while (linesToChange--) { + CHECK_STREAM_PTR(2); + pixelPtr = rowPtr + stream->readByte() - 1; + + for (int8 rleCode = stream->readSByte(); rleCode != -1; rleCode = stream->readSByte()) { + if (rleCode == 0) { + // there's another skip code in the stream + CHECK_STREAM_PTR(1); + pixelPtr += stream->readByte() - 1; + } else if (rleCode < 0) { + // decode the run length code + rleCode = -rleCode; + + CHECK_STREAM_PTR(4); + + byte a = stream->readByte(); + byte r = stream->readByte(); + byte g = stream->readByte(); + byte b = stream->readByte(); + + CHECK_PIXEL_PTR(rleCode); + + while (rleCode--) + rgb[pixelPtr++] = _pixelFormat.ARGBToColor(a, r, g, b); + } else { + CHECK_STREAM_PTR(rleCode * 4); + CHECK_PIXEL_PTR(rleCode); + + // copy pixels directly to output + while (rleCode--) { + byte a = stream->readByte(); + byte r = stream->readByte(); + byte g = stream->readByte(); + byte b = stream->readByte(); + rgb[pixelPtr++] = _pixelFormat.ARGBToColor(a, r, g, b); + } + } + } + + rowPtr += _surface->w; + } +} + +Surface *QTRLEDecoder::decodeImage(Common::SeekableReadStream *stream) { + uint16 start_line = 0; + uint16 height = _surface->h; + + // check if this frame is even supposed to change + if (stream->size() < 8) + return _surface; + + // start after the chunk size + stream->readUint32BE(); + + // fetch the header + uint16 header = stream->readUint16BE(); + + // if a header is present, fetch additional decoding parameters + if (header & 8) { + if(stream->size() < 14) + return _surface; + start_line = stream->readUint16BE(); + stream->readUint16BE(); // Unknown + height = stream->readUint16BE(); + stream->readUint16BE(); // Unknown + } + + uint32 row_ptr = _surface->w * start_line; + + switch (_bitsPerPixel) { + case 1: + case 33: + decode1(stream, row_ptr, height); + break; + case 2: + case 34: + decode2_4(stream, row_ptr, height, 2); + break; + case 4: + case 36: + decode2_4(stream, row_ptr, height, 4); + break; + case 8: + case 40: + decode8(stream, row_ptr, height); + break; + case 16: + decode16(stream, row_ptr, height); + break; + case 24: + decode24(stream, row_ptr, height); + break; + case 32: + decode32(stream, row_ptr, height); + break; + default: + error ("Unsupported bits per pixel %d", _bitsPerPixel); + } + + return _surface; +} + +QTRLEDecoder::~QTRLEDecoder() { + _surface->free(); +} + +} // End of namespace Graphics diff --git a/graphics/video/codecs/qtrle.h b/graphics/video/codecs/qtrle.h new file mode 100644 index 0000000000..efbef14411 --- /dev/null +++ b/graphics/video/codecs/qtrle.h @@ -0,0 +1,58 @@ +/* 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_QTRLE_H +#define GRAPHICS_VIDEO_QTRLE_H + +#include "graphics/pixelformat.h" +#include "graphics/video/codecs/codec.h" + +namespace Graphics { + +class QTRLEDecoder : public Codec { +public: + QTRLEDecoder(uint16 width, uint16 height, byte bitsPerPixel); + ~QTRLEDecoder(); + + Surface *decodeImage(Common::SeekableReadStream *stream); + PixelFormat getPixelFormat() const { return _pixelFormat; } + +private: + byte _bitsPerPixel; + + Surface *_surface; + PixelFormat _pixelFormat; + + void decode1(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange); + void decode2_4(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange, byte bpp); + void decode8(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange); + void decode16(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange); + void decode24(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange); + void decode32(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange); +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/video/codecs/rpza.cpp b/graphics/video/codecs/rpza.cpp new file mode 100644 index 0000000000..f0ed72e730 --- /dev/null +++ b/graphics/video/codecs/rpza.cpp @@ -0,0 +1,208 @@ +/* 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 off ffmpeg's RPZA decoder + +#include "graphics/video/codecs/rpza.h" + +#include "common/system.h" +#include "graphics/colormasks.h" + +namespace Graphics { + +RPZADecoder::RPZADecoder(uint16 width, uint16 height) : Codec() { + _pixelFormat = g_system->getScreenFormat(); + + // We need to increase the surface size to a multiple of 4 + uint16 wMod = width % 4; + if(wMod != 0) + width += 4 - wMod; + + debug(2, "RPZA corrected width: %d", width); + + _surface = new Surface(); + _surface->create(width, height, _pixelFormat.bytesPerPixel); +} + +#define ADVANCE_BLOCK() \ + pixelPtr += 4; \ + if (pixelPtr >= _surface->w) { \ + pixelPtr = 0; \ + rowPtr += _surface->w * 4; \ + } \ + totalBlocks--; \ + if (totalBlocks < 0) \ + error("block counter just went negative (this should not happen)") \ + +// Convert from RGB555 to the format specified by the screen +#define PUT_PIXEL(color) \ + if ((int32)blockPtr < _surface->w * _surface->h) { \ + byte r = 0, g = 0, b = 0; \ + colorToRGB<ColorMasks<555> >(color, r, g, b); \ + if (_pixelFormat.bytesPerPixel == 2) \ + *((uint16 *)_surface->pixels + blockPtr) = _pixelFormat.RGBToColor(r, g, b); \ + else \ + *((uint32 *)_surface->pixels + blockPtr) = _pixelFormat.RGBToColor(r, g, b); \ + } \ + blockPtr++ + +Surface *RPZADecoder::decodeImage(Common::SeekableReadStream *stream) { + uint16 colorA = 0, colorB = 0; + uint16 color4[4]; + + uint32 rowPtr = 0; + uint32 pixelPtr = 0; + uint32 blockPtr = 0; + uint32 rowInc = _surface->w - 4; + uint16 ta; + uint16 tb; + + // First byte is always 0xe1. Warn if it's different + byte firstByte = stream->readByte(); + if (firstByte != 0xe1) + warning("First RPZA chunk byte is 0x%02x instead of 0xe1", firstByte); + + // Get chunk size, ingnoring first byte + uint32 chunkSize = stream->readUint16BE() << 8; + chunkSize += stream->readByte(); + + // If length mismatch use size from MOV file and try to decode anyway + if (chunkSize != (uint32)stream->size()) { + warning("MOV chunk size != encoded chunk size; using MOV chunk size"); + chunkSize = stream->size(); + } + + // Number of 4x4 blocks in frame + int32 totalBlocks = ((_surface->w + 3) / 4) * ((_surface->h + 3) / 4); + + // Process chunk data + while ((uint32)stream->pos() < chunkSize) { + byte opcode = stream->readByte(); // Get opcode + byte numBlocks = (opcode & 0x1f) + 1; // Extract block counter from opcode + + // If opcode MSbit is 0, we need more data to decide what to do + if ((opcode & 0x80) == 0) { + colorA = (opcode << 8) | stream->readByte(); + opcode = 0; + if (stream->readByte() & 0x80) { + // Must behave as opcode 110xxxxx, using colorA computed + // above. Use fake opcode 0x20 to enter switch block at + // the right place + opcode = 0x20; + numBlocks = 1; + } + stream->seek(-1, SEEK_CUR); + } + + switch (opcode & 0xe0) { + case 0x80: // Skip blocks + while (numBlocks--) { + ADVANCE_BLOCK(); + } + break; + case 0xa0: // Fill blocks with one color + colorA = stream->readUint16BE(); + while (numBlocks--) { + blockPtr = rowPtr + pixelPtr; + for (byte pixel_y = 0; pixel_y < 4; pixel_y++) { + for (byte pixel_x = 0; pixel_x < 4; pixel_x++) { + PUT_PIXEL(colorA); + } + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // Fill blocks with 4 colors + case 0xc0: + colorA = stream->readUint16BE(); + case 0x20: + colorB = stream->readUint16BE(); + + // Sort out the colors + color4[0] = colorB; + color4[1] = 0; + color4[2] = 0; + color4[3] = colorA; + + // Red components + ta = (colorA >> 10) & 0x1F; + tb = (colorB >> 10) & 0x1F; + color4[1] |= ((11 * ta + 21 * tb) >> 5) << 10; + color4[2] |= ((21 * ta + 11 * tb) >> 5) << 10; + + // Green components + ta = (colorA >> 5) & 0x1F; + tb = (colorB >> 5) & 0x1F; + color4[1] |= ((11 * ta + 21 * tb) >> 5) << 5; + color4[2] |= ((21 * ta + 11 * tb) >> 5) << 5; + + // Blue components + ta = colorA & 0x1F; + tb = colorB & 0x1F; + color4[1] |= ((11 * ta + 21 * tb) >> 5); + color4[2] |= ((21 * ta + 11 * tb) >> 5); + + while (numBlocks--) { + blockPtr = rowPtr + pixelPtr; + for (byte pixel_y = 0; pixel_y < 4; pixel_y++) { + byte index = stream->readByte(); + for (byte pixel_x = 0; pixel_x < 4; pixel_x++){ + byte idx = (index >> (2 * (3 - pixel_x))) & 0x03; + PUT_PIXEL(color4[idx]); + } + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // Fill block with 16 colors + case 0x00: + blockPtr = rowPtr + pixelPtr; + for (byte pixel_y = 0; pixel_y < 4; pixel_y++) { + for (byte pixel_x = 0; pixel_x < 4; pixel_x++){ + // We already have color of upper left pixel + if (pixel_y != 0 || pixel_x != 0) + colorA = stream->readUint16BE(); + + PUT_PIXEL(colorA); + } + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + break; + + // Unknown opcode + default: + error("Unknown opcode %02x in rpza chunk", opcode); + } + } + + return _surface; +} + +} // End of namespace Graphics diff --git a/graphics/video/codecs/rpza.h b/graphics/video/codecs/rpza.h new file mode 100644 index 0000000000..e6d32feb72 --- /dev/null +++ b/graphics/video/codecs/rpza.h @@ -0,0 +1,49 @@ +/* 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_RPZA_H +#define GRAPHICS_VIDEO_RPZA_H + +#include "graphics/pixelformat.h" +#include "graphics/video/codecs/codec.h" + +namespace Graphics { + +class RPZADecoder : public Codec { +public: + RPZADecoder(uint16 width, uint16 height); + ~RPZADecoder() { delete _surface; } + + Surface *decodeImage(Common::SeekableReadStream *stream); + PixelFormat getPixelFormat() const { return _pixelFormat; } + +private: + Surface *_surface; + PixelFormat _pixelFormat; +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/video/codecs/smc.cpp b/graphics/video/codecs/smc.cpp new file mode 100644 index 0000000000..4661e3dc84 --- /dev/null +++ b/graphics/video/codecs/smc.cpp @@ -0,0 +1,385 @@ +/* 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 off ffmpeg's SMC decoder + +#include "graphics/video/codecs/smc.h" + +namespace Graphics { + +#define GET_BLOCK_COUNT() \ + (opcode & 0x10) ? (1 + stream->readByte()) : 1 + (opcode & 0x0F); + +#define ADVANCE_BLOCK() \ +{ \ + pixelPtr += 4; \ + if (pixelPtr >= _surface->w) { \ + pixelPtr = 0; \ + rowPtr += _surface->w * 4; \ + } \ + totalBlocks--; \ + if (totalBlocks < 0) { \ + warning("block counter just went negative (this should not happen)"); \ + return _surface; \ + } \ +} + +SMCDecoder::SMCDecoder(uint16 width, uint16 height) { + _surface = new Graphics::Surface(); + _surface->create(width, height, 1); +} + +Graphics::Surface *SMCDecoder::decodeImage(Common::SeekableReadStream *stream) { + byte *pixels = (byte *)_surface->pixels; + + uint32 numBlocks = 0; + uint32 colorFlags = 0; + uint32 colorFlagsA = 0; + uint32 colorFlagsB = 0; + + const uint16 rowInc = _surface->w - 4; + int32 rowPtr = 0; + int32 pixelPtr = 0; + uint32 blockPtr = 0; + uint32 prevBlockPtr = 0; + uint32 prevBlockPtr1 = 0, prevBlockPtr2 = 0; + byte prevBlockFlag = false; + byte pixel = 0; + + uint32 colorPairIndex = 0; + uint32 colorQuadIndex = 0; + uint32 colorOctetIndex = 0; + uint32 colorTableIndex = 0; // indices to color pair, quad, or octet tables + + int32 chunkSize = stream->readUint32BE() & 0x00FFFFFF; + if (chunkSize != stream->size()) + warning("MOV chunk size != SMC chunk size (%d != %d); ignoring SMC chunk size", chunkSize, stream->size()); + + int32 totalBlocks = ((_surface->w + 3) / 4) * ((_surface->h + 3) / 4); + + // traverse through the blocks + while (totalBlocks != 0) { + // sanity checks + + // make sure stream ptr hasn't gone out of bounds + if (stream->pos() > stream->size()) { + warning("SMC decoder just went out of bounds (stream ptr = %d, chunk size = %d)", stream->pos(), stream->size()); + return _surface; + } + + // make sure the row pointer hasn't gone wild + if (rowPtr >= _surface->w * _surface->h) { + warning("SMC decoder just went out of bounds (row ptr = %d, size = %d)", rowPtr, _surface->w * _surface->h); + return _surface; + } + + byte opcode = stream->readByte(); + + switch (opcode & 0xF0) { + // skip n blocks + case 0x00: + case 0x10: + numBlocks = GET_BLOCK_COUNT(); + while (numBlocks--) { + ADVANCE_BLOCK(); + } + break; + + // repeat last block n times + case 0x20: + case 0x30: + numBlocks = GET_BLOCK_COUNT(); + + // sanity check + if (rowPtr == 0 && pixelPtr == 0) { + warning("encountered repeat block opcode (%02X) but no blocks rendered yet", opcode & 0xF0); + break; + } + + // figure out where the previous block started + if (pixelPtr == 0) + prevBlockPtr1 = (rowPtr - _surface->w * 4) + _surface->w - 4; + else + prevBlockPtr1 = rowPtr + pixelPtr - 4; + + while (numBlocks--) { + blockPtr = rowPtr + pixelPtr; + prevBlockPtr = prevBlockPtr1; + for (byte y = 0; y < 4; y++) { + for (byte x = 0; x < 4; x++) + pixels[blockPtr++] = pixels[prevBlockPtr++]; + blockPtr += rowInc; + prevBlockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // repeat previous pair of blocks n times + case 0x40: + case 0x50: + numBlocks = GET_BLOCK_COUNT(); + numBlocks *= 2; + + // sanity check + if (rowPtr == 0 && pixelPtr < 2 * 4) { + warning("encountered repeat block opcode (%02X) but not enough blocks rendered yet", opcode & 0xF0); + break; + } + + // figure out where the previous 2 blocks started + if (pixelPtr == 0) + prevBlockPtr1 = (rowPtr - _surface->w * 4) + _surface->w - 4 * 2; + else if (pixelPtr == 4) + prevBlockPtr1 = (rowPtr - _surface->w * 4) + rowInc; + else + prevBlockPtr1 = rowPtr + pixelPtr - 4 * 2; + + if (pixelPtr == 0) + prevBlockPtr2 = (rowPtr - _surface->w * 4) + rowInc; + else + prevBlockPtr2 = rowPtr + pixelPtr - 4; + + prevBlockFlag = 0; + while (numBlocks--) { + blockPtr = rowPtr + pixelPtr; + + if (prevBlockFlag) + prevBlockPtr = prevBlockPtr2; + else + prevBlockPtr = prevBlockPtr1; + + prevBlockFlag = !prevBlockFlag; + + for (byte y = 0; y < 4; y++) { + for (byte x = 0; x < 4; x++) + pixels[blockPtr++] = pixels[prevBlockPtr++]; + + blockPtr += rowInc; + prevBlockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // 1-color block encoding + case 0x60: + case 0x70: + numBlocks = GET_BLOCK_COUNT(); + pixel = stream->readByte(); + + while (numBlocks--) { + blockPtr = rowPtr + pixelPtr; + for (byte y = 0; y < 4; y++) { + for (byte x = 0; x < 4; x++) + pixels[blockPtr++] = pixel; + + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // 2-color block encoding + case 0x80: + case 0x90: + numBlocks = (opcode & 0x0F) + 1; + + // figure out which color pair to use to paint the 2-color block + if ((opcode & 0xF0) == 0x80) { + // fetch the next 2 colors from bytestream and store in next + // available entry in the color pair table + for (byte i = 0; i < CPAIR; i++) { + pixel = stream->readByte(); + colorTableIndex = CPAIR * colorPairIndex + i; + _colorPairs[colorTableIndex] = pixel; + } + + // this is the base index to use for this block + colorTableIndex = CPAIR * colorPairIndex; + colorPairIndex++; + + // wraparound + if (colorPairIndex == COLORS_PER_TABLE) + colorPairIndex = 0; + } else + colorTableIndex = CPAIR * stream->readByte(); + + while (numBlocks--) { + colorFlags = stream->readUint16BE(); + uint16 flagMask = 0x8000; + blockPtr = rowPtr + pixelPtr; + for (byte y = 0; y < 4; y++) { + for (byte x = 0; x < 4; x++) { + if (colorFlags & flagMask) + pixel = colorTableIndex + 1; + else + pixel = colorTableIndex; + + flagMask >>= 1; + pixels[blockPtr++] = _colorPairs[pixel]; + } + + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // 4-color block encoding + case 0xA0: + case 0xB0: + numBlocks = (opcode & 0x0F) + 1; + + // figure out which color quad to use to paint the 4-color block + if ((opcode & 0xF0) == 0xA0) { + // fetch the next 4 colors from bytestream and store in next + // available entry in the color quad table + for (byte i = 0; i < CQUAD; i++) { + pixel = stream->readByte(); + colorTableIndex = CQUAD * colorQuadIndex + i; + _colorQuads[colorTableIndex] = pixel; + } + + // this is the base index to use for this block + colorTableIndex = CQUAD * colorQuadIndex; + colorQuadIndex++; + + // wraparound + if (colorQuadIndex == COLORS_PER_TABLE) + colorQuadIndex = 0; + } else + colorTableIndex = CQUAD * stream->readByte(); + + while (numBlocks--) { + colorFlags = stream->readUint32BE(); + + // flag mask actually acts as a bit shift count here + byte flagMask = 30; + blockPtr = rowPtr + pixelPtr; + + for (byte y = 0; y < 4; y++) { + for (byte x = 0; x < 4; x++) { + pixel = colorTableIndex + ((colorFlags >> flagMask) & 0x03); + flagMask -= 2; + pixels[blockPtr++] = _colorQuads[pixel]; + } + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // 8-color block encoding + case 0xC0: + case 0xD0: + numBlocks = (opcode & 0x0F) + 1; + + // figure out which color octet to use to paint the 8-color block + if ((opcode & 0xF0) == 0xC0) { + // fetch the next 8 colors from bytestream and store in next + // available entry in the color octet table + for (byte i = 0; i < COCTET; i++) { + pixel = stream->readByte(); + colorTableIndex = COCTET * colorOctetIndex + i; + _colorOctets[colorTableIndex] = pixel; + } + + // this is the base index to use for this block + colorTableIndex = COCTET * colorOctetIndex; + colorOctetIndex++; + + // wraparound + if (colorOctetIndex == COLORS_PER_TABLE) + colorOctetIndex = 0; + } else + colorTableIndex = COCTET * stream->readByte(); + + while (numBlocks--) { + /* + For this input of 6 hex bytes: + 01 23 45 67 89 AB + Mangle it to this output: + flags_a = xx012456, flags_b = xx89A37B + */ + + // build the color flags + byte flagData[6]; + stream->read(flagData, 6); + + colorFlagsA = ((READ_BE_UINT16(flagData) & 0xFFF0) << 8) | (READ_BE_UINT16(flagData + 2) >> 4); + colorFlagsB = ((READ_BE_UINT16(flagData + 4) & 0xFFF0) << 8) | ((flagData[1] & 0xF) << 8) | + ((flagData[3] & 0xF) << 4) | (flagData[5] & 0xf); + + colorFlags = colorFlagsA; + + // flag mask actually acts as a bit shift count here + byte flagMask = 21; + blockPtr = rowPtr + pixelPtr; + for (byte y = 0; y < 4; y++) { + // reload flags at third row (iteration y == 2) + if (y == 2) { + colorFlags = colorFlagsB; + flagMask = 21; + } + + for (byte x = 0; x < 4; x++) { + pixel = colorTableIndex + ((colorFlags >> flagMask) & 0x07); + flagMask -= 3; + pixels[blockPtr++] = _colorOctets[pixel]; + } + + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // 16-color block encoding (every pixel is a different color) + case 0xE0: + numBlocks = (opcode & 0x0F) + 1; + + while (numBlocks--) { + blockPtr = rowPtr + pixelPtr; + for (byte y = 0; y < 4; y++) { + for (byte x = 0; x < 4; x++) + pixels[blockPtr++] = stream->readByte(); + + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + case 0xF0: + warning("0xF0 opcode seen in SMC chunk (contact the developers)"); + break; + } + } + + return _surface; +} + +} // End of namespace Graphics diff --git a/graphics/video/codecs/smc.h b/graphics/video/codecs/smc.h new file mode 100644 index 0000000000..2d4355a83e --- /dev/null +++ b/graphics/video/codecs/smc.h @@ -0,0 +1,59 @@ +/* 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_SMC_H +#define GRAPHICS_VIDEO_SMC_H + +#include "graphics/video/codecs/codec.h" + +namespace Graphics { + +enum { + CPAIR = 2, + CQUAD = 4, + COCTET = 8, + COLORS_PER_TABLE = 256 +}; + +class SMCDecoder : public Codec { +public: + SMCDecoder(uint16 width, uint16 height); + ~SMCDecoder() { delete _surface; } + + Surface *decodeImage(Common::SeekableReadStream *stream); + PixelFormat getPixelFormat() const { return PixelFormat::createFormatCLUT8(); } + +private: + Surface *_surface; + + // SMC color tables + byte _colorPairs[COLORS_PER_TABLE * CPAIR]; + byte _colorQuads[COLORS_PER_TABLE * CQUAD]; + byte _colorOctets[COLORS_PER_TABLE * COCTET]; +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/video/qt_decoder.cpp b/graphics/video/qt_decoder.cpp new file mode 100644 index 0000000000..5351b9b676 --- /dev/null +++ b/graphics/video/qt_decoder.cpp @@ -0,0 +1,1279 @@ +/* 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$ + * + */ + +// +// Heavily based on ffmpeg code. +// +// Copyright (c) 2001 Fabrice Bellard. +// First version by Francois Revol revol@free.fr +// Seek function by Gael Chardon gael.dev@4now.net +// + +#include "graphics/video/qt_decoder.h" + +#include "common/debug.h" +#include "common/endian.h" +#include "common/util.h" +#include "common/zlib.h" + +// Audio codecs +#include "sound/decoders/adpcm.h" +#include "sound/decoders/raw.h" +#include "sound/decoders/qdm2.h" + +// Video codecs +#include "graphics/video/codecs/cinepak.h" +#include "graphics/video/codecs/mjpeg.h" +#include "graphics/video/codecs/qtrle.h" +#include "graphics/video/codecs/rpza.h" +#include "graphics/video/codecs/smc.h" + +namespace Graphics { + +//////////////////////////////////////////// +// QuickTimeDecoder +//////////////////////////////////////////// + +QuickTimeDecoder::QuickTimeDecoder() : VideoDecoder() { + _audStream = NULL; + _beginOffset = 0; + _videoCodec = NULL; + _curFrame = -1; + _startTime = _nextFrameStartTime = 0; + _audHandle = Audio::SoundHandle(); + _numStreams = 0; + _fd = 0; + _scaledSurface = 0; + _dirtyPalette = false; +} + +QuickTimeDecoder::~QuickTimeDecoder() { + close(); +} + +uint16 QuickTimeDecoder::getWidth() const { + if (_videoStreamIndex < 0) + return 0; + + return _streams[_videoStreamIndex]->width / getScaleMode(); +} + +uint16 QuickTimeDecoder::getHeight() const { + if (_videoStreamIndex < 0) + return 0; + + return _streams[_videoStreamIndex]->height / getScaleMode(); +} + +uint32 QuickTimeDecoder::getFrameCount() const { + if (_videoStreamIndex < 0) + return 0; + + return _streams[_videoStreamIndex]->nb_frames; +} + +byte QuickTimeDecoder::getBitsPerPixel() { + if (_videoStreamIndex < 0) + return 0; + + return _streams[_videoStreamIndex]->bits_per_sample & 0x1F; +} + +uint32 QuickTimeDecoder::getCodecTag() { + if (_videoStreamIndex < 0) + return 0; + + return _streams[_videoStreamIndex]->codec_tag; +} + +ScaleMode QuickTimeDecoder::getScaleMode() const { + if (_videoStreamIndex < 0) + return kScaleNormal; + + return (ScaleMode)(_scaleMode * _streams[_videoStreamIndex]->scaleMode); +} + +uint32 QuickTimeDecoder::getFrameDuration() { + if (_videoStreamIndex < 0) + return 0; + + uint32 curFrameIndex = 0; + for (int32 i = 0; i < _streams[_videoStreamIndex]->stts_count; i++) { + curFrameIndex += _streams[_videoStreamIndex]->stts_data[i].count; + if ((uint32)_curFrame < curFrameIndex) { + // Ok, now we have what duration this frame has. + return _streams[_videoStreamIndex]->stts_data[i].duration; + } + } + + // This should never occur + error ("Cannot find duration for frame %d", _curFrame); + return 0; +} + +PixelFormat QuickTimeDecoder::getPixelFormat() const { + if (!_videoCodec) + return PixelFormat::createFormatCLUT8(); + + return _videoCodec->getPixelFormat(); +} + +void QuickTimeDecoder::rewind() { + delete _videoCodec; _videoCodec = NULL; + _curFrame = -1; + _startTime = _nextFrameStartTime = 0; + + // Restart the audio too + stopAudio(); + if (_audioStreamIndex >= 0) { + _curAudioChunk = 0; + _audStream = Audio::makeQueuingAudioStream(_streams[_audioStreamIndex]->sample_rate, _streams[_audioStreamIndex]->channels == 2); + } + startAudio(); +} + +Codec *QuickTimeDecoder::createCodec(uint32 codecTag, byte bitsPerPixel) { + if (codecTag == MKID_BE('cvid')) { + // Cinepak: As used by most Myst and all Riven videos as well as some Myst ME videos. "The Chief" videos also use this. + return new CinepakDecoder(); + } else if (codecTag == MKID_BE('rpza')) { + // Apple Video ("Road Pizza"): Used by some Myst videos. + return new RPZADecoder(getWidth(), getHeight()); + } else if (codecTag == MKID_BE('rle ')) { + // QuickTime RLE: Used by some Myst ME videos. + return new QTRLEDecoder(getWidth(), getHeight(), bitsPerPixel); + } else if (codecTag == MKID_BE('smc ')) { + // Apple SMC: Used by some Myst videos. + return new SMCDecoder(getWidth(), getHeight()); + } else if (codecTag == MKID_BE('SVQ1')) { + // Sorenson Video 1: Used by some Myst ME videos. + warning ("Sorenson Video 1 not yet supported"); + } else if (codecTag == MKID_BE('SVQ3')) { + // Sorenson Video 3: Used by some Myst ME videos. + warning ("Sorenson Video 3 not yet supported"); + } else if (codecTag == MKID_BE('jpeg')) { + // Motion JPEG: Used by some Myst ME 10th Anniversary videos. + return new JPEGDecoder(); + } else if (codecTag == MKID_BE('QkBk')) { + // CDToons: Used by most of the Broderbund games. This is an unknown format so far. + warning ("CDToons not yet supported"); + } else { + warning ("Unsupported codec \'%s\'", tag2str(codecTag)); + } + + return NULL; +} + +void QuickTimeDecoder::startAudio() { + if (_audStream) // No audio/audio not supported + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_audHandle, _audStream); +} + +void QuickTimeDecoder::stopAudio() { + if (_audStream) { + g_system->getMixer()->stopHandle(_audHandle); + _audStream = NULL; // the mixer automatically frees the stream + } +} + +void QuickTimeDecoder::pauseVideoIntern(bool pause) { + if (_audStream) + g_system->getMixer()->pauseHandle(_audHandle, pause); +} + +Surface *QuickTimeDecoder::decodeNextFrame() { + if (!_videoCodec || _curFrame >= (int32)getFrameCount() - 1) + return NULL; + + if (_startTime == 0) + _startTime = g_system->getMillis(); + + _curFrame++; + _nextFrameStartTime += getFrameDuration(); + + Common::SeekableReadStream *frameData = getNextFramePacket(); + + if (frameData) { + Surface *frame = _videoCodec->decodeImage(frameData); + delete frameData; + return scaleSurface(frame); + } + + return NULL; +} + +Surface *QuickTimeDecoder::scaleSurface(Surface *frame) { + if (getScaleMode() == kScaleNormal) + return frame; + + assert(_scaledSurface); + + for (uint32 j = 0; j < _scaledSurface->h; j++) + for (uint32 k = 0; k < _scaledSurface->w; k++) + memcpy(_scaledSurface->getBasePtr(k, j), frame->getBasePtr(k * getScaleMode(), j * getScaleMode()), frame->bytesPerPixel); + + return _scaledSurface; +} + +bool QuickTimeDecoder::endOfVideo() const { + return (!_audStream || _audStream->endOfData()) && (!_videoCodec || _curFrame >= (int32)getFrameCount() - 1); +} + +bool QuickTimeDecoder::needsUpdate() const { + return !endOfVideo() && getTimeToNextFrame() == 0; +} + +uint32 QuickTimeDecoder::getElapsedTime() const { + if (_audStream) + return g_system->getMixer()->getSoundElapsedTime(_audHandle); + + return g_system->getMillis() - _startTime; +} + +uint32 QuickTimeDecoder::getTimeToNextFrame() const { + if (endOfVideo() || _curFrame < 0) + return 0; + + // Convert from the Sega FILM base to 1000 + uint32 nextFrameStartTime = _nextFrameStartTime * 1000 / _streams[_videoStreamIndex]->time_scale; + uint32 elapsedTime = getElapsedTime(); + + if (nextFrameStartTime <= elapsedTime) + return 0; + + return nextFrameStartTime - elapsedTime; +} + +bool QuickTimeDecoder::load(Common::SeekableReadStream &stream) { + _fd = &stream; + _foundMOOV = _foundMDAT = false; + _numStreams = 0; + _partial = 0; + _videoStreamIndex = _audioStreamIndex = -1; + _startTime = 0; + + initParseTable(); + + MOVatom atom = { 0, 0, 0xffffffff }; + + if (readDefault(atom) < 0 || (!_foundMOOV && !_foundMDAT)) + return false; + + debug(0, "on_parse_exit_offset=%d", _fd->pos()); + + // some cleanup : make sure we are on the mdat atom + if((uint32)_fd->pos() != _mdatOffset) + _fd->seek(_mdatOffset, SEEK_SET); + + _next_chunk_offset = _mdatOffset; // initialise reading + + for (uint32 i = 0; i < _numStreams;) { + if (_streams[i]->codec_type == CODEC_TYPE_MOV_OTHER) {// not audio, not video, delete + delete _streams[i]; + for (uint32 j = i + 1; j < _numStreams; j++) + _streams[j - 1] = _streams[j]; + _numStreams--; + } else + i++; + } + + for (uint32 i = 0; i < _numStreams; i++) { + MOVStreamContext *sc = _streams[i]; + + if(!sc->time_rate) + sc->time_rate = 1; + + if(!sc->time_scale) + sc->time_scale = _timeScale; + + //av_set_pts_info(s->streams[i], 64, sc->time_rate, sc->time_scale); + + sc->duration /= sc->time_rate; + + sc->ffindex = i; + sc->is_ff_stream = 1; + + if (sc->codec_type == CODEC_TYPE_VIDEO && _videoStreamIndex < 0) + _videoStreamIndex = i; + else if (sc->codec_type == CODEC_TYPE_AUDIO && _audioStreamIndex < 0) + _audioStreamIndex = i; + } + + if (_audioStreamIndex >= 0 && checkAudioCodecSupport(_streams[_audioStreamIndex]->codec_tag)) { + _audStream = Audio::makeQueuingAudioStream(_streams[_audioStreamIndex]->sample_rate, _streams[_audioStreamIndex]->channels == 2); + _curAudioChunk = 0; + + // Make sure the bits per sample transfers to the sample size + if (_streams[_audioStreamIndex]->codec_tag == MKID_BE('raw ') || _streams[_audioStreamIndex]->codec_tag == MKID_BE('twos')) + _streams[_audioStreamIndex]->sample_size = (_streams[_audioStreamIndex]->bits_per_sample / 8) * _streams[_audioStreamIndex]->channels; + + startAudio(); + } + + if (_videoStreamIndex >= 0) { + _videoCodec = createCodec(getCodecTag(), getBitsPerPixel()); + + if (getScaleMode() != kScaleNormal) { + // We have to initialize the scaled surface + _scaledSurface = new Surface(); + _scaledSurface->create(getWidth(), getHeight(), getPixelFormat().bytesPerPixel); + } + } + + return true; +} + +void QuickTimeDecoder::initParseTable() { + static const ParseTable p[] = { + { MKID_BE('dinf'), &QuickTimeDecoder::readDefault }, + { MKID_BE('dref'), &QuickTimeDecoder::readLeaf }, + { MKID_BE('edts'), &QuickTimeDecoder::readDefault }, + { MKID_BE('elst'), &QuickTimeDecoder::readELST }, + { MKID_BE('hdlr'), &QuickTimeDecoder::readHDLR }, + { MKID_BE('mdat'), &QuickTimeDecoder::readMDAT }, + { MKID_BE('mdhd'), &QuickTimeDecoder::readMDHD }, + { MKID_BE('mdia'), &QuickTimeDecoder::readDefault }, + { MKID_BE('minf'), &QuickTimeDecoder::readDefault }, + { MKID_BE('moov'), &QuickTimeDecoder::readMOOV }, + { MKID_BE('mvhd'), &QuickTimeDecoder::readMVHD }, + { MKID_BE('smhd'), &QuickTimeDecoder::readLeaf }, + { MKID_BE('stbl'), &QuickTimeDecoder::readDefault }, + { MKID_BE('stco'), &QuickTimeDecoder::readSTCO }, + { MKID_BE('stsc'), &QuickTimeDecoder::readSTSC }, + { MKID_BE('stsd'), &QuickTimeDecoder::readSTSD }, + { MKID_BE('stss'), &QuickTimeDecoder::readSTSS }, + { MKID_BE('stsz'), &QuickTimeDecoder::readSTSZ }, + { MKID_BE('stts'), &QuickTimeDecoder::readSTTS }, + { MKID_BE('tkhd'), &QuickTimeDecoder::readTKHD }, + { MKID_BE('trak'), &QuickTimeDecoder::readTRAK }, + { MKID_BE('udta'), &QuickTimeDecoder::readLeaf }, + { MKID_BE('vmhd'), &QuickTimeDecoder::readLeaf }, + { MKID_BE('cmov'), &QuickTimeDecoder::readCMOV }, + { MKID_BE('wave'), &QuickTimeDecoder::readWAVE }, + { 0, 0 } + }; + + _parseTable = p; +} + +int QuickTimeDecoder::readDefault(MOVatom atom) { + uint32 total_size = 0; + MOVatom a; + int err = 0; + + a.offset = atom.offset; + + while(((total_size + 8) < atom.size) && !_fd->eos() && !err) { + a.size = atom.size; + a.type = 0; + + if (atom.size >= 8) { + a.size = _fd->readUint32BE(); + a.type = _fd->readUint32BE(); + } + + total_size += 8; + a.offset += 8; + debug(4, "type: %08x %.4s sz: %x %x %x", a.type, tag2str(a.type), a.size, atom.size, total_size); + + if (a.size == 1) { // 64 bit extended size + warning("64 bit extended size is not supported in QuickTime"); + return -1; + } + + if (a.size == 0) { + a.size = atom.size - total_size; + if (a.size <= 8) + break; + } + + uint32 i = 0; + + for (; _parseTable[i].type != 0 && _parseTable[i].type != a.type; i++) + // empty; + + if (a.size < 8) + break; + + a.size -= 8; + + if (_parseTable[i].type == 0) { // skip leaf atoms data + debug(0, ">>> Skipped [%s]", tag2str(a.type)); + + _fd->seek(a.size, SEEK_CUR); + } else { + uint32 start_pos = _fd->pos(); + err = (this->*_parseTable[i].func)(a); + + uint32 left = a.size - _fd->pos() + start_pos; + + if (left > 0) // skip garbage at atom end + _fd->seek(left, SEEK_CUR); + } + + a.offset += a.size; + total_size += a.size; + } + + if (!err && total_size < atom.size) + _fd->seek(atom.size - total_size, SEEK_SET); + + return err; +} + +int QuickTimeDecoder::readLeaf(MOVatom atom) { + if (atom.size > 1) + _fd->seek(atom.size, SEEK_SET); + + return 0; +} + +int QuickTimeDecoder::readMOOV(MOVatom atom) { + if (readDefault(atom) < 0) + return -1; + + // we parsed the 'moov' atom, we can terminate the parsing as soon as we find the 'mdat' + // so we don't parse the whole file if over a network + _foundMOOV = true; + + if(_foundMDAT) + return 1; // found both, just go + + return 0; // now go for mdat +} + +int QuickTimeDecoder::readCMOV(MOVatom atom) { +#ifdef USE_ZLIB + // Read in the dcom atom + _fd->readUint32BE(); + if (_fd->readUint32BE() != MKID_BE('dcom')) + return -1; + if (_fd->readUint32BE() != MKID_BE('zlib')) { + warning("Unknown cmov compression type"); + return -1; + } + + // Read in the cmvd atom + uint32 compressedSize = _fd->readUint32BE() - 12; + if (_fd->readUint32BE() != MKID_BE('cmvd')) + return -1; + uint32 uncompressedSize = _fd->readUint32BE(); + + // Read in data + byte *compressedData = (byte *)malloc(compressedSize); + _fd->read(compressedData, compressedSize); + + // Create uncompressed stream + byte *uncompressedData = (byte *)malloc(uncompressedSize); + + // Uncompress the data + unsigned long dstLen = uncompressedSize; + if (!Common::uncompress(uncompressedData, &dstLen, compressedData, compressedSize)) { + warning ("Could not uncompress cmov chunk"); + return -1; + } + + // Load data into a new MemoryReadStream and assign _fd to be that + Common::SeekableReadStream *oldStream = _fd; + _fd = new Common::MemoryReadStream(uncompressedData, uncompressedSize, DisposeAfterUse::YES); + + // Read the contents of the uncompressed data + MOVatom a = { MKID_BE('moov'), 0, uncompressedSize }; + int err = readDefault(a); + + // Assign the file handle back to the original handle + free(compressedData); + delete _fd; + _fd = oldStream; + + return err; +#else + warning ("zlib not found, cannot read QuickTime cmov atom"); + return -1; +#endif +} + +int QuickTimeDecoder::readMVHD(MOVatom atom) { + byte version = _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + if (version == 1) { + warning("QuickTime version 1"); + _fd->readUint32BE(); _fd->readUint32BE(); + _fd->readUint32BE(); _fd->readUint32BE(); + } else { + _fd->readUint32BE(); // creation time + _fd->readUint32BE(); // modification time + } + + _timeScale = _fd->readUint32BE(); // time scale + debug(0, "time scale = %i\n", _timeScale); + + // duration + _duration = (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); + _fd->readUint32BE(); // preferred scale + + _fd->readUint16BE(); // preferred volume + + _fd->seek(10, SEEK_CUR); // reserved + + // We only need two values from the movie display matrix. Most of the values are just + // skipped. xMod and yMod are 16:16 fixed point numbers, the last part of the 3x3 matrix + // is 2:30. + uint32 xMod = _fd->readUint32BE(); + _fd->skip(12); + uint32 yMod = _fd->readUint32BE(); + _fd->skip(16); + + if (xMod != yMod) + error("X and Y resolution modifiers differ"); + + if (xMod == 0x8000) + _scaleMode = kScaleHalf; + else if (xMod == 0x4000) + _scaleMode = kScaleQuarter; + else + _scaleMode = kScaleNormal; + + debug(1, "readMVHD(): scaleMode = %d", (int)_scaleMode); + + _fd->readUint32BE(); // preview time + _fd->readUint32BE(); // preview duration + _fd->readUint32BE(); // poster time + _fd->readUint32BE(); // selection time + _fd->readUint32BE(); // selection duration + _fd->readUint32BE(); // current time + _fd->readUint32BE(); // next track ID + + return 0; +} + +int QuickTimeDecoder::readTRAK(MOVatom atom) { + MOVStreamContext *sc = new MOVStreamContext(); + + if (!sc) + return -1; + + sc->sample_to_chunk_index = -1; + sc->codec_type = CODEC_TYPE_MOV_OTHER; + sc->start_time = 0; // XXX: check + _streams[_numStreams++] = sc; + + return readDefault(atom); +} + +// this atom contains actual media data +int QuickTimeDecoder::readMDAT(MOVatom atom) { + if (atom.size == 0) // wrong one (MP4) + return 0; + + _foundMDAT = true; + + _mdatOffset = atom.offset; + _mdatSize = atom.size; + + if (_foundMOOV) + return 1; // found both, just go + + _fd->seek(atom.size, SEEK_CUR); + + return 0; // now go for moov +} + +int QuickTimeDecoder::readTKHD(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + byte version = _fd->readByte(); + + _fd->readByte(); _fd->readByte(); + _fd->readByte(); // flags + // + //MOV_TRACK_ENABLED 0x0001 + //MOV_TRACK_IN_MOVIE 0x0002 + //MOV_TRACK_IN_PREVIEW 0x0004 + //MOV_TRACK_IN_POSTER 0x0008 + // + + if (version == 1) { + _fd->readUint32BE(); _fd->readUint32BE(); + _fd->readUint32BE(); _fd->readUint32BE(); + } else { + _fd->readUint32BE(); // creation time + _fd->readUint32BE(); // modification time + } + + /* st->id = */_fd->readUint32BE(); // track id (NOT 0 !) + _fd->readUint32BE(); // reserved + //st->start_time = 0; // check + (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); // highlevel (considering edits) duration in movie timebase + _fd->readUint32BE(); // reserved + _fd->readUint32BE(); // reserved + + _fd->readUint16BE(); // layer + _fd->readUint16BE(); // alternate group + _fd->readUint16BE(); // volume + _fd->readUint16BE(); // reserved + + // We only need the two values from the displacement matrix for a track. + // See readMVHD() for more information. + uint32 xMod = _fd->readUint32BE(); + _fd->skip(12); + uint32 yMod = _fd->readUint32BE(); + _fd->skip(16); + + if (xMod != yMod) + error("X and Y resolution modifiers differ"); + + if (xMod == 0x8000) + st->scaleMode = kScaleHalf; + else if (xMod == 0x4000) + st->scaleMode = kScaleQuarter; + else + st->scaleMode = kScaleNormal; + + debug(1, "readTKHD(): scaleMode = %d", (int)_scaleMode); + + // these are fixed-point, 16:16 + // uint32 tkWidth = _fd->readUint32BE() >> 16; // track width + // uint32 tkHeight = _fd->readUint32BE() >> 16; // track height + + return 0; +} + +// edit list atom +int QuickTimeDecoder::readELST(MOVatom atom) { + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + uint32 editCount = _streams[_numStreams - 1]->edit_count = _fd->readUint32BE(); // entries + + for (uint32 i = 0; i < editCount; i++){ + _fd->readUint32BE(); // Track duration + _fd->readUint32BE(); // Media time + _fd->readUint32BE(); // Media rate + } + + debug(0, "track[%i].edit_count = %i", _numStreams - 1, _streams[_numStreams - 1]->edit_count); + + if (editCount != 1) + warning("Multiple edit list entries. Things may go awry"); + + return 0; +} + +int QuickTimeDecoder::readHDLR(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + // component type + uint32 ctype = _fd->readUint32LE(); + uint32 type = _fd->readUint32BE(); // component subtype + + debug(0, "ctype= %s (0x%08lx)", tag2str(ctype), (long)ctype); + debug(0, "stype= %s", tag2str(type)); + + if(ctype == MKID_BE('mhlr')) // MOV + debug(0, "MOV detected"); + else if(ctype == 0) { + warning("MP4 streams are not supported"); + return -1; + } + + if (type == MKID_BE('vide')) + st->codec_type = CODEC_TYPE_VIDEO; + else if (type == MKID_BE('soun')) + st->codec_type = CODEC_TYPE_AUDIO; + + _fd->readUint32BE(); // component manufacture + _fd->readUint32BE(); // component flags + _fd->readUint32BE(); // component flags mask + + if (atom.size <= 24) + return 0; // nothing left to read + + // .mov: PASCAL string + byte len = _fd->readByte(); + _fd->seek(len, SEEK_CUR); + + _fd->seek(atom.size - (_fd->pos() - atom.offset), SEEK_CUR); + + return 0; +} + +int QuickTimeDecoder::readMDHD(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + byte version = _fd->readByte(); + + if (version > 1) + return 1; // unsupported + + _fd->readByte(); _fd->readByte(); + _fd->readByte(); // flags + + if (version == 1) { + _fd->readUint32BE(); _fd->readUint32BE(); + _fd->readUint32BE(); _fd->readUint32BE(); + } else { + _fd->readUint32BE(); // creation time + _fd->readUint32BE(); // modification time + } + + st->time_scale = _fd->readUint32BE(); + st->duration = (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); // duration + + _fd->readUint16BE(); // language + _fd->readUint16BE(); // quality + + return 0; +} + +int QuickTimeDecoder::readSTSD(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + uint32 entries = _fd->readUint32BE(); + + while (entries--) { //Parsing Sample description table + MOVatom a = { 0, 0, 0 }; + uint32 start_pos = _fd->pos(); + int size = _fd->readUint32BE(); // size + uint32 format = _fd->readUint32BE(); // data format + + _fd->readUint32BE(); // reserved + _fd->readUint16BE(); // reserved + _fd->readUint16BE(); // index + + debug(0, "size=%d 4CC= %s codec_type=%d", size, tag2str(format), st->codec_type); + st->codec_tag = format; + + if (st->codec_type == CODEC_TYPE_VIDEO) { + debug(0, "Video Codec FourCC: \'%s\'", tag2str(format)); + + _fd->readUint16BE(); // version + _fd->readUint16BE(); // revision level + _fd->readUint32BE(); // vendor + _fd->readUint32BE(); // temporal quality + _fd->readUint32BE(); // spacial quality + + st->width = _fd->readUint16BE(); // width + st->height = _fd->readUint16BE(); // height + + _fd->readUint32BE(); // horiz resolution + _fd->readUint32BE(); // vert resolution + _fd->readUint32BE(); // data size, always 0 + uint16 frames_per_sample = _fd->readUint16BE(); // frames per samples + + debug(0, "frames/samples = %d", frames_per_sample); + + byte codec_name[32]; + _fd->read(codec_name, 32); // codec name, pascal string (FIXME: true for mp4?) + if (codec_name[0] <= 31) { + memcpy(st->codec_name, &codec_name[1], codec_name[0]); + st->codec_name[codec_name[0]] = 0; + } + + st->bits_per_sample = _fd->readUint16BE(); // depth + st->color_table_id = _fd->readUint16BE(); // colortable id + +// These are set in mov_read_stts and might already be set! +// st->codec->time_base.den = 25; +// st->codec->time_base.num = 1; + + + // figure out the palette situation + byte colorDepth = st->bits_per_sample & 0x1F; + bool colorGreyscale = (st->bits_per_sample & 0x20) != 0; + + debug(0, "color depth: %d", colorDepth); + + // if the depth is 2, 4, or 8 bpp, file is palettized + if (colorDepth == 2 || colorDepth == 4 || colorDepth == 8) { + _dirtyPalette = true; + + if (colorGreyscale) { + debug(0, "Greyscale palette"); + + // compute the greyscale palette + uint16 colorCount = 1 << colorDepth; + int16 colorIndex = 255; + byte colorDec = 256 / (colorCount - 1); + for (byte j = 0; j < colorCount; j++) { + _palette[j * 3] = _palette[j * 3 + 1] = _palette[j * 3 + 2] = colorIndex; + colorIndex -= colorDec; + if (colorIndex < 0) + colorIndex = 0; + } + } else if (st->color_table_id & 0x08) { + // if flag bit 3 is set, use the default palette + //uint16 colorCount = 1 << colorDepth; + + warning("Predefined palette! %dbpp", colorDepth); +#if 0 + byte *color_table; + byte r, g, b; + + if (colorDepth == 2) + color_table = ff_qt_default_palette_4; + else if (colorDepth == 4) + color_table = ff_qt_default_palette_16; + else + color_table = ff_qt_default_palette_256; + + for (byte j = 0; j < color_count; j++) { + r = color_table[j * 4 + 0]; + g = color_table[j * 4 + 1]; + b = color_table[j * 4 + 2]; + _palette_control.palette[j] = (r << 16) | (g << 8) | (b); + } +#endif + + } else { + debug(0, "Palette from file"); + + // load the palette from the file + uint32 colorStart = _fd->readUint32BE(); + /* uint16 colorCount = */ _fd->readUint16BE(); + uint16 colorEnd = _fd->readUint16BE(); + for (uint32 j = colorStart; j <= colorEnd; j++) { + // each R, G, or B component is 16 bits; + // only use the top 8 bits; skip alpha bytes + // up front + _fd->readByte(); + _fd->readByte(); + _palette[j * 3] = _fd->readByte(); + _fd->readByte(); + _palette[j * 3 + 1] = _fd->readByte(); + _fd->readByte(); + _palette[j * 3 + 2] = _fd->readByte(); + _fd->readByte(); + } + } + st->palettized = true; + } else + st->palettized = false; + } else if (st->codec_type == CODEC_TYPE_AUDIO) { + debug(0, "Audio Codec FourCC: \'%s\'", tag2str(format)); + + st->stsd_version = _fd->readUint16BE(); + _fd->readUint16BE(); // revision level + _fd->readUint32BE(); // vendor + + st->channels = _fd->readUint16BE(); // channel count + st->bits_per_sample = _fd->readUint16BE(); // sample size + // do we need to force to 16 for AMR ? + + // handle specific s8 codec + _fd->readUint16BE(); // compression id = 0 + _fd->readUint16BE(); // packet size = 0 + + st->sample_rate = (_fd->readUint32BE() >> 16); + + debug(0, "stsd version =%d", st->stsd_version); + if (st->stsd_version == 0) { + // Not used, except in special cases. See below. + st->samples_per_frame = st->bytes_per_frame = 0; + } else if (st->stsd_version == 1) { + // Read QT version 1 fields. In version 0 these dont exist. + st->samples_per_frame = _fd->readUint32BE(); + debug(0, "stsd samples_per_frame =%d", st->samples_per_frame); + _fd->readUint32BE(); // bytes per packet + st->bytes_per_frame = _fd->readUint32BE(); + debug(0, "stsd bytes_per_frame =%d", st->bytes_per_frame); + _fd->readUint32BE(); // bytes per sample + } else { + warning("Unsupported QuickTime STSD audio version %d", st->stsd_version); + return 1; + } + + // Version 0 videos (such as the Riven ones) don't have this set, + // but we need it later on. Add it in here. + if (format == MKID_BE('ima4')) { + st->samples_per_frame = 64; + st->bytes_per_frame = 34 * st->channels; + } + } else { + // other codec type, just skip (rtp, mp4s, tmcd ...) + _fd->seek(size - (_fd->pos() - start_pos), SEEK_CUR); + } + + // this will read extra atoms at the end (wave, alac, damr, avcC, SMI ...) + a.size = size - (_fd->pos() - start_pos); + if (a.size > 8) + readDefault(a); + else if (a.size > 0) + _fd->seek(a.size, SEEK_CUR); + } + + if (st->codec_type == CODEC_TYPE_AUDIO && st->sample_rate == 0 && st->time_scale > 1) + st->sample_rate= st->time_scale; + + return 0; +} + +int QuickTimeDecoder::readSTSC(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->sample_to_chunk_sz = _fd->readUint32BE(); + + debug(0, "track[%i].stsc.entries = %i", _numStreams - 1, st->sample_to_chunk_sz); + + st->sample_to_chunk = new MOVstsc[st->sample_to_chunk_sz]; + + if (!st->sample_to_chunk) + return -1; + + for (uint32 i = 0; i < st->sample_to_chunk_sz; i++) { + st->sample_to_chunk[i].first = _fd->readUint32BE(); + st->sample_to_chunk[i].count = _fd->readUint32BE(); + st->sample_to_chunk[i].id = _fd->readUint32BE(); + //printf ("Sample to Chunk[%d]: First = %d, Count = %d\n", i, st->sample_to_chunk[i].first, st->sample_to_chunk[i].count); + } + + return 0; +} + +int QuickTimeDecoder::readSTSS(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->keyframe_count = _fd->readUint32BE(); + + debug(0, "keyframe_count = %d", st->keyframe_count); + + st->keyframes = new uint32[st->keyframe_count]; + + if (!st->keyframes) + return -1; + + for (uint32 i = 0; i < st->keyframe_count; i++) { + st->keyframes[i] = _fd->readUint32BE(); + debug(6, "keyframes[%d] = %d", i, st->keyframes[i]); + + } + return 0; +} + +int QuickTimeDecoder::readSTSZ(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->sample_size = _fd->readUint32BE(); + st->sample_count = _fd->readUint32BE(); + + debug(5, "sample_size = %d sample_count = %d", st->sample_size, st->sample_count); + + if (st->sample_size) + return 0; // there isn't any table following + + st->sample_sizes = new uint32[st->sample_count]; + + if (!st->sample_sizes) + return -1; + + for(uint32 i = 0; i < st->sample_count; i++) { + st->sample_sizes[i] = _fd->readUint32BE(); + debug(6, "sample_sizes[%d] = %d", i, st->sample_sizes[i]); + } + + return 0; +} + +static uint32 ff_gcd(uint32 a, uint32 b) { + if(b) return ff_gcd(b, a%b); + else return a; +} + +int QuickTimeDecoder::readSTTS(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + uint32 duration = 0; + uint32 total_sample_count = 0; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->stts_count = _fd->readUint32BE(); + st->stts_data = new MOVstts[st->stts_count]; + + debug(0, "track[%i].stts.entries = %i", _numStreams - 1, st->stts_count); + + st->time_rate = 0; + + for (int32 i = 0; i < st->stts_count; i++) { + int sample_duration; + int sample_count; + + sample_count = _fd->readUint32BE(); + sample_duration = _fd->readUint32BE(); + st->stts_data[i].count = sample_count; + st->stts_data[i].duration = sample_duration; + + st->time_rate = ff_gcd(st->time_rate, sample_duration); + + debug(0, "sample_count=%d, sample_duration=%d", sample_count, sample_duration); + + duration += sample_duration * sample_count; + total_sample_count += sample_count; + } + + st->nb_frames = total_sample_count; + + if (duration) + st->duration = duration; + + return 0; +} + +int QuickTimeDecoder::readSTCO(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->chunk_count = _fd->readUint32BE(); + st->chunk_offsets = new uint32[st->chunk_count]; + + if (!st->chunk_offsets) + return -1; + + for (uint32 i = 0; i < st->chunk_count; i++) { + // WORKAROUND/HACK: The offsets in Riven videos (ones inside the Mohawk archives themselves) + // have offsets relative to the archive and not the video. This is quite nasty. We subtract + // the initial offset of the stream to get the correct value inside of the stream. + st->chunk_offsets[i] = _fd->readUint32BE() - _beginOffset; + } + + for (uint32 i = 0; i < _numStreams; i++) { + MOVStreamContext *sc2 = _streams[i]; + + if(sc2 && sc2->chunk_offsets){ + uint32 first = sc2->chunk_offsets[0]; + uint32 last = sc2->chunk_offsets[sc2->chunk_count - 1]; + + if(first >= st->chunk_offsets[st->chunk_count - 1] || last <= st->chunk_offsets[0]) + _ni = 1; + } + } + + return 0; +} + +int QuickTimeDecoder::readWAVE(MOVatom atom) { + if (_numStreams < 1) + return 0; + + MOVStreamContext *st = _streams[_numStreams - 1]; + + if (atom.size > (1 << 30)) + return -1; + + if (st->codec_tag == MKID_BE('QDM2')) // Read extradata for QDM2 + st->extradata = _fd->readStream(atom.size - 8); + else if (atom.size > 8) + return readDefault(atom); + else + _fd->skip(atom.size); + + return 0; +} + +void QuickTimeDecoder::close() { + stopAudio(); + + delete _videoCodec; _videoCodec = 0; + + for (uint32 i = 0; i < _numStreams; i++) + delete _streams[i]; + + delete _fd; + + if (_scaledSurface) { + _scaledSurface->free(); + delete _scaledSurface; + _scaledSurface = 0; + } + + // The audio stream is deleted automatically + _audStream = NULL; + + VideoDecoder::reset(); +} + +Common::SeekableReadStream *QuickTimeDecoder::getNextFramePacket() { + if (_videoStreamIndex < 0) + return NULL; + + // First, we have to track down which chunk holds the sample and which sample in the chunk contains the frame we are looking for. + int32 totalSampleCount = 0; + int32 sampleInChunk = 0; + int32 actualChunk = -1; + + for (uint32 i = 0; i < _streams[_videoStreamIndex]->chunk_count; i++) { + int32 sampleToChunkIndex = -1; + + for (uint32 j = 0; j < _streams[_videoStreamIndex]->sample_to_chunk_sz; j++) + if (i >= _streams[_videoStreamIndex]->sample_to_chunk[j].first - 1) + sampleToChunkIndex = j; + + if (sampleToChunkIndex < 0) + error("This chunk (%d) is imaginary", sampleToChunkIndex); + + totalSampleCount += _streams[_videoStreamIndex]->sample_to_chunk[sampleToChunkIndex].count; + + if (totalSampleCount > getCurFrame()) { + actualChunk = i; + sampleInChunk = _streams[_videoStreamIndex]->sample_to_chunk[sampleToChunkIndex].count - totalSampleCount + getCurFrame(); + break; + } + } + + if (actualChunk < 0) { + warning ("Could not find data for frame %d", getCurFrame()); + return NULL; + } + + // Next seek to that frame + _fd->seek(_streams[_videoStreamIndex]->chunk_offsets[actualChunk]); + + // Then, if the chunk holds more than one frame, seek to where the frame we want is located + for (int32 i = getCurFrame() - sampleInChunk; i < getCurFrame(); i++) { + if (_streams[_videoStreamIndex]->sample_size != 0) + _fd->skip(_streams[_videoStreamIndex]->sample_size); + else + _fd->skip(_streams[_videoStreamIndex]->sample_sizes[i]); + } + + // Finally, read in the raw data for the frame + //printf ("Frame Data[%d]: Offset = %d, Size = %d\n", getCurFrame(), _fd->pos(), _streams[_videoStreamIndex]->sample_sizes[getCurFrame()]); + + if (_streams[_videoStreamIndex]->sample_size != 0) + return _fd->readStream(_streams[_videoStreamIndex]->sample_size); + + return _fd->readStream(_streams[_videoStreamIndex]->sample_sizes[getCurFrame()]); +} + +bool QuickTimeDecoder::checkAudioCodecSupport(uint32 tag) { + // Check if the codec is a supported codec + if (tag == MKID_BE('twos') || tag == MKID_BE('raw ') || tag == MKID_BE('ima4')) + return true; + +#ifdef SOUND_QDM2_H + if (tag == MKID_BE('QDM2')) + return true; +#endif + + warning("Audio Codec Not Supported: \'%s\'", tag2str(tag)); + + return false; +} + +Audio::AudioStream *QuickTimeDecoder::createAudioStream(Common::SeekableReadStream *stream) { + if (!stream || _audioStreamIndex < 0) + return NULL; + + if (_streams[_audioStreamIndex]->codec_tag == MKID_BE('twos') || _streams[_audioStreamIndex]->codec_tag == MKID_BE('raw ')) { + // Fortunately, most of the audio used in Myst videos is raw... + uint16 flags = 0; + if (_streams[_audioStreamIndex]->codec_tag == MKID_BE('raw ')) + flags |= Audio::FLAG_UNSIGNED; + if (_streams[_audioStreamIndex]->channels == 2) + flags |= Audio::FLAG_STEREO; + if (_streams[_audioStreamIndex]->bits_per_sample == 16) + flags |= Audio::FLAG_16BITS; + uint32 dataSize = stream->size(); + byte *data = (byte *)malloc(dataSize); + stream->read(data, dataSize); + delete stream; + return Audio::makeRawStream(data, dataSize, _streams[_audioStreamIndex]->sample_rate, flags); + } else if (_streams[_audioStreamIndex]->codec_tag == MKID_BE('ima4')) { + // Riven uses this codec (as do some Myst ME videos) + return Audio::makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), Audio::kADPCMApple, _streams[_audioStreamIndex]->sample_rate, _streams[_audioStreamIndex]->channels, 34); +#ifdef SOUND_QDM2_H + } else if (_streams[_audioStreamIndex]->codec_tag == MKID_BE('QDM2')) { + // Several Myst ME videos use this codec + return new Audio::QDM2Stream(stream, _streams[_audioStreamIndex]->extradata); +#endif + } + + error("Unsupported audio codec"); + + return NULL; +} + +void QuickTimeDecoder::updateAudioBuffer() { + if (!_audStream) + return; + + // Keep three streams in buffer so that if/when the first two end, it goes right into the next + for (; _audStream->numQueuedStreams() < 3 && _curAudioChunk < _streams[_audioStreamIndex]->chunk_count; _curAudioChunk++) { + Common::MemoryWriteStreamDynamic *wStream = new Common::MemoryWriteStreamDynamic(); + + _fd->seek(_streams[_audioStreamIndex]->chunk_offsets[_curAudioChunk]); + + // First, we have to get the sample count + uint32 sampleCount = 0; + for (uint32 j = 0; j < _streams[_audioStreamIndex]->sample_to_chunk_sz; j++) + if (_curAudioChunk >= (_streams[_audioStreamIndex]->sample_to_chunk[j].first - 1)) + sampleCount = _streams[_audioStreamIndex]->sample_to_chunk[j].count; + assert(sampleCount); + + // Then calculate the right sizes + while (sampleCount > 0) { + uint32 samples = 0, size = 0; + + if (_streams[_audioStreamIndex]->samples_per_frame >= 160) { + samples = _streams[_audioStreamIndex]->samples_per_frame; + size = _streams[_audioStreamIndex]->bytes_per_frame; + } else if (_streams[_audioStreamIndex]->samples_per_frame > 1) { + samples = MIN<uint32>((1024 / _streams[_audioStreamIndex]->samples_per_frame) * _streams[_audioStreamIndex]->samples_per_frame, sampleCount); + size = (samples / _streams[_audioStreamIndex]->samples_per_frame) * _streams[_audioStreamIndex]->bytes_per_frame; + } else { + samples = MIN<uint32>(1024, sampleCount); + size = samples * _streams[_audioStreamIndex]->sample_size; + } + + // Now, we read in the data for this data and output it + byte *data = (byte *)malloc(size); + _fd->read(data, size); + wStream->write(data, size); + free(data); + sampleCount -= samples; + } + + // Now queue the buffer + _audStream->queueAudioStream(createAudioStream(new Common::MemoryReadStream(wStream->getData(), wStream->size(), DisposeAfterUse::YES))); + delete wStream; + } +} + +} // End of namespace Graphics diff --git a/graphics/video/qt_decoder.h b/graphics/video/qt_decoder.h new file mode 100644 index 0000000000..dc0557b388 --- /dev/null +++ b/graphics/video/qt_decoder.h @@ -0,0 +1,282 @@ +/* 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$ + * + */ + +// +// Heavily based on ffmpeg code. +// +// Copyright (c) 2001 Fabrice Bellard. +// First version by Francois Revol revol@free.fr +// Seek function by Gael Chardon gael.dev@4now.net +// + +#ifndef GRAPHICS_QT_DECODER_H +#define GRAPHICS_QT_DECODER_H + +#include "common/scummsys.h" +#include "common/queue.h" + +#include "graphics/video/video_decoder.h" +#include "graphics/video/codecs/codec.h" + +#include "sound/audiostream.h" +#include "sound/mixer.h" + +namespace Common { + class File; +} + +namespace Graphics { + +enum ScaleMode { + kScaleNormal = 1, + kScaleHalf = 2, + kScaleQuarter = 4 +}; + +class QuickTimeDecoder : public RewindableVideoDecoder { +public: + QuickTimeDecoder(); + virtual ~QuickTimeDecoder(); + + /** + * Returns the width of the video + * @return the width of the video + */ + uint16 getWidth() const; + + /** + * Returns the height of the video + * @return the height of the video + */ + uint16 getHeight() const; + + /** + * Returns the amount of frames in the video + * @return the amount of frames in the video + */ + uint32 getFrameCount() const; + + /** + * Load a QuickTime video file from a SeekableReadStream + * @param stream the stream to load + */ + bool load(Common::SeekableReadStream &stream); + + /** + * Close a QuickTime encoded video file + */ + void close(); + + /** + * Returns the palette of the video + * @return the palette of the video + */ + byte *getPalette() { _dirtyPalette = false; return _palette; } + bool hasDirtyPalette() const { return _dirtyPalette; } + + /** + * Set the beginning offset of the video so we can modify the offsets in the stco + * atom of videos inside the Mohawk archives + * @param the beginning offset of the video + */ + void setChunkBeginOffset(uint32 offset) { _beginOffset = offset; } + + bool isVideoLoaded() const { return _fd != 0; } + Surface *decodeNextFrame(); + bool needsUpdate() const; + bool endOfVideo() const; + uint32 getElapsedTime() const; + uint32 getTimeToNextFrame() const; + PixelFormat getPixelFormat() const; + + // RewindableVideoDecoder API + void rewind(); + + // TODO: This audio function need to be removed from the public and/or added to + // the VideoDecoder API directly. I plan on replacing this function with something + // that can figure out how much audio is needed instead of constantly keeping two + // chunks in memory. + void updateAudioBuffer(); + +protected: + // This is the file handle from which data is read from. It can be the actual file handle or a decompressed stream. + Common::SeekableReadStream *_fd; + + struct MOVatom { + uint32 type; + uint32 offset; + uint32 size; + }; + + struct ParseTable { + uint32 type; + int (QuickTimeDecoder::*func)(MOVatom atom); + }; + + struct MOVstts { + int count; + int duration; + }; + + struct MOVstsc { + uint32 first; + uint32 count; + uint32 id; + }; + + enum CodecType { + CODEC_TYPE_MOV_OTHER, + CODEC_TYPE_VIDEO, + CODEC_TYPE_AUDIO + }; + + struct MOVStreamContext { + MOVStreamContext() { memset(this, 0, sizeof(MOVStreamContext)); } + ~MOVStreamContext() { + delete[] chunk_offsets; + delete[] stts_data; + delete[] ctts_data; + delete[] sample_to_chunk; + delete[] sample_sizes; + delete[] keyframes; + delete extradata; + } + + int ffindex; /* the ffmpeg stream id */ + int is_ff_stream; /* Is this stream presented to ffmpeg ? i.e. is this an audio or video stream ? */ + uint32 next_chunk; + uint32 chunk_count; + uint32 *chunk_offsets; + int stts_count; + MOVstts *stts_data; + int ctts_count; + MOVstts *ctts_data; + int edit_count; /* number of 'edit' (elst atom) */ + uint32 sample_to_chunk_sz; + MOVstsc *sample_to_chunk; + int32 sample_to_chunk_index; + int sample_to_time_index; + uint32 sample_to_time_sample; + uint32 sample_to_time_time; + int sample_to_ctime_index; + int sample_to_ctime_sample; + uint32 sample_size; + uint32 sample_count; + uint32 *sample_sizes; + uint32 keyframe_count; + uint32 *keyframes; + int32 time_scale; + int time_rate; + uint32 current_sample; + uint32 left_in_chunk; /* how many samples before next chunk */ + + uint16 width; + uint16 height; + int codec_type; + uint32 codec_tag; + char codec_name[32]; + uint16 bits_per_sample; + uint16 color_table_id; + bool palettized; + Common::SeekableReadStream *extradata; + + uint16 stsd_version; + uint16 channels; + uint16 sample_rate; + uint32 samples_per_frame; + uint32 bytes_per_frame; + + uint32 nb_frames; + uint32 duration; + uint32 start_time; + ScaleMode scaleMode; + }; + + const ParseTable *_parseTable; + bool _foundMOOV; + bool _foundMDAT; + uint32 _timeScale; + uint32 _duration; + uint32 _mdatOffset; + uint32 _mdatSize; + uint32 _next_chunk_offset; + MOVStreamContext *_partial; + uint32 _numStreams; + int _ni; + ScaleMode _scaleMode; + MOVStreamContext *_streams[20]; + byte _palette[256 * 3]; + bool _dirtyPalette; + uint32 _beginOffset; + + void initParseTable(); + Audio::AudioStream *createAudioStream(Common::SeekableReadStream *stream); + bool checkAudioCodecSupport(uint32 tag); + Common::SeekableReadStream *getNextFramePacket(); + uint32 getFrameDuration(); + uint32 getCodecTag(); + byte getBitsPerPixel(); + + Audio::QueuingAudioStream *_audStream; + void startAudio(); + void stopAudio(); + int8 _audioStreamIndex; + uint _curAudioChunk; + Audio::SoundHandle _audHandle; + + Codec *createCodec(uint32 codecTag, byte bitsPerPixel); + Codec *_videoCodec; + uint32 _nextFrameStartTime; + int8 _videoStreamIndex; + + Surface *_scaledSurface; + Surface *scaleSurface(Surface *frame); + ScaleMode getScaleMode() const; + + void pauseVideoIntern(bool pause); + + int readDefault(MOVatom atom); + int readLeaf(MOVatom atom); + int readELST(MOVatom atom); + int readHDLR(MOVatom atom); + int readMDAT(MOVatom atom); + int readMDHD(MOVatom atom); + int readMOOV(MOVatom atom); + int readMVHD(MOVatom atom); + int readTKHD(MOVatom atom); + int readTRAK(MOVatom atom); + int readSTCO(MOVatom atom); + int readSTSC(MOVatom atom); + int readSTSD(MOVatom atom); + int readSTSS(MOVatom atom); + int readSTSZ(MOVatom atom); + int readSTTS(MOVatom atom); + int readCMOV(MOVatom atom); + int readWAVE(MOVatom atom); +}; + +} // End of namespace Graphics + +#endif |