diff --git a/video/module.mk b/video/module.mk index 8f6ce8af839..797aab5b7ea 100644 --- a/video/module.mk +++ b/video/module.mk @@ -9,6 +9,7 @@ MODULE_OBJS := \ hnm_decoder.o \ mpegps_decoder.o \ mve_decoder.o \ + paco_decoder.o \ psx_decoder.o \ qt_decoder.o \ smk_decoder.o \ diff --git a/video/paco_decoder.cpp b/video/paco_decoder.cpp new file mode 100644 index 00000000000..0e90dd39371 --- /dev/null +++ b/video/paco_decoder.cpp @@ -0,0 +1,505 @@ +/* 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 3 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, see . + * + */ + +#include "common/endian.h" +#include "common/rect.h" +#include "common/stream.h" +#include "common/system.h" +#include "common/textconsole.h" +#include "graphics/surface.h" +#include "video/qt_data.h" +#include "video/paco_decoder.h" + + +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" + +namespace Video { + +PacoDecoder::PacoDecoder() { +} + +PacoDecoder::~PacoDecoder() { + close(); +} + +bool PacoDecoder::loadStream(Common::SeekableReadStream *stream) { + close(); + + stream->readUint16BE(); // 1 + stream->readUint16BE(); // 0x26 + + uint16 width = stream->readUint16BE(); + uint16 height = stream->readUint16BE(); + int16 frameRate = stream->readUint16BE(); + frameRate = ABS(frameRate); // Negative framerate is indicative of audio, but not always + uint16 flags = stream->readUint16BE(); + bool hasAudio = (flags & 0x100) == 0x100; + + stream->readUint32BE(); // maxChunksize + stream->readUint32BE(); // always 0 + stream->readUint32BE(); // audio related flags + uint16 frameCount = stream->readUint16BE(); + stream->readUint16BE(); // copy of frameCount + stream->readUint16BE(); // always 8? + stream->readUint16BE(); // always 0x600? + stream->readUint32BE(); // flags + stream->readUint16BE(); // 0 + + addTrack(new PacoVideoTrack(stream, frameRate, frameCount, hasAudio, width, height)); + return true; +} + +const Common::List *PacoDecoder::getDirtyRects() const { + const Track *track = getTrack(0); + + if (track) + return ((const PacoVideoTrack *)track)->getDirtyRects(); + + return 0; +} + +void PacoDecoder::clearDirtyRects() { + Track *track = getTrack(0); + + if (track) + ((PacoVideoTrack *)track)->clearDirtyRects(); +} + +void PacoDecoder::copyDirtyRectsToBuffer(uint8 *dst, uint pitch) { + Track *track = getTrack(0); + + if (track) + ((PacoVideoTrack *)track)->copyDirtyRectsToBuffer(dst, pitch); +} + +PacoDecoder::PacoVideoTrack::PacoVideoTrack( + Common::SeekableReadStream *stream, uint16 frameRate, uint16 frameCount, bool hasAudio, uint16 width, uint16 height) { + + _fileStream = stream; + _frameDelay = 1000 / frameRate; // frameRate is in fps, ms per frame + _frameCount = frameCount; + + _surface = new Graphics::Surface(); + _surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + _palette = new byte[3 * 256](); + memcpy(_palette, quickTimeDefaultPalette256, 256 * 3); + _dirtyPalette = false; + + _curFrame = 0; + _nextFrameStartTime = 0; + + for (uint i = 0; i < _frameCount; i++) { + _frameSizes[i] = stream->readUint32BE(); + } +} + +PacoDecoder::PacoVideoTrack::~PacoVideoTrack() { + delete _fileStream; + delete[] _palette; + + _surface->free(); + delete _surface; +} + +bool PacoDecoder::PacoVideoTrack::endOfTrack() const { + return getCurFrame() >= getFrameCount(); +} + +uint16 PacoDecoder::PacoVideoTrack::getWidth() const { + return _surface->w; +} + +uint16 PacoDecoder::PacoVideoTrack::getHeight() const { + return _surface->h; +} + +Graphics::PixelFormat PacoDecoder::PacoVideoTrack::getPixelFormat() const { + return _surface->format; +} + + +enum frameTypes { + NOP = 0, // nop + // 1 - old initialisation data? + PALLETE = 2, // - new initialisation data (usually 0x30 0x00 0x00 ... meaning 8-bit with default QuickTime palette) + DELAY = 3, // - delay information + AUDIO = 4, // - audio data (8-bit unsigned PCM) + // 5 - should not be present + // 6 - should not be present + // 7 - unknown + VIDEO = 8, // - video frame + // 9 - unknown + // 10 - dummy? + EOC = 11 // - end of chunk marker +}; + +const Graphics::Surface *PacoDecoder::PacoVideoTrack::decodeNextFrame() { + uint32 nextFrame = _fileStream->pos() + _frameSizes[_curFrame]; + + debug(" frame %3d size %d @ %lX", _curFrame, _frameSizes[_curFrame], _fileStream->pos()); + + while (_fileStream->pos() < nextFrame) { + int64 currentPos = _fileStream->pos(); + int frameType = _fileStream->readByte(); + int v = _fileStream->readByte(); + uint32 chunkSize = (v << 16 ) | _fileStream->readUint16BE(); + debug(" slot type %d size %d @ %lX", frameType, chunkSize, _fileStream->pos() - 4); + + switch (frameType) { + case AUDIO: + warning("PacoDecode::decodeFrame(): Audio not implemented"); + break; + case VIDEO: + handleFrame(chunkSize - 4); + break; + case PALLETE: + warning("PacoDecode::decodeFrame(): Palette not implemented"); + break; + case EOC: + break; + default: + error("PacoDecoder::decodeFrame(): unknown main chunk type (type = 0x%02X)", frameType); + break; + } + _fileStream->seek(currentPos + chunkSize); + } + + _curFrame++; + _nextFrameStartTime += _frameDelay; + + return _surface; +} + +enum { + COPY = 0, // raw copy pixels + RLE, // RLE + PRLE, // pair RLE (read a pair of pixels and repeat it the specified number of times) + QRLE, // quad RLE (read four pixels and repeat it the specified number of times) + SKIP, // skip + ENDCURRENTLINE, // end current line and skip additional len lines + EOFRAME = 15 // not real opcode but 00 F0 00 00 is often seen at the end of frame and can serve as an end marker +}; + +#define ADJUST_LINE \ + if (compr == 1) \ + ypos2 = ypos; \ + else { \ + ypos2 = ypos * 2 - y; \ + if (ypos2 >= y + bh) { \ + ypos2 -= bh; \ + if (!(bh & 1)) \ + ypos2++; \ + } \ + } + +#define PUTPIX(pix) \ + do { \ + *dst++ = pix; \ + xpos++; \ + } while(0); + +#define SKIP() \ + do { \ + dst++; \ + xpos++; \ + } while(0); + + +void PacoDecoder::PacoVideoTrack::handleFrame(uint32 chunkSize) { + uint16 w = getWidth(); + + uint16 x = _fileStream->readUint16BE(); // x offset of the updated area + uint16 y = _fileStream->readUint16BE(); // y offset of the updated area + uint16 bw = _fileStream->readUint16BE(); // updated area width + uint16 bh = _fileStream->readUint16BE(); // updated area height + uint compr = _fileStream->readByte(); // compression method and flags + _fileStream->readByte(); // padding + + debug(" +%d,%d - %dx%d compr %X", x, y, bw, bh, compr); + + compr = compr & 0xF; + + uint8 *fdata = new uint8[1048576]; // 0x100000 copied from original pacodec + _fileStream->read(fdata, chunkSize - 10); // remove header length + debug("pos: %ld", _fileStream->pos()); + int16 xpos = x, ypos = y, ypos2 = y; + byte *dst = (byte *)_surface->getPixels() + x + y * w; + + const uint8 *src = fdata; + int16 i, c, c1, c2, c3, c4; + uint8 clrs[16]; + + + while (ypos < y + bh) { + c = *src++; + debug("debug info: ypos %d y %d bh %d src: %d", ypos, y, bh, c); + + if (c == 0 ){ // long operation + int16 op = src[0] >> 4; + int16 len = ((src[0] & 0xF) << 8) | src[1]; + src += 2; + debug(" long operation: opcode: %d", op); + switch (op) { + case COPY: + while (len--) + PUTPIX(*src++); + break; + case RLE: + c1 = *src++; + while (len--) + PUTPIX(c1); + break; + case PRLE: + c1 = *src++; + c2 = *src++; + while (len--){ + PUTPIX(c1); + PUTPIX(c2); + } + break; + case QRLE: + c1 = *src++; + c2 = *src++; + c3 = *src++; + c4 = *src++; + while (len--) { + PUTPIX(c1); + PUTPIX(c2); + PUTPIX(c3); + PUTPIX(c4); + } + break; + case SKIP: + while (len--) + SKIP(); + break; + case ENDCURRENTLINE: + xpos = x; + ypos += len + 1; + ADJUST_LINE; + dst = (byte *)_surface->getPixels() + xpos + ypos2 * w; + break; + case EOFRAME: + xpos = x; + ypos = y + bh; + break; + default: + PUTPIX(0xFF); + debug("PacoDecoder::PacoVideoTrack::handleFrame: Long op: 0x0 op %d", op); + } + + } else if (c < 128) { // copy the same amount of pixels + debug(" copy pixels: %d", c); + while (c--) + PUTPIX(*src++); + } else if (c < 254) { // repeat the following value 256 - op times + debug(" copy pixels -op: %d", 256 - c); + c1 = *src++; + c = 256 - c; + while (c--) + PUTPIX(c1); + } else if (c < 255) { + // next byte is either the number of pixels to skip (if non-zero) or + // a signal of compact RLE mode + c = *src++; + + if (!c) { // compact RLE mode + unsigned mask = (src[0] << 8) | src[1]; + src += 2; + debug("debug info compact RLE: c: %d mask: %d", c, mask); + + for (i = 0; i < 16; i++, mask >>= 1) { + if (mask & 1) + clrs[i] = *src++; + } + while (xpos < x + bw) { + int16 op = *src++; + int16 len = op & 0xF; + op >>= 4; + if (op == 0) { // low nibble.... + op = len; + len = *src++; + debug("debug info compact: op: %d", op); + switch (op) { + case COPY: + debug("debug info COPY: %d", len); + while (len--) { + c = *src++; + PUTPIX(clrs[c >> 4]); + if (!len) + break; + len--; + PUTPIX(clrs[c & 0xF]); + } + break; + case RLE: + debug("debug info RLE: %d", len); + c = *src++; + while (len--) + PUTPIX(clrs[c & 0xF]); + break; + case PRLE: + debug("debug info PRLE: %d", len); + c = *src++; + c1 = clrs[c >> 4]; + c2 = clrs[c & 0xF]; + while (len--) { + PUTPIX(c1); + PUTPIX(c2); + } + break; + case QRLE: + debug("debug info QRLE: %d", len); + c = *src++; + c1 = clrs[c >> 4]; + c2 = clrs[c & 0xF]; + c = *src++; + c3 = clrs[c >> 4]; + c4 = clrs[c & 0xF]; + while (len--) { + PUTPIX(c1); + PUTPIX(c2); + PUTPIX(c3); + PUTPIX(c4); + } + break; + case SKIP: + debug("debug info SKIP: %d", len); + while (len--) + SKIP(); + break; + case ENDCURRENTLINE: + debug("debug info ENDCURRENTLINE: %d", len); + xpos = x + bw; + break; + default: + warning("PacoDecoder::PacoVideoTrack::handleFrame: Compact RLE mode: 0x0 op %d", op); + } + } else if (op < 8) { // copy 1-7 colors + debug("debug info copy 1-7 colors: %d", len); + PUTPIX(clrs[len]); + op--; + while (op--) { + c = *src++; + PUTPIX(clrs[c >> 4]); + if (!op) + break; + op--; + PUTPIX(clrs[c & 0xF]); + } + } else if (op < 14) { // repeat color + debug("debug info Repeat color: %d", len); + op = 16 - op; + while (op--) + PUTPIX(clrs[len]); + } else if (op < 15) { // skip number of pixels in low nibbel + debug("debug info Skip number of pixels: %d", len); + while (len--) + SKIP(); + } else { + if (len < 8) { // Pair run + debug("debug info pair run: %d", len); + c = *src++; + c1 = clrs[c >> 4]; + c2 = clrs[c & 0xF]; + while (len--) { + PUTPIX(c1); + PUTPIX(c2); + } + } else { // Quad run + debug("debug info quad run: %d", len); + len = 16 - len; + c = *src++; + c1 = clrs[c >> 4]; + c2 = clrs[c & 0xF]; + c = *src++; + c3 = clrs[c >> 4]; + c4 = clrs[c & 0xF]; + while (len--) { + PUTPIX(c1); + PUTPIX(c2); + PUTPIX(c3); + PUTPIX(c4); + } + } + } + } + } else { + debug("debug info SKIP: %d", c); + while (c--) + SKIP(); + } + } else { + // pair or quad run. Read the next byte and if it is below 128 then read and + // repeat a pair of pixels len times, otherwise read and repeat four pixels + // (but 256 - len times) + c = *src++; + if (c < 128) { // pair run + debug("debug info PAIR RUN: %d", c); + + c1 = *src++; + c2 = *src++; + while (c--) { + PUTPIX(c1); + PUTPIX(c2); + } + } else { // quad run + debug("debug info QUAD RUN: %d", c); + c = 256 - c; + c1 = *src++; + c2 = *src++; + c3 = *src++; + c4 = *src++; + while (c--) { + PUTPIX(c1); + PUTPIX(c2); + PUTPIX(c3); + PUTPIX(c4); + } + } + } + if (xpos > x + bw) debug("!!!"); + if (xpos >= x + bw) { + debug("debug info ADJUST LINE"); + xpos = x; + ypos++; + ADJUST_LINE; + dst = (byte *)_surface->getPixels() + x + ypos2 * w; + } + } + + _dirtyRects.clear(); + _dirtyRects.push_back(Common::Rect(x, y, bw, bh)); + +} + +void PacoDecoder::PacoVideoTrack::copyDirtyRectsToBuffer(uint8 *dst, uint pitch) { + for (Common::List::const_iterator it = _dirtyRects.begin(); it != _dirtyRects.end(); ++it) { + for (int y = (*it).top; y < (*it).bottom; ++y) { + const int x = (*it).left; + memcpy(dst + y * pitch + x, (byte *)_surface->getBasePtr(x, y), (*it).right - x); + } + } + clearDirtyRects(); +} + +} // End of namespace Video diff --git a/video/paco_decoder.h b/video/paco_decoder.h new file mode 100644 index 00000000000..7ac0b51aa1a --- /dev/null +++ b/video/paco_decoder.h @@ -0,0 +1,103 @@ +/* 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 3 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, see . + * + */ + +#ifndef VIDEO_PACODECODER_H +#define VIDEO_PACODECODER_H + +#include "video/video_decoder.h" +#include "common/list.h" +#include "common/rect.h" + +namespace Common { +class SeekableReadStream; +} + +namespace Graphics { +struct PixelFormat; +struct Surface; +} + +namespace Video { + +/** + * Decoder for PACo videos. + * + * Video decoder used in engines: + * - director + */ +class PacoDecoder : public VideoDecoder { +public: + PacoDecoder(); + virtual ~PacoDecoder(); + + virtual bool loadStream(Common::SeekableReadStream *stream); + + const Common::List *getDirtyRects() const; + void clearDirtyRects(); + void copyDirtyRectsToBuffer(uint8 *dst, uint pitch); + +protected: + class PacoVideoTrack : public VideoTrack { + public: + PacoVideoTrack( + Common::SeekableReadStream *stream, uint16 frameRate, uint16 frameCount, + bool hasAudio, uint16 width, uint16 height); + ~PacoVideoTrack(); + + bool endOfTrack() const; + virtual bool isRewindable() const { return false; } + + uint16 getWidth() const; + uint16 getHeight() const; + Graphics::PixelFormat getPixelFormat() const; + int getCurFrame() const { return _curFrame; } + int getFrameCount() const { return _frameCount; } + uint32 getNextFrameStartTime() const { return _nextFrameStartTime; } + virtual const Graphics::Surface *decodeNextFrame(); + virtual void handleFrame(uint32 chunkSize); + const byte *getPalette() const { return _palette; } + bool hasDirtyPalette() const { return false; } + + const Common::List *getDirtyRects() const { return &_dirtyRects; } + void clearDirtyRects() { _dirtyRects.clear(); } + void copyDirtyRectsToBuffer(uint8 *dst, uint pitch); + + protected: + Common::SeekableReadStream *_fileStream; + Graphics::Surface *_surface; + + int _curFrame; + + byte *_palette; + int _frameSizes[65536]; // can be done differently? + mutable bool _dirtyPalette; + + uint32 _frameCount; + uint32 _frameDelay; + uint32 _nextFrameStartTime; + + Common::List _dirtyRects; + }; +}; + +} // End of namespace Video + +#endif