mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-04 08:17:40 +00:00
614 lines
16 KiB
C++
614 lines
16 KiB
C++
/* 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 {
|
|
|
|
enum frameTypes {
|
|
NOP = 0, // nop
|
|
// 1 - old initialisation data?
|
|
PALETTE = 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
|
|
};
|
|
|
|
PacoDecoder::PacoDecoder()
|
|
: _fileStream(nullptr), _videoTrack(nullptr), _audioTrack(nullptr) {
|
|
}
|
|
|
|
PacoDecoder::~PacoDecoder() {
|
|
close();
|
|
}
|
|
|
|
void PacoDecoder::PacoDecoder::close() {
|
|
Video::VideoDecoder::close();
|
|
|
|
delete _fileStream;
|
|
_fileStream = nullptr;
|
|
_videoTrack = nullptr;
|
|
_audioTrack = nullptr;
|
|
}
|
|
|
|
bool PacoDecoder::loadStream(Common::SeekableReadStream *stream) {
|
|
close();
|
|
|
|
_curFrame = 0;
|
|
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
|
|
if (frameRate == 0) {
|
|
// 0 is equivalent to playing back at the fastest rate
|
|
frameRate = 60;
|
|
}
|
|
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
|
|
|
|
for (uint i = 0; i < frameCount; i++) {
|
|
_frameSizes[i] = stream->readUint32BE();
|
|
}
|
|
|
|
_fileStream = stream;
|
|
|
|
_videoTrack = new PacoVideoTrack(frameRate, frameCount, width, height);
|
|
addTrack(_videoTrack);
|
|
if (hasAudio) {
|
|
_audioTrack = new PacoAudioTrack(getAudioSamplingRate());
|
|
addTrack(_audioTrack);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int PacoDecoder::getAudioSamplingRate() {
|
|
/**
|
|
* SamplingRate is found inside the audio packets
|
|
* Search for the first audio packet and use it.
|
|
*/
|
|
const Common::Array<int> samplingRates = {5563, 7418, 11127, 22254};
|
|
int index = 0;
|
|
|
|
int64 startPos = _fileStream->pos();
|
|
|
|
while (_fileStream->pos() < _fileStream->size()) {
|
|
int64 currentPos = _fileStream->pos();
|
|
int frameType = _fileStream->readByte();
|
|
int v = _fileStream->readByte();
|
|
uint32 chunkSize = (v << 16 ) | _fileStream->readUint16BE();
|
|
if (frameType != AUDIO) {
|
|
_fileStream->seek(currentPos + chunkSize);
|
|
continue;
|
|
}
|
|
uint16 header = _fileStream->readUint16BE();
|
|
_fileStream->readUint16BE();
|
|
index = (header >> 10) & 7;
|
|
break;
|
|
}
|
|
_fileStream->seek(startPos);
|
|
|
|
return samplingRates[index];
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
const byte* PacoDecoder::getPalette(){
|
|
Track *track = getTrack(0);
|
|
|
|
if (track)
|
|
return ((PacoVideoTrack *)track)->getPalette();
|
|
return nullptr;
|
|
}
|
|
|
|
const byte* PacoDecoder::PacoVideoTrack::getPalette() const {
|
|
_dirtyPalette = false;
|
|
return _palette;
|
|
}
|
|
|
|
PacoDecoder::PacoVideoTrack::PacoVideoTrack(
|
|
uint16 frameRate, uint16 frameCount, uint16 width, uint16 height) {
|
|
_curFrame = 0;
|
|
_frameRate = frameRate;
|
|
_frameCount = frameCount;
|
|
|
|
_surface = new Graphics::Surface();
|
|
_surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
|
|
_palette = const_cast<byte *>(quickTimeDefaultPalette256);
|
|
_dirtyPalette = true;
|
|
}
|
|
|
|
PacoDecoder::PacoVideoTrack::~PacoVideoTrack() {
|
|
_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;
|
|
}
|
|
|
|
void PacoDecoder::readNextPacket() {
|
|
if (_curFrame >= _videoTrack->getFrameCount())
|
|
return;
|
|
|
|
uint32 nextFrame = _fileStream->pos() + _frameSizes[_curFrame];
|
|
|
|
debug(2, " frame %3d size %d @ %lX", _curFrame, _frameSizes[_curFrame], long(_fileStream->pos()));
|
|
|
|
_curFrame++;
|
|
|
|
while (_fileStream->pos() < nextFrame) {
|
|
int64 currentPos = _fileStream->pos();
|
|
int frameType = _fileStream->readByte();
|
|
int v = _fileStream->readByte();
|
|
uint32 chunkSize = (v << 16 ) | _fileStream->readUint16BE();
|
|
debug(2, " slot type %d size %d @ %lX", frameType, chunkSize, long(_fileStream->pos() - 4));
|
|
|
|
switch (frameType) {
|
|
case AUDIO:
|
|
if (_audioTrack)
|
|
_audioTrack->queueSound(_fileStream, chunkSize - 4);
|
|
break;
|
|
case VIDEO:
|
|
if (_videoTrack)
|
|
_videoTrack->handleFrame(_fileStream, chunkSize - 4, _curFrame);
|
|
break;
|
|
case PALETTE:
|
|
if (_videoTrack)
|
|
_videoTrack->handlePalette(_fileStream);
|
|
break;
|
|
case EOC:
|
|
if (_videoTrack)
|
|
_videoTrack->handleEOC();
|
|
break;
|
|
case NOP:
|
|
break;
|
|
default:
|
|
error("PacoDecoder::decodeFrame(): unknown main chunk type (type = 0x%02X)", frameType);
|
|
break;
|
|
}
|
|
_fileStream->seek(currentPos + chunkSize);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
const Graphics::Surface *PacoDecoder::PacoVideoTrack::decodeNextFrame() {
|
|
return _surface;
|
|
}
|
|
|
|
void PacoDecoder::PacoVideoTrack::handlePalette(Common::SeekableReadStream *fileStream) {
|
|
uint32 header = fileStream->readUint32BE();
|
|
if (header == 0x30000000) { // default quicktime palette
|
|
_palette = const_cast<byte *>(quickTimeDefaultPalette256);
|
|
} else {
|
|
fileStream->readUint32BE(); // 4 bytes of 00
|
|
_palette = new byte[256 * 3]();
|
|
for (int i = 0; i < 256 * 3; i++){
|
|
_palette[i] = fileStream->readByte();
|
|
}
|
|
}
|
|
_dirtyPalette = true;
|
|
}
|
|
|
|
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(Common::SeekableReadStream *fileStream, uint32 chunkSize, int curFrame) {
|
|
_curFrame = curFrame;
|
|
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(5, " +%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(5, "pos: %ld", long(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(5, "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(5, " 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(5, "PacoDecoder::PacoVideoTrack::handleFrame: Long op: 0x0 op %d", op);
|
|
}
|
|
|
|
} else if (c < 128) { // copy the same amount of pixels
|
|
debug(5, " copy pixels: %d", c);
|
|
while (c--)
|
|
PUTPIX(*src++);
|
|
} else if (c < 254) { // repeat the following value 256 - op times
|
|
debug(5, " 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(5, "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(5, "debug info compact: op: %d", op);
|
|
switch (op) {
|
|
case COPY:
|
|
debug(5, "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(5, "debug info RLE: %d", len);
|
|
c = *src++;
|
|
while (len--)
|
|
PUTPIX(clrs[c & 0xF]);
|
|
break;
|
|
case PRLE:
|
|
debug(5, "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(5, "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(5, "debug info SKIP: %d", len);
|
|
while (len--)
|
|
SKIP();
|
|
break;
|
|
case ENDCURRENTLINE:
|
|
debug(5, "debug info ENDCURRENTLINE: %d", len);
|
|
xpos = x + bw;
|
|
ypos += len;
|
|
break;
|
|
default:
|
|
warning("PacoDecoder::PacoVideoTrack::handleFrame: Compact RLE mode: 0x0 op %d", op);
|
|
}
|
|
} else if (op < 8) { // copy 1-7 colors
|
|
debug(5, "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(5, "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(5, "debug info Skip number of pixels: %d", len);
|
|
while (len--)
|
|
SKIP();
|
|
} else {
|
|
if (len < 8) { // Pair run
|
|
debug(5, "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(5, "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(5, "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(5, "debug info PAIR RUN: %d", c);
|
|
|
|
c1 = *src++;
|
|
c2 = *src++;
|
|
while (c--) {
|
|
PUTPIX(c1);
|
|
PUTPIX(c2);
|
|
}
|
|
} else { // quad run
|
|
debug(5, "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(5, "!!!");
|
|
if (xpos >= x + bw) {
|
|
debug(5, "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, x + bw, y + bh));
|
|
delete[] fdata;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
PacoDecoder::PacoAudioTrack::PacoAudioTrack(int samplingRate)
|
|
: AudioTrack(Audio::Mixer::kPlainSoundType) {
|
|
_samplingRate = samplingRate;
|
|
byte audioFlags = Audio::FLAG_UNSIGNED;
|
|
_packetStream = Audio::makePacketizedRawStream(samplingRate, audioFlags);
|
|
}
|
|
|
|
PacoDecoder::PacoAudioTrack::~PacoAudioTrack() {
|
|
delete _packetStream;
|
|
}
|
|
|
|
void PacoDecoder::PacoAudioTrack::queueSound(Common::SeekableReadStream *fileStream, uint32 chunkSize) {
|
|
const Common::Array<int> samplingRates = {5563, 7418, 11127, 22254};
|
|
uint16 header = fileStream->readUint16BE();
|
|
fileStream->readUint16BE();
|
|
int index = (header >> 10) & 7;
|
|
int currentRate = samplingRates[index];
|
|
if ( currentRate != _samplingRate)
|
|
warning("PacoDecoder::PacoAudioTrack: Sampling rate differs from first frame: %i != %i", currentRate, _samplingRate);
|
|
|
|
_packetStream->queuePacket(fileStream->readStream(chunkSize - 4));
|
|
}
|
|
|
|
|
|
} // End of namespace Video
|