mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-13 21:31:53 +00:00
8bc861cb1a
This emulates the "slow" render mode of the Smacker lib, which returns the blocks changed each frame instead of full frames. Nightlong relies on this to correctly refresh its screen buffer.
973 lines
26 KiB
C++
973 lines
26 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 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.
|
|
*
|
|
*/
|
|
|
|
// Based on http://wiki.multimedia.cx/index.php?title=Smacker
|
|
// and the FFmpeg Smacker decoder (libavcodec/smacker.c), revision 16143
|
|
// http://git.ffmpeg.org/?p=ffmpeg;a=blob;f=libavcodec/smacker.c;hb=b8437a00a2f14d4a437346455d624241d726128e
|
|
|
|
#include "video/smk_decoder.h"
|
|
|
|
#include "common/endian.h"
|
|
#include "common/util.h"
|
|
#include "common/stream.h"
|
|
#include "common/bitarray.h"
|
|
#include "common/bitstream.h"
|
|
#include "common/system.h"
|
|
#include "common/textconsole.h"
|
|
|
|
#include "audio/audiostream.h"
|
|
#include "audio/mixer.h"
|
|
#include "audio/decoders/raw.h"
|
|
|
|
namespace Video {
|
|
|
|
enum SmkBlockTypes {
|
|
SMK_BLOCK_MONO = 0,
|
|
SMK_BLOCK_FULL = 1,
|
|
SMK_BLOCK_SKIP = 2,
|
|
SMK_BLOCK_FILL = 3
|
|
};
|
|
|
|
/*
|
|
* class SmallHuffmanTree
|
|
* A Huffman-tree to hold 8-bit values.
|
|
*/
|
|
|
|
class SmallHuffmanTree {
|
|
public:
|
|
SmallHuffmanTree(Common::BitStreamMemory8LSB &bs);
|
|
|
|
uint16 getCode(Common::BitStreamMemory8LSB &bs);
|
|
private:
|
|
enum {
|
|
SMK_NODE = 0x8000
|
|
};
|
|
|
|
uint16 decodeTree(uint32 prefix, int length);
|
|
|
|
uint16 _treeSize;
|
|
uint16 _tree[511];
|
|
|
|
uint16 _prefixtree[256];
|
|
byte _prefixlength[256];
|
|
|
|
Common::BitStreamMemory8LSB &_bs;
|
|
bool _empty;
|
|
};
|
|
|
|
SmallHuffmanTree::SmallHuffmanTree(Common::BitStreamMemory8LSB &bs)
|
|
: _treeSize(0), _bs(bs), _empty(false) {
|
|
if (!_bs.getBit()) {
|
|
_empty = true;
|
|
return;
|
|
}
|
|
|
|
for (uint16 i = 0; i < 256; ++i)
|
|
_prefixtree[i] = _prefixlength[i] = 0;
|
|
|
|
decodeTree(0, 0);
|
|
|
|
(void)_bs.getBit();
|
|
}
|
|
|
|
uint16 SmallHuffmanTree::decodeTree(uint32 prefix, int length) {
|
|
if (_empty)
|
|
return 0;
|
|
|
|
if (!_bs.getBit()) { // Leaf
|
|
_tree[_treeSize] = _bs.getBits(8);
|
|
|
|
if (length <= 8) {
|
|
for (int i = 0; i < 256; i += (1 << length)) {
|
|
_prefixtree[prefix | i] = _treeSize;
|
|
_prefixlength[prefix | i] = length;
|
|
}
|
|
}
|
|
++_treeSize;
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint16 t = _treeSize++;
|
|
|
|
if (length == 8) {
|
|
_prefixtree[prefix] = t;
|
|
_prefixlength[prefix] = 8;
|
|
}
|
|
|
|
uint16 r1 = decodeTree(prefix, length + 1);
|
|
|
|
_tree[t] = (SMK_NODE | r1);
|
|
|
|
uint16 r2 = decodeTree(prefix | (1 << length), length + 1);
|
|
|
|
return r1+r2+1;
|
|
}
|
|
|
|
uint16 SmallHuffmanTree::getCode(Common::BitStreamMemory8LSB &bs) {
|
|
if (_empty)
|
|
return 0;
|
|
|
|
byte peek = bs.peekBits(MIN<uint32>(bs.size() - bs.pos(), 8));
|
|
uint16 *p = &_tree[_prefixtree[peek]];
|
|
bs.skip(_prefixlength[peek]);
|
|
|
|
while (*p & SMK_NODE) {
|
|
if (bs.getBit())
|
|
p += *p & ~SMK_NODE;
|
|
p++;
|
|
}
|
|
|
|
return *p;
|
|
}
|
|
|
|
/*
|
|
* class BigHuffmanTree
|
|
* A Huffman-tree to hold 16-bit values.
|
|
*/
|
|
|
|
class BigHuffmanTree {
|
|
public:
|
|
BigHuffmanTree(Common::BitStreamMemory8LSB &bs, int allocSize);
|
|
~BigHuffmanTree();
|
|
|
|
void reset();
|
|
uint32 getCode(Common::BitStreamMemory8LSB &bs);
|
|
private:
|
|
enum {
|
|
SMK_NODE = 0x80000000
|
|
};
|
|
|
|
uint32 decodeTree(uint32 prefix, int length);
|
|
|
|
uint32 _treeSize;
|
|
uint32 *_tree;
|
|
uint32 _last[3];
|
|
|
|
uint32 _prefixtree[256];
|
|
byte _prefixlength[256];
|
|
|
|
/* Used during construction */
|
|
Common::BitStreamMemory8LSB &_bs;
|
|
uint32 _markers[3];
|
|
SmallHuffmanTree *_loBytes;
|
|
SmallHuffmanTree *_hiBytes;
|
|
};
|
|
|
|
BigHuffmanTree::BigHuffmanTree(Common::BitStreamMemory8LSB &bs, int allocSize)
|
|
: _bs(bs) {
|
|
uint32 bit = _bs.getBit();
|
|
if (!bit) {
|
|
_tree = new uint32[1];
|
|
_tree[0] = 0;
|
|
_last[0] = _last[1] = _last[2] = 0;
|
|
return;
|
|
}
|
|
|
|
for (uint32 i = 0; i < 256; ++i)
|
|
_prefixtree[i] = _prefixlength[i] = 0;
|
|
|
|
_loBytes = new SmallHuffmanTree(_bs);
|
|
_hiBytes = new SmallHuffmanTree(_bs);
|
|
|
|
_markers[0] = _bs.getBits(16);
|
|
_markers[1] = _bs.getBits(16);
|
|
_markers[2] = _bs.getBits(16);
|
|
|
|
_last[0] = _last[1] = _last[2] = 0xffffffff;
|
|
|
|
_treeSize = 0;
|
|
_tree = new uint32[allocSize / 4];
|
|
decodeTree(0, 0);
|
|
(void)_bs.getBit();
|
|
|
|
for (uint32 i = 0; i < 3; ++i) {
|
|
if (_last[i] == 0xffffffff) {
|
|
_last[i] = _treeSize;
|
|
_tree[_treeSize++] = 0;
|
|
}
|
|
}
|
|
|
|
delete _loBytes;
|
|
delete _hiBytes;
|
|
}
|
|
|
|
BigHuffmanTree::~BigHuffmanTree() {
|
|
delete[] _tree;
|
|
}
|
|
|
|
void BigHuffmanTree::reset() {
|
|
_tree[_last[0]] = _tree[_last[1]] = _tree[_last[2]] = 0;
|
|
}
|
|
|
|
uint32 BigHuffmanTree::decodeTree(uint32 prefix, int length) {
|
|
uint32 bit = _bs.getBit();
|
|
|
|
if (!bit) { // Leaf
|
|
uint32 lo = _loBytes->getCode(_bs);
|
|
uint32 hi = _hiBytes->getCode(_bs);
|
|
|
|
uint32 v = (hi << 8) | lo;
|
|
|
|
_tree[_treeSize] = v;
|
|
|
|
if (length <= 8) {
|
|
for (int i = 0; i < 256; i += (1 << length)) {
|
|
_prefixtree[prefix | i] = _treeSize;
|
|
_prefixlength[prefix | i] = length;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
if (_markers[i] == v) {
|
|
_last[i] = _treeSize;
|
|
_tree[_treeSize] = 0;
|
|
}
|
|
}
|
|
++_treeSize;
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint32 t = _treeSize++;
|
|
|
|
if (length == 8) {
|
|
_prefixtree[prefix] = t;
|
|
_prefixlength[prefix] = 8;
|
|
}
|
|
|
|
uint32 r1 = decodeTree(prefix, length + 1);
|
|
|
|
_tree[t] = SMK_NODE | r1;
|
|
|
|
uint32 r2 = decodeTree(prefix | (1 << length), length + 1);
|
|
return r1+r2+1;
|
|
}
|
|
|
|
uint32 BigHuffmanTree::getCode(Common::BitStreamMemory8LSB &bs) {
|
|
byte peek = bs.peekBits(MIN<uint32>(bs.size() - bs.pos(), 8));
|
|
uint32 *p = &_tree[_prefixtree[peek]];
|
|
bs.skip(_prefixlength[peek]);
|
|
|
|
while (*p & SMK_NODE) {
|
|
if (bs.getBit())
|
|
p += (*p) & ~SMK_NODE;
|
|
p++;
|
|
}
|
|
|
|
uint32 v = *p;
|
|
if (v != _tree[_last[0]]) {
|
|
_tree[_last[2]] = _tree[_last[1]];
|
|
_tree[_last[1]] = _tree[_last[0]];
|
|
_tree[_last[0]] = v;
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
SmackerDecoder::SmackerDecoder() {
|
|
_fileStream = 0;
|
|
_firstFrameStart = 0;
|
|
_frameTypes = 0;
|
|
_frameSizes = 0;
|
|
}
|
|
|
|
SmackerDecoder::~SmackerDecoder() {
|
|
close();
|
|
}
|
|
|
|
bool SmackerDecoder::loadStream(Common::SeekableReadStream *stream) {
|
|
close();
|
|
|
|
_fileStream = stream;
|
|
|
|
// Read in the Smacker header
|
|
_header.signature = _fileStream->readUint32BE();
|
|
|
|
if (_header.signature != MKTAG('S', 'M', 'K', '2') && _header.signature != MKTAG('S', 'M', 'K', '4'))
|
|
return false;
|
|
|
|
uint32 width = _fileStream->readUint32LE();
|
|
uint32 height = _fileStream->readUint32LE();
|
|
uint32 frameCount = _fileStream->readUint32LE();
|
|
int32 frameDelay = _fileStream->readSint32LE();
|
|
|
|
// frame rate contains 2 digits after the comma, so 1497 is actually 14.97 fps
|
|
Common::Rational frameRate;
|
|
if (frameDelay > 0)
|
|
frameRate = Common::Rational(1000, frameDelay);
|
|
else if (frameDelay < 0)
|
|
frameRate = Common::Rational(100000, -frameDelay);
|
|
else
|
|
frameRate = 1000;
|
|
|
|
// Flags are determined by which bit is set, which can be one of the following:
|
|
// 0 - set to 1 if file contains a ring frame.
|
|
// 1 - set to 1 if file is Y-interlaced
|
|
// 2 - set to 1 if file is Y-doubled
|
|
// If bits 1 or 2 are set, the frame should be scaled to twice its height
|
|
// before it is displayed.
|
|
_header.flags = _fileStream->readUint32LE();
|
|
if (_header.flags & 1)
|
|
frameCount++;
|
|
|
|
SmackerVideoTrack *videoTrack = createVideoTrack(width, height, frameCount, frameRate, _header.flags, _header.signature);
|
|
addTrack(videoTrack);
|
|
|
|
// TODO: should we do any extra processing for Smacker files with ring frames?
|
|
|
|
// TODO: should we do any extra processing for Y-doubled videos? Are they the
|
|
// same as Y-interlaced videos?
|
|
|
|
uint32 i;
|
|
for (i = 0; i < 7; ++i)
|
|
_header.audioSize[i] = _fileStream->readUint32LE();
|
|
|
|
_header.treesSize = _fileStream->readUint32LE();
|
|
_header.mMapSize = _fileStream->readUint32LE();
|
|
_header.mClrSize = _fileStream->readUint32LE();
|
|
_header.fullSize = _fileStream->readUint32LE();
|
|
_header.typeSize = _fileStream->readUint32LE();
|
|
|
|
for (i = 0; i < 7; ++i) {
|
|
// AudioRate - Frequency and format information for each sound track, up to 7 audio tracks.
|
|
// The 32 constituent bits have the following meaning:
|
|
// * bit 31 - indicates Huffman + DPCM compression
|
|
// * bit 30 - indicates that audio data is present for this track
|
|
// * bit 29 - 1 = 16-bit audio; 0 = 8-bit audio
|
|
// * bit 28 - 1 = stereo audio; 0 = mono audio
|
|
// * bit 27 - indicates Bink RDFT compression
|
|
// * bit 26 - indicates Bink DCT compression
|
|
// * bits 25-24 - unused
|
|
// * bits 23-0 - audio sample rate
|
|
uint32 audioInfo = _fileStream->readUint32LE();
|
|
_header.audioInfo[i].hasAudio = audioInfo & 0x40000000;
|
|
_header.audioInfo[i].is16Bits = audioInfo & 0x20000000;
|
|
_header.audioInfo[i].isStereo = audioInfo & 0x10000000;
|
|
_header.audioInfo[i].sampleRate = audioInfo & 0xFFFFFF;
|
|
|
|
if (audioInfo & 0x8000000)
|
|
_header.audioInfo[i].compression = kCompressionRDFT;
|
|
else if (audioInfo & 0x4000000)
|
|
_header.audioInfo[i].compression = kCompressionDCT;
|
|
else if (audioInfo & 0x80000000)
|
|
_header.audioInfo[i].compression = kCompressionDPCM;
|
|
else
|
|
_header.audioInfo[i].compression = kCompressionNone;
|
|
|
|
if (_header.audioInfo[i].hasAudio) {
|
|
if (_header.audioInfo[i].compression == kCompressionRDFT || _header.audioInfo[i].compression == kCompressionDCT)
|
|
warning("Unhandled Smacker v2 audio compression");
|
|
|
|
addTrack(new SmackerAudioTrack(_header.audioInfo[i], getSoundType()));
|
|
} else {
|
|
addTrack(new SmackerEmptyTrack());
|
|
}
|
|
}
|
|
|
|
_header.dummy = _fileStream->readUint32LE();
|
|
|
|
_frameSizes = new uint32[frameCount];
|
|
for (i = 0; i < frameCount; ++i)
|
|
_frameSizes[i] = _fileStream->readUint32LE();
|
|
|
|
_frameTypes = new byte[frameCount];
|
|
for (i = 0; i < frameCount; ++i)
|
|
_frameTypes[i] = _fileStream->readByte();
|
|
|
|
byte *huffmanTrees = (byte *) malloc(_header.treesSize);
|
|
_fileStream->read(huffmanTrees, _header.treesSize);
|
|
|
|
Common::BitStreamMemory8LSB bs(new Common::BitStreamMemoryStream(huffmanTrees, _header.treesSize, DisposeAfterUse::YES), DisposeAfterUse::YES);
|
|
videoTrack->readTrees(bs, _header.mMapSize, _header.mClrSize, _header.fullSize, _header.typeSize);
|
|
|
|
_firstFrameStart = _fileStream->pos();
|
|
|
|
return true;
|
|
}
|
|
|
|
void SmackerDecoder::close() {
|
|
VideoDecoder::close();
|
|
|
|
delete _fileStream;
|
|
_fileStream = 0;
|
|
|
|
delete[] _frameTypes;
|
|
_frameTypes = 0;
|
|
|
|
delete[] _frameSizes;
|
|
_frameSizes = 0;
|
|
}
|
|
|
|
bool SmackerDecoder::rewind() {
|
|
// Call the parent method to rewind the tracks first
|
|
if (!VideoDecoder::rewind())
|
|
return false;
|
|
|
|
// And seek back to where the first frame begins
|
|
_fileStream->seek(_firstFrameStart);
|
|
return true;
|
|
}
|
|
|
|
void SmackerDecoder::readNextPacket() {
|
|
SmackerVideoTrack *videoTrack = (SmackerVideoTrack *)getTrack(0);
|
|
|
|
if (videoTrack->endOfTrack())
|
|
return;
|
|
|
|
videoTrack->increaseCurFrame();
|
|
|
|
uint i;
|
|
uint32 chunkSize = 0;
|
|
uint32 dataSizeUnpacked = 0;
|
|
|
|
uint32 startPos = _fileStream->pos();
|
|
|
|
// Check if we got a frame with palette data, and
|
|
// call back the virtual setPalette function to set
|
|
// the current palette
|
|
if (_frameTypes[videoTrack->getCurFrame()] & 1)
|
|
videoTrack->unpackPalette(_fileStream);
|
|
|
|
// Load audio tracks
|
|
for (i = 0; i < 7; ++i) {
|
|
if (!(_frameTypes[videoTrack->getCurFrame()] & (2 << i)))
|
|
continue;
|
|
|
|
chunkSize = _fileStream->readUint32LE();
|
|
chunkSize -= 4; // subtract the first 4 bytes (chunk size)
|
|
|
|
if (_header.audioInfo[i].compression == kCompressionNone) {
|
|
dataSizeUnpacked = chunkSize;
|
|
} else {
|
|
dataSizeUnpacked = _fileStream->readUint32LE();
|
|
chunkSize -= 4; // subtract the next 4 bytes (unpacked data size)
|
|
}
|
|
|
|
handleAudioTrack(i, chunkSize, dataSizeUnpacked);
|
|
}
|
|
|
|
uint32 frameSize = _frameSizes[videoTrack->getCurFrame()] & ~3;
|
|
// uint32 remainder = _frameSizes[videoTrack->getCurFrame()] & 3;
|
|
|
|
if (_fileStream->pos() - startPos > frameSize)
|
|
error("Smacker actual frame size exceeds recorded frame size");
|
|
|
|
uint32 frameDataSize = frameSize - (_fileStream->pos() - startPos);
|
|
|
|
byte *frameData = (byte *)malloc(frameDataSize + 1);
|
|
// Padding to keep the BigHuffmanTrees from reading past the data end
|
|
frameData[frameDataSize] = 0x00;
|
|
|
|
_fileStream->read(frameData, frameDataSize);
|
|
|
|
Common::BitStreamMemory8LSB bs(new Common::BitStreamMemoryStream(frameData, frameDataSize + 1, DisposeAfterUse::YES), DisposeAfterUse::YES);
|
|
videoTrack->decodeFrame(bs);
|
|
|
|
_fileStream->seek(startPos + frameSize);
|
|
}
|
|
|
|
void SmackerDecoder::handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize) {
|
|
if (chunkSize == 0)
|
|
return;
|
|
|
|
if (_header.audioInfo[track].hasAudio) {
|
|
// Get the audio track, which start at offset 1 (first track is video)
|
|
SmackerAudioTrack *audioTrack = (SmackerAudioTrack *)getTrack(track + 1);
|
|
|
|
// If it's track 0, play the audio data
|
|
byte *soundBuffer = (byte *)malloc(chunkSize + 1);
|
|
// Padding to keep the SmallHuffmanTrees from reading past the data end
|
|
soundBuffer[chunkSize] = 0x00;
|
|
|
|
_fileStream->read(soundBuffer, chunkSize);
|
|
|
|
if (_header.audioInfo[track].compression == kCompressionRDFT || _header.audioInfo[track].compression == kCompressionDCT) {
|
|
// TODO: Compressed audio (Bink RDFT/DCT encoded)
|
|
free(soundBuffer);
|
|
return;
|
|
} else if (_header.audioInfo[track].compression == kCompressionDPCM) {
|
|
// Compressed audio (Huffman DPCM encoded)
|
|
audioTrack->queueCompressedBuffer(soundBuffer, chunkSize + 1, unpackedSize);
|
|
free(soundBuffer);
|
|
} else {
|
|
// Uncompressed audio (PCM)
|
|
audioTrack->queuePCM(soundBuffer, chunkSize);
|
|
}
|
|
} else {
|
|
// Ignore possibly unused data
|
|
_fileStream->skip(chunkSize);
|
|
}
|
|
}
|
|
|
|
VideoDecoder::AudioTrack *SmackerDecoder::getAudioTrack(int index) {
|
|
// Smacker audio track indexes are relative to the first audio track
|
|
Track *track = getTrack(index + 1);
|
|
|
|
if (!track || track->getTrackType() != Track::kTrackTypeAudio)
|
|
return 0;
|
|
|
|
return (AudioTrack *)track;
|
|
}
|
|
|
|
SmackerDecoder::SmackerVideoTrack::SmackerVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature) {
|
|
_surface = new Graphics::Surface();
|
|
_surface->create(width, height * ((flags & 6) ? 2 : 1), Graphics::PixelFormat::createFormatCLUT8());
|
|
_dirtyBlocks.set_size(width * height / 16);
|
|
_frameCount = frameCount;
|
|
_frameRate = frameRate;
|
|
_flags = flags;
|
|
_signature = signature;
|
|
_curFrame = -1;
|
|
_dirtyPalette = false;
|
|
_MMapTree = _MClrTree = _FullTree = _TypeTree = 0;
|
|
memset(_palette, 0, 3 * 256);
|
|
}
|
|
|
|
SmackerDecoder::SmackerVideoTrack::~SmackerVideoTrack() {
|
|
_surface->free();
|
|
delete _surface;
|
|
|
|
delete _MMapTree;
|
|
delete _MClrTree;
|
|
delete _FullTree;
|
|
delete _TypeTree;
|
|
}
|
|
|
|
uint16 SmackerDecoder::SmackerVideoTrack::getWidth() const {
|
|
return _surface->w;
|
|
}
|
|
|
|
uint16 SmackerDecoder::SmackerVideoTrack::getHeight() const {
|
|
return _surface->h;
|
|
}
|
|
|
|
Graphics::PixelFormat SmackerDecoder::SmackerVideoTrack::getPixelFormat() const {
|
|
return _surface->format;
|
|
}
|
|
|
|
void SmackerDecoder::SmackerVideoTrack::readTrees(Common::BitStreamMemory8LSB &bs, uint32 mMapSize, uint32 mClrSize, uint32 fullSize, uint32 typeSize) {
|
|
_MMapTree = new BigHuffmanTree(bs, mMapSize);
|
|
_MClrTree = new BigHuffmanTree(bs, mClrSize);
|
|
_FullTree = new BigHuffmanTree(bs, fullSize);
|
|
_TypeTree = new BigHuffmanTree(bs, typeSize);
|
|
}
|
|
|
|
void SmackerDecoder::SmackerVideoTrack::decodeFrame(Common::BitStreamMemory8LSB &bs) {
|
|
_MMapTree->reset();
|
|
_MClrTree->reset();
|
|
_FullTree->reset();
|
|
_TypeTree->reset();
|
|
_dirtyBlocks.clear();
|
|
|
|
// Height needs to be doubled if we have flags (Y-interlaced or Y-doubled)
|
|
uint doubleY = (_flags & 6) ? 2 : 1;
|
|
|
|
uint bw = getWidth() / 4;
|
|
uint bh = getHeight() / doubleY / 4;
|
|
uint stride = getWidth();
|
|
uint block = 0, blocks = bw*bh;
|
|
|
|
byte *out;
|
|
uint type, run, j, mode;
|
|
uint32 p1, p2, clr, map;
|
|
byte hi, lo;
|
|
uint i;
|
|
|
|
while (block < blocks) {
|
|
type = _TypeTree->getCode(bs);
|
|
run = getBlockRun((type >> 2) & 0x3f);
|
|
|
|
switch (type & 3) {
|
|
case SMK_BLOCK_MONO:
|
|
while (run-- && block < blocks) {
|
|
clr = _MClrTree->getCode(bs);
|
|
map = _MMapTree->getCode(bs);
|
|
out = (byte *)_surface->getPixels() + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4;
|
|
hi = clr >> 8;
|
|
lo = clr & 0xff;
|
|
for (i = 0; i < 4; i++) {
|
|
for (j = 0; j < doubleY; j++) {
|
|
out[0] = (map & 1) ? hi : lo;
|
|
out[1] = (map & 2) ? hi : lo;
|
|
out[2] = (map & 4) ? hi : lo;
|
|
out[3] = (map & 8) ? hi : lo;
|
|
out += stride;
|
|
}
|
|
map >>= 4;
|
|
}
|
|
_dirtyBlocks.set(block);
|
|
++block;
|
|
}
|
|
break;
|
|
case SMK_BLOCK_FULL:
|
|
// Smacker v2 has one mode, Smacker v4 has three
|
|
if (_signature == MKTAG('S','M','K','2')) {
|
|
mode = 0;
|
|
} else {
|
|
// 00 - mode 0
|
|
// 10 - mode 1
|
|
// 01 - mode 2
|
|
mode = 0;
|
|
if (bs.getBit()) {
|
|
mode = 1;
|
|
} else if (bs.getBit()) {
|
|
mode = 2;
|
|
}
|
|
}
|
|
|
|
while (run-- && block < blocks) {
|
|
out = (byte *)_surface->getPixels() + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4;
|
|
switch (mode) {
|
|
case 0:
|
|
for (i = 0; i < 4; ++i) {
|
|
p1 = _FullTree->getCode(bs);
|
|
p2 = _FullTree->getCode(bs);
|
|
for (j = 0; j < doubleY; ++j) {
|
|
out[2] = p1 & 0xff;
|
|
out[3] = p1 >> 8;
|
|
out[0] = p2 & 0xff;
|
|
out[1] = p2 >> 8;
|
|
out += stride;
|
|
}
|
|
}
|
|
break;
|
|
case 1:
|
|
p1 = _FullTree->getCode(bs);
|
|
out[0] = out[1] = p1 & 0xFF;
|
|
out[2] = out[3] = p1 >> 8;
|
|
out += stride;
|
|
out[0] = out[1] = p1 & 0xFF;
|
|
out[2] = out[3] = p1 >> 8;
|
|
out += stride;
|
|
p2 = _FullTree->getCode(bs);
|
|
out[0] = out[1] = p2 & 0xFF;
|
|
out[2] = out[3] = p2 >> 8;
|
|
out += stride;
|
|
out[0] = out[1] = p2 & 0xFF;
|
|
out[2] = out[3] = p2 >> 8;
|
|
out += stride;
|
|
break;
|
|
case 2:
|
|
for (i = 0; i < 2; i++) {
|
|
// We first get p2 and then p1
|
|
// Check ffmpeg thread "[PATCH] Smacker video decoder bug fix"
|
|
// http://article.gmane.org/gmane.comp.video.ffmpeg.devel/78768
|
|
p2 = _FullTree->getCode(bs);
|
|
p1 = _FullTree->getCode(bs);
|
|
for (j = 0; j < doubleY; ++j) {
|
|
out[0] = p1 & 0xff;
|
|
out[1] = p1 >> 8;
|
|
out[2] = p2 & 0xff;
|
|
out[3] = p2 >> 8;
|
|
out += stride;
|
|
}
|
|
for (j = 0; j < doubleY; ++j) {
|
|
out[0] = p1 & 0xff;
|
|
out[1] = p1 >> 8;
|
|
out[2] = p2 & 0xff;
|
|
out[3] = p2 >> 8;
|
|
out += stride;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
_dirtyBlocks.set(block);
|
|
++block;
|
|
}
|
|
break;
|
|
case SMK_BLOCK_SKIP:
|
|
while (run-- && block < blocks)
|
|
block++;
|
|
break;
|
|
case SMK_BLOCK_FILL:
|
|
uint32 col;
|
|
mode = type >> 8;
|
|
while (run-- && block < blocks) {
|
|
out = (byte *)_surface->getPixels() + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4;
|
|
col = mode * 0x01010101;
|
|
for (i = 0; i < 4 * doubleY; ++i) {
|
|
out[0] = out[1] = out[2] = out[3] = col;
|
|
out += stride;
|
|
}
|
|
_dirtyBlocks.set(block);
|
|
++block;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SmackerDecoder::SmackerVideoTrack::unpackPalette(Common::SeekableReadStream *stream) {
|
|
uint startPos = stream->pos();
|
|
uint32 len = 4 * stream->readByte();
|
|
|
|
byte *chunk = (byte *)malloc(len);
|
|
stream->read(chunk, len);
|
|
byte *p = chunk;
|
|
|
|
byte oldPalette[3 * 256];
|
|
memcpy(oldPalette, _palette, 3 * 256);
|
|
|
|
byte *pal = _palette;
|
|
|
|
int sz = 0;
|
|
byte b0;
|
|
while (sz < 256) {
|
|
b0 = *p++;
|
|
if (b0 & 0x80) { // if top bit is 1 (0x80 = 10000000)
|
|
sz += (b0 & 0x7f) + 1; // get lower 7 bits + 1 (0x7f = 01111111)
|
|
pal += 3 * ((b0 & 0x7f) + 1);
|
|
} else if (b0 & 0x40) { // if top 2 bits are 01 (0x40 = 01000000)
|
|
byte c = (b0 & 0x3f) + 1; // get lower 6 bits + 1 (0x3f = 00111111)
|
|
uint s = 3 * *p++;
|
|
sz += c;
|
|
|
|
while (c--) {
|
|
*pal++ = oldPalette[s + 0];
|
|
*pal++ = oldPalette[s + 1];
|
|
*pal++ = oldPalette[s + 2];
|
|
s += 3;
|
|
}
|
|
} else { // top 2 bits are 00
|
|
sz++;
|
|
// get the lower 6 bits for each component (0x3f = 00111111)
|
|
byte r = b0 & 0x3f;
|
|
byte g = (*p++) & 0x3f;
|
|
byte b = (*p++) & 0x3f;
|
|
|
|
// upscale to full 8-bit color values. The Multimedia Wiki suggests
|
|
// a lookup table for this, but this should produce the same result.
|
|
*pal++ = (r * 4 + r / 16);
|
|
*pal++ = (g * 4 + g / 16);
|
|
*pal++ = (b * 4 + b / 16);
|
|
}
|
|
}
|
|
|
|
stream->seek(startPos + len);
|
|
free(chunk);
|
|
|
|
_dirtyPalette = true;
|
|
}
|
|
|
|
SmackerDecoder::SmackerAudioTrack::SmackerAudioTrack(const AudioInfo &audioInfo, Audio::Mixer::SoundType soundType) :
|
|
AudioTrack(soundType),
|
|
_audioInfo(audioInfo) {
|
|
_audioStream = Audio::makeQueuingAudioStream(_audioInfo.sampleRate, _audioInfo.isStereo);
|
|
}
|
|
|
|
SmackerDecoder::SmackerAudioTrack::~SmackerAudioTrack() {
|
|
delete _audioStream;
|
|
}
|
|
|
|
bool SmackerDecoder::SmackerAudioTrack::rewind() {
|
|
delete _audioStream;
|
|
_audioStream = Audio::makeQueuingAudioStream(_audioInfo.sampleRate, _audioInfo.isStereo);
|
|
return true;
|
|
}
|
|
|
|
Audio::AudioStream *SmackerDecoder::SmackerAudioTrack::getAudioStream() const {
|
|
return _audioStream;
|
|
}
|
|
|
|
void SmackerDecoder::SmackerAudioTrack::queueCompressedBuffer(byte *buffer, uint32 bufferSize, uint32 unpackedSize) {
|
|
Common::BitStreamMemory8LSB audioBS(new Common::BitStreamMemoryStream(buffer, bufferSize), DisposeAfterUse::YES);
|
|
bool dataPresent = audioBS.getBit();
|
|
|
|
if (!dataPresent)
|
|
return;
|
|
|
|
bool isStereo = audioBS.getBit();
|
|
assert(isStereo == _audioInfo.isStereo);
|
|
bool is16Bits = audioBS.getBit();
|
|
assert(is16Bits == _audioInfo.is16Bits);
|
|
|
|
int numBytes = 1 * (isStereo ? 2 : 1) * (is16Bits ? 2 : 1);
|
|
|
|
byte *unpackedBuffer = (byte *)malloc(unpackedSize);
|
|
byte *curPointer = unpackedBuffer;
|
|
uint32 curPos = 0;
|
|
|
|
SmallHuffmanTree *audioTrees[4];
|
|
for (int k = 0; k < numBytes; k++)
|
|
audioTrees[k] = new SmallHuffmanTree(audioBS);
|
|
|
|
// Base values, stored as big endian
|
|
|
|
int32 bases[2];
|
|
|
|
if (isStereo) {
|
|
if (is16Bits) {
|
|
bases[1] = SWAP_BYTES_16(audioBS.getBits(16));
|
|
} else {
|
|
bases[1] = audioBS.getBits(8);
|
|
}
|
|
}
|
|
|
|
if (is16Bits) {
|
|
bases[0] = SWAP_BYTES_16(audioBS.getBits(16));
|
|
} else {
|
|
bases[0] = audioBS.getBits(8);
|
|
}
|
|
|
|
// The bases are the first samples, too
|
|
for (int i = 0; i < (isStereo ? 2 : 1); i++, curPointer += (is16Bits ? 2 : 1), curPos += (is16Bits ? 2 : 1)) {
|
|
if (is16Bits)
|
|
WRITE_BE_UINT16(curPointer, bases[i]);
|
|
else
|
|
*curPointer = (bases[i] & 0xFF) ^ 0x80;
|
|
}
|
|
|
|
// Next follow the deltas, which are added to the corresponding base values and
|
|
// are stored as little endian
|
|
// We store the unpacked bytes in big endian format
|
|
|
|
while (curPos < unpackedSize) {
|
|
// If the sample is stereo, the data is stored for the left and right channel, respectively
|
|
// (the exact opposite to the base values)
|
|
if (!is16Bits) {
|
|
for (int k = 0; k < (isStereo ? 2 : 1); k++) {
|
|
int8 delta = (int8) ((int16) audioTrees[k]->getCode(audioBS));
|
|
bases[k] = (bases[k] + delta) & 0xFF;
|
|
*curPointer++ = bases[k] ^ 0x80;
|
|
curPos++;
|
|
}
|
|
} else {
|
|
for (int k = 0; k < (isStereo ? 2 : 1); k++) {
|
|
byte lo = audioTrees[k * 2]->getCode(audioBS);
|
|
byte hi = audioTrees[k * 2 + 1]->getCode(audioBS);
|
|
bases[k] += (int16) (lo | (hi << 8));
|
|
|
|
WRITE_BE_UINT16(curPointer, bases[k]);
|
|
curPointer += 2;
|
|
curPos += 2;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
for (int k = 0; k < numBytes; k++)
|
|
delete audioTrees[k];
|
|
|
|
queuePCM(unpackedBuffer, unpackedSize);
|
|
}
|
|
|
|
void SmackerDecoder::SmackerAudioTrack::queuePCM(byte *buffer, uint32 bufferSize) {
|
|
byte flags = 0;
|
|
if (_audioInfo.is16Bits)
|
|
flags |= Audio::FLAG_16BITS;
|
|
if (_audioInfo.isStereo)
|
|
flags |= Audio::FLAG_STEREO;
|
|
|
|
_audioStream->queueBuffer(buffer, bufferSize, DisposeAfterUse::YES, flags);
|
|
}
|
|
|
|
SmackerDecoder::SmackerVideoTrack *SmackerDecoder::createVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature) const {
|
|
return new SmackerVideoTrack(width, height, frameCount, frameRate, flags, signature);
|
|
}
|
|
|
|
Common::Rational SmackerDecoder::getFrameRate() const {
|
|
const SmackerVideoTrack *videoTrack = (const SmackerVideoTrack *)getTrack(0);
|
|
|
|
return videoTrack->getFrameRate();
|
|
}
|
|
|
|
const Common::Rect *SmackerDecoder::getNextDirtyRect() {
|
|
SmackerVideoTrack *videoTrack = (SmackerVideoTrack *)getTrack(0);
|
|
|
|
return videoTrack->getNextDirtyRect();
|
|
}
|
|
|
|
const Common::Rect *SmackerDecoder::SmackerVideoTrack::getNextDirtyRect() {
|
|
uint doubleY = (_flags & 6) ? 2 : 1;
|
|
|
|
uint bw = getWidth() / 4;
|
|
uint bh = getHeight() / 4;
|
|
uint blocks = bw*bh;
|
|
|
|
// Scan forward in dirty blocks bitarray for next dirty rect
|
|
uint block_idx = (_lastDirtyRect.left) / 4 + (_lastDirtyRect.top / 4 / doubleY) * bw;
|
|
while (block_idx < blocks && !_dirtyBlocks.get(block_idx)) {
|
|
++block_idx;
|
|
}
|
|
if (block_idx == blocks) {
|
|
_lastDirtyRect = Common::Rect();
|
|
return nullptr;
|
|
}
|
|
|
|
uint block_x0 = block_idx % bw;
|
|
uint block_y0 = block_idx / bw;
|
|
|
|
// Find the width of the dirty rect
|
|
uint block_x1 = block_x0 + 1;
|
|
while (block_x1 < bw && _dirtyBlocks.get(block_x1 + block_y0 * bw)) {
|
|
++block_x1;
|
|
}
|
|
|
|
// Find the height of the dirty rect
|
|
uint block_y1 = block_y0 + 1;
|
|
while (block_y1 < bh) {
|
|
// Check that the rect to the left of the next line isn't dirty
|
|
if (block_x0 != 0 && _dirtyBlocks.get(block_x0 - 1 + block_y1 * bw)) {
|
|
break;
|
|
}
|
|
|
|
// Check that all the rects on this line are dirty
|
|
uint bx;
|
|
for (bx = block_x0; bx != block_x1; ++bx) {
|
|
if (!_dirtyBlocks.get(bx + block_y1 * bw)) {
|
|
break;
|
|
}
|
|
}
|
|
if (bx != block_x1) {
|
|
break;
|
|
}
|
|
|
|
// Check that the rect to the right of this line isn't dirty
|
|
if (bx != bw && _dirtyBlocks.get(bx + block_y1 * bw)) {
|
|
break;
|
|
}
|
|
++block_y1;
|
|
}
|
|
|
|
// Undirty all the rects that we're returning
|
|
for (uint y = block_y0; y != block_y1; ++y) {
|
|
for (uint x = block_x0; x != block_x1; ++x) {
|
|
_dirtyBlocks.unset(x + y * bw);
|
|
}
|
|
}
|
|
|
|
_lastDirtyRect = Common::Rect(
|
|
int16(4 * block_x0),
|
|
int16(4 * block_y0 * doubleY),
|
|
int16(4 * block_x1),
|
|
int16(4 * block_y1 * doubleY)
|
|
);
|
|
return &_lastDirtyRect;
|
|
}
|
|
|
|
} // End of namespace Video
|