diff options
author | Eugene Sandulenko | 2011-01-23 17:14:43 +0000 |
---|---|---|
committer | Eugene Sandulenko | 2011-01-23 17:14:43 +0000 |
commit | 806ccf5d25ebe337103942cdb6b5cf8800de115a (patch) | |
tree | ead210f06163f97521b2ae26b06b450be8bf10a3 /video/smk_decoder.cpp | |
parent | 36ed9be335217ef6fcf8e07b257fb6a1ee1ed8f2 (diff) | |
download | scummvm-rg350-806ccf5d25ebe337103942cdb6b5cf8800de115a.tar.gz scummvm-rg350-806ccf5d25ebe337103942cdb6b5cf8800de115a.tar.bz2 scummvm-rg350-806ccf5d25ebe337103942cdb6b5cf8800de115a.zip |
GRAPHICS: Move graphics/video/ to video/. Step 1/2
svn-id: r55473
Diffstat (limited to 'video/smk_decoder.cpp')
-rw-r--r-- | video/smk_decoder.cpp | 902 |
1 files changed, 902 insertions, 0 deletions
diff --git a/video/smk_decoder.cpp b/video/smk_decoder.cpp new file mode 100644 index 0000000000..e4d577acd2 --- /dev/null +++ b/video/smk_decoder.cpp @@ -0,0 +1,902 @@ +/* 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 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 "graphics/video/smk_decoder.h" + +#include "common/archive.h" +#include "common/endian.h" +#include "common/util.h" +#include "common/stream.h" +#include "common/system.h" + +#include "sound/audiostream.h" +#include "sound/mixer.h" +#include "sound/decoders/raw.h" + +namespace Graphics { + +enum SmkBlockTypes { + SMK_BLOCK_MONO = 0, + SMK_BLOCK_FULL = 1, + SMK_BLOCK_SKIP = 2, + SMK_BLOCK_FILL = 3 +}; + +/* + * class BitStream + * Little-endian bit stream provider. + */ + +class BitStream { +public: + BitStream(byte *buf, uint32 length) + : _buf(buf), _end(buf+length), _bitCount(8) { + _curByte = *_buf++; + } + + bool getBit(); + byte getBits8(); + + byte peek8() const; + void skip(int n); + +private: + byte *_buf; + byte *_end; + byte _curByte; + byte _bitCount; +}; + +bool BitStream::getBit() { + if (_bitCount == 0) { + assert(_buf < _end); + _curByte = *_buf++; + _bitCount = 8; + } + + bool v = _curByte & 1; + + _curByte >>= 1; + --_bitCount; + + return v; +} + +byte BitStream::getBits8() { + assert(_buf < _end); + + byte v = (*_buf << _bitCount) | _curByte; + _curByte = *_buf++ >> (8 - _bitCount); + + return v; +} + +byte BitStream::peek8() const { + if (_buf == _end) + return _curByte; + + assert(_buf < _end); + return (*_buf << _bitCount) | _curByte; +} + +void BitStream::skip(int n) { + assert(n <= 8); + _curByte >>= n; + + if (_bitCount >= n) { + _bitCount -= n; + } else { + assert(_buf < _end); + _bitCount = _bitCount + 8 - n; + _curByte = *_buf++ >> (8 - _bitCount); + } +} + +/* + * class SmallHuffmanTree + * A Huffman-tree to hold 8-bit values. + */ + +class SmallHuffmanTree { +public: + SmallHuffmanTree(BitStream &bs); + + uint16 getCode(BitStream &bs); +private: + enum { + SMK_NODE = 0x8000 + }; + + uint16 decodeTree(uint32 prefix, int length); + + uint16 _treeSize; + uint16 _tree[511]; + + uint16 _prefixtree[256]; + byte _prefixlength[256]; + + BitStream &_bs; +}; + +SmallHuffmanTree::SmallHuffmanTree(BitStream &bs) + : _treeSize(0), _bs(bs) { + uint32 bit = _bs.getBit(); + assert(bit); + + for (uint16 i = 0; i < 256; ++i) + _prefixtree[i] = _prefixlength[i] = 0; + + decodeTree(0, 0); + + bit = _bs.getBit(); + assert(!bit); +} + +uint16 SmallHuffmanTree::decodeTree(uint32 prefix, int length) { + if (!_bs.getBit()) { // Leaf + _tree[_treeSize] = _bs.getBits8(); + + 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(BitStream &bs) { + byte peek = bs.peek8(); + 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(BitStream &bs, int allocSize); + ~BigHuffmanTree(); + + void reset(); + uint32 getCode(BitStream &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 */ + BitStream &_bs; + uint32 _markers[3]; + SmallHuffmanTree *_loBytes; + SmallHuffmanTree *_hiBytes; +}; + +BigHuffmanTree::BigHuffmanTree(BitStream &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.getBits8(); + _markers[0] |= (_bs.getBits8() << 8); + _markers[1] = _bs.getBits8(); + _markers[1] |= (_bs.getBits8() << 8); + _markers[2] = _bs.getBits8(); + _markers[2] |= (_bs.getBits8() << 8); + + _last[0] = _last[1] = _last[2] = 0xffffffff; + + _treeSize = 0; + _tree = new uint32[allocSize / 4]; + decodeTree(0, 0); + bit = _bs.getBit(); + assert(!bit); + + 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(BitStream &bs) { + byte peek = bs.peek8(); + 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(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) + : _audioStarted(false), _audioStream(0), _mixer(mixer), _soundType(soundType) { + _surface = 0; + _fileStream = 0; + _dirtyPalette = false; +} + +SmackerDecoder::~SmackerDecoder() { + close(); +} + +uint32 SmackerDecoder::getElapsedTime() const { + if (_audioStream && _audioStarted) + return _mixer->getSoundElapsedTime(_audioHandle); + + return VideoDecoder::getElapsedTime(); +} + +bool SmackerDecoder::load(Common::SeekableReadStream *stream) { + close(); + + _fileStream = stream; + + // Seek to the first frame + _header.signature = _fileStream->readUint32BE(); + + // No BINK support available + if (_header.signature == MKID_BE('BIKi')) { + delete _fileStream; + _fileStream = 0; + return false; + } + + assert(_header.signature == MKID_BE('SMK2') || _header.signature == MKID_BE('SMK4')); + + uint32 width = _fileStream->readUint32LE(); + uint32 height = _fileStream->readUint32LE(); + _frameCount = _fileStream->readUint32LE(); + int32 frameRate = _fileStream->readSint32LE(); + + // framerate contains 2 digits after the comma, so 1497 is actually 14.97 fps + if (frameRate > 0) + _frameRate = Common::Rational(1000, frameRate); + else if (frameRate < 0) + _frameRate = Common::Rational(100000, -frameRate); + 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(); + + // 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"); + + if (i == 0) + _audioStream = Audio::makeQueuingAudioStream(_header.audioInfo[0].sampleRate, _header.audioInfo[0].isStereo); + } + } + + _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 = new byte[_header.treesSize]; + _fileStream->read(huffmanTrees, _header.treesSize); + + BitStream bs(huffmanTrees, _header.treesSize); + + _MMapTree = new BigHuffmanTree(bs, _header.mMapSize); + _MClrTree = new BigHuffmanTree(bs, _header.mClrSize); + _FullTree = new BigHuffmanTree(bs, _header.fullSize); + _TypeTree = new BigHuffmanTree(bs, _header.typeSize); + + delete[] huffmanTrees; + + _surface = new Graphics::Surface(); + + // Height needs to be doubled if we have flags (Y-interlaced or Y-doubled) + _surface->create(width, height * (_header.flags ? 2 : 1), 1); + + _palette = (byte *)malloc(3 * 256); + memset(_palette, 0, 3 * 256); + return true; +} + +void SmackerDecoder::close() { + if (!_fileStream) + return; + + if (_audioStarted && _audioStream) { + _mixer->stopHandle(_audioHandle); + _audioStream = 0; + _audioStarted = false; + } + + delete _fileStream; + _fileStream = 0; + + _surface->free(); + delete _surface; + _surface = 0; + + delete _MMapTree; + delete _MClrTree; + delete _FullTree; + delete _TypeTree; + + delete[] _frameSizes; + delete[] _frameTypes; + free(_palette); + + reset(); +} + +const Surface *SmackerDecoder::decodeNextFrame() { + uint i; + uint32 chunkSize = 0; + uint32 dataSizeUnpacked = 0; + + uint32 startPos = _fileStream->pos(); + + _curFrame++; + + // Check if we got a frame with palette data, and + // call back the virtual setPalette function to set + // the current palette + if (_frameTypes[_curFrame] & 1) { + unpackPalette(); + _dirtyPalette = true; + } + + // Load audio tracks + for (i = 0; i < 7; ++i) { + if (!(_frameTypes[_curFrame] & (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[_curFrame] & ~3; +// uint32 remainder = _frameSizes[_curFrame] & 3; + + if (_fileStream->pos() - startPos > frameSize) + error("Smacker actual frame size exceeds recorded frame size"); + + uint32 frameDataSize = frameSize - (_fileStream->pos() - startPos); + + _frameData = (byte *)malloc(frameDataSize); + _fileStream->read(_frameData, frameDataSize); + + BitStream bs(_frameData, frameDataSize); + + _MMapTree->reset(); + _MClrTree->reset(); + _FullTree->reset(); + _TypeTree->reset(); + + // Height needs to be doubled if we have flags (Y-interlaced or Y-doubled) + uint doubleY = _header.flags ? 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; + + 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->pixels + (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; + } + ++block; + } + break; + case SMK_BLOCK_FULL: + // Smacker v2 has one mode, Smacker v4 has three + if (_header.signature == MKID_BE('SMK2')) { + 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->pixels + (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; + } + ++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->pixels + (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; + } + ++block; + } + break; + } + } + + _fileStream->seek(startPos + frameSize); + + free(_frameData); + + if (_curFrame == 0) + _startTime = g_system->getMillis(); + + return _surface; +} + +void SmackerDecoder::handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize) { + if (_header.audioInfo[track].hasAudio && chunkSize > 0 && track == 0) { + // If it's track 0, play the audio data + byte *soundBuffer = (byte *)malloc(chunkSize); + + _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) + queueCompressedBuffer(soundBuffer, chunkSize, unpackedSize, track); + free(soundBuffer); + } else { + // Uncompressed audio (PCM) + byte flags = 0; + if (_header.audioInfo[track].is16Bits) + flags = flags | Audio::FLAG_16BITS; + if (_header.audioInfo[track].isStereo) + flags = flags | Audio::FLAG_STEREO; + + _audioStream->queueBuffer(soundBuffer, chunkSize, DisposeAfterUse::YES, flags); + // The sound buffer will be deleted by QueuingAudioStream + } + + if (!_audioStarted) { + _mixer->playStream(_soundType, &_audioHandle, _audioStream, -1, 255); + _audioStarted = true; + } + } else { + // Ignore the rest of the audio tracks, if they exist + // TODO: Are there any Smacker videos with more than one audio stream? + // If yes, we should play the rest of the audio streams as well + if (chunkSize > 0) + _fileStream->skip(chunkSize); + } +} + +void SmackerDecoder::queueCompressedBuffer(byte *buffer, uint32 bufferSize, + uint32 unpackedSize, int streamNum) { + + BitStream audioBS(buffer, bufferSize); + bool dataPresent = audioBS.getBit(); + + if (!dataPresent) + return; + + bool isStereo = audioBS.getBit(); + assert(isStereo == _header.audioInfo[streamNum].isStereo); + bool is16Bits = audioBS.getBit(); + assert(is16Bits == _header.audioInfo[streamNum].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) { + byte hi = audioBS.getBits8(); + byte lo = audioBS.getBits8(); + bases[1] = (int16) ((hi << 8) | lo); + } else { + bases[1] = audioBS.getBits8(); + } + } + + if (is16Bits) { + byte hi = audioBS.getBits8(); + byte lo = audioBS.getBits8(); + bases[0] = (int16) ((hi << 8) | lo); + } else { + bases[0] = audioBS.getBits8(); + } + + // 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++) { + bases[k] += (int8) ((int16) audioTrees[k]->getCode(audioBS)); + *curPointer++ = CLIP<int>(bases[k], 0, 255) ^ 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]; + + byte flags = 0; + if (_header.audioInfo[0].is16Bits) + flags = flags | Audio::FLAG_16BITS; + if (_header.audioInfo[0].isStereo) + flags = flags | Audio::FLAG_STEREO; + _audioStream->queueBuffer(unpackedBuffer, unpackedSize, DisposeAfterUse::YES, flags); + // unpackedBuffer will be deleted by QueuingAudioStream +} + +void SmackerDecoder::unpackPalette() { + uint startPos = _fileStream->pos(); + uint32 len = 4 * _fileStream->readByte(); + + byte *chunk = (byte *)malloc(len); + _fileStream->read(&chunk[0], len); + byte *p = &chunk[0]; + + 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 b = b0 & 0x3f; + byte g = (*p++) & 0x3f; + byte r = (*p++) & 0x3f; + + assert(g < 0xc0 && b < 0xc0); + + // upscale to full 8-bit color values by multiplying by 4 + *pal++ = b * 4; + *pal++ = g * 4; + *pal++ = r * 4; + } + } + + _fileStream->seek(startPos + len); + free(chunk); +} + +} // End of namespace Graphics |