/* 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/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; }; SmallHuffmanTree::SmallHuffmanTree(Common::BitStreamMemory8LSB &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.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) { byte peek = bs.peekBits(MIN(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); 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(Common::BitStreamMemory8LSB &bs) { byte peek = bs.peekBits(MIN(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(); 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())); } } _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 ? 2 : 1), Graphics::PixelFormat::createFormatCLUT8()); _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(); // Height needs to be doubled if we have flags (Y-interlaced or Y-doubled) uint doubleY = _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; 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; } ++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; } ++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; } ++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); } } // End of namespace Video