[Scummvm-git-logs] scummvm master -> 24b7267b8beade20342822e3e29484590758beb4

rvanlaar noreply at scummvm.org
Mon Feb 21 18:17:23 UTC 2022


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
24b7267b8b VIDEO: Implement decoder for PACo files


Commit: 24b7267b8beade20342822e3e29484590758beb4
    https://github.com/scummvm/scummvm/commit/24b7267b8beade20342822e3e29484590758beb4
Author: Roland van Laar (roland at rolandvanlaar.nl)
Date: 2022-02-21T19:11:28+01:00

Commit Message:
VIDEO: Implement decoder for PACo files

The code decodes PACo video frames.
Other features, such as audio and palette changes are detected
but not handled.

Format documentation: https://wiki.multimedia.cx/index.php?title=PACo
Thanks to Kostya for reverse engineering the format and the original C
code.

Changed paths:
  A video/paco_decoder.cpp
  A video/paco_decoder.h
    video/module.mk


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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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<Common::Rect> *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<Common::Rect>::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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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<Common::Rect> *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<Common::Rect> *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<Common::Rect> _dirtyRects;
+	};
+};
+
+} // End of namespace Video
+
+#endif




More information about the Scummvm-git-logs mailing list