VIDEO: Rewrite SmackerDecoder to use the new API

This commit is contained in:
Matthew Hoops 2012-07-23 21:04:51 -04:00
parent 3a780a63db
commit a652f6669e
11 changed files with 346 additions and 272 deletions

View File

@ -415,7 +415,7 @@ void MoviePlayerDXA::updateBalance() {
MoviePlayerSMK::MoviePlayerSMK(AGOSEngine_Feeble *vm, const char *name)
: MoviePlayer(vm), SmackerDecoder(vm->_mixer) {
: MoviePlayer(vm), SmackerDecoder() {
debug(0, "Creating SMK cutscene player");
memset(baseName, 0, sizeof(baseName));
@ -431,12 +431,12 @@ bool MoviePlayerSMK::load() {
if (!loadStream(videoStream))
error("Failed to load video stream from file %s", videoName.c_str());
start();
debug(0, "Playing video %s", videoName.c_str());
CursorMan.showMouse(false);
_firstFrameOffset = _fileStream->pos();
return true;
}
@ -477,10 +477,8 @@ void MoviePlayerSMK::handleNextFrame() {
}
void MoviePlayerSMK::nextFrame() {
if (_vm->_interactiveVideo == TYPE_LOOPING && endOfVideo()) {
_fileStream->seek(_firstFrameOffset);
_curFrame = -1;
}
if (_vm->_interactiveVideo == TYPE_LOOPING && endOfVideo())
rewind();
if (!endOfVideo()) {
decodeNextFrame();

View File

@ -67,9 +67,6 @@ protected:
virtual void handleNextFrame();
virtual bool processFrame() = 0;
virtual void startSound() {}
protected:
uint32 _firstFrameOffset;
};
class MoviePlayerDXA : public MoviePlayer, Video::DXADecoder {
@ -93,6 +90,7 @@ private:
bool processFrame();
void startSound();
void copyFrameToBuffer(byte *dst, uint x, uint y, uint pitch);
uint32 _firstFrameOffset;
};
class MoviePlayerSMK : public MoviePlayer, Video::SmackerDecoder {

View File

@ -92,7 +92,7 @@ int Scene::FTA2EndProc(FTA2Endings whichEnding) {
}
void Scene::playMovie(const char *filename) {
Video::SmackerDecoder *smkDecoder = new Video::SmackerDecoder(_vm->_mixer);
Video::SmackerDecoder *smkDecoder = new Video::SmackerDecoder();
if (!smkDecoder->loadFile(filename))
return;
@ -101,6 +101,8 @@ void Scene::playMovie(const char *filename) {
uint16 y = (g_system->getHeight() - smkDecoder->getHeight()) / 2;
bool skipVideo = false;
smkDecoder->start();
while (!_vm->shouldQuit() && !smkDecoder->endOfVideo() && !skipVideo) {
if (smkDecoder->needsUpdate()) {
const Graphics::Surface *frame = smkDecoder->decodeNextFrame();

View File

@ -40,7 +40,10 @@ MoviePlayer::MoviePlayer(ScummEngine_v90he *vm, Audio::Mixer *mixer) : _vm(vm) {
_video = new Video::BinkDecoder();
else
#endif
_video = new Video::SmackerDecoder(mixer);
{
_video = new Video::SmackerDecoder();
((Video::AdvancedVideoDecoder *)_video)->start();
}
_flags = 0;
_wizResNum = 0;

View File

@ -179,6 +179,13 @@ bool MoviePlayer::load(uint32 id) {
break;
case kVideoDecoderSMK:
filename = Common::String::format("%s.smk", sequenceList[id]);
if (_decoder->loadFile(filename)) {
((Video::AdvancedVideoDecoder *)_decoder)->start(); // TODO: Remove after new API is complete
return true;
} else {
return false;
}
break;
case kVideoDecoderPSX:
filename = Common::String::format("%s.str", (_vm->_systemVars.isDemo) ? sequenceList[id] : sequenceListPSX[id]);
@ -547,7 +554,7 @@ MoviePlayer *makeMoviePlayer(uint32 id, SwordEngine *vm, Text *textMan, ResMan *
filename = Common::String::format("%s.smk", sequenceList[id]);
if (Common::File::exists(filename)) {
Video::SmackerDecoder *smkDecoder = new Video::SmackerDecoder(snd);
Video::SmackerDecoder *smkDecoder = new Video::SmackerDecoder();
return new MoviePlayer(vm, textMan, resMan, snd, system, bgSoundHandle, smkDecoder, kVideoDecoderSMK);
}

View File

@ -89,6 +89,13 @@ bool MoviePlayer::load(const char *name) {
break;
case kVideoDecoderSMK:
filename = Common::String::format("%s.smk", name);
if (_decoder->loadFile(filename)) {
((Video::AdvancedVideoDecoder *)_decoder)->start(); // TODO: Remove after new API is complete
return true;
} else {
return false;
}
break;
case kVideoDecoderPSX:
filename = Common::String::format("%s.str", name);
@ -442,7 +449,7 @@ MoviePlayer *makeMoviePlayer(const char *name, Sword2Engine *vm, Audio::Mixer *s
filename = Common::String::format("%s.smk", name);
if (Common::File::exists(filename)) {
Video::SmackerDecoder *smkDecoder = new Video::SmackerDecoder(snd);
Video::SmackerDecoder *smkDecoder = new Video::SmackerDecoder();
return new MoviePlayer(vm, snd, system, bgSoundHandle, smkDecoder, kVideoDecoderSMK);
}

View File

@ -33,6 +33,10 @@
namespace Toon {
ToonstruckSmackerDecoder::ToonstruckSmackerDecoder() : Video::SmackerDecoder() {
_lowRes = false;
}
void ToonstruckSmackerDecoder::handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize) {
debugC(6, kDebugMovie, "handleAudioTrack(%d, %d, %d)", track, chunkSize, unpackedSize);
@ -40,33 +44,21 @@ void ToonstruckSmackerDecoder::handleAudioTrack(byte track, uint32 chunkSize, ui
/* uint16 width = */ _fileStream->readUint16LE();
uint16 height = _fileStream->readUint16LE();
_lowRes = (height == getHeight() / 2);
} else
} else {
Video::SmackerDecoder::handleAudioTrack(track, chunkSize, unpackedSize);
}
bool ToonstruckSmackerDecoder::loadFile(const Common::String &filename) {
debugC(1, kDebugMovie, "loadFile(%s)", filename.c_str());
_lowRes = false;
if (Video::SmackerDecoder::loadFile(filename)) {
if (_surface->h == 200) {
if (_surface) {
_surface->free();
delete _surface;
}
_surface = new Graphics::Surface();
_surface->create(640, 400, Graphics::PixelFormat::createFormatCLUT8());
_header.flags = 4;
}
return true;
}
return false;
}
ToonstruckSmackerDecoder::ToonstruckSmackerDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) : Video::SmackerDecoder(mixer, soundType) {
bool ToonstruckSmackerDecoder::loadStream(Common::SeekableReadStream *stream) {
if (!Video::SmackerDecoder::loadStream(stream))
return false;
_lowRes = false;
return true;
}
Video::SmackerDecoder::SmackerVideoTrack *ToonstruckSmackerDecoder::createVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature) const {
return Video::SmackerDecoder::createVideoTrack(width, height, frameCount, frameRate, (height == 200) ? 4 : flags, signature);
}
// decoder is deallocated with Movie destruction i.e. new ToonstruckSmackerDecoder is needed
@ -103,6 +95,9 @@ void Movie::play(const Common::String &video, int32 flags) {
bool Movie::playVideo(bool isFirstIntroVideo) {
debugC(1, kDebugMovie, "playVideo(isFirstIntroVideo: %d)", isFirstIntroVideo);
_decoder->start();
while (!_vm->shouldQuit() && !_decoder->endOfVideo()) {
if (_decoder->needsUpdate()) {
const Graphics::Surface *frame = _decoder->decodeNextFrame();

View File

@ -30,13 +30,17 @@ namespace Toon {
class ToonstruckSmackerDecoder : public Video::SmackerDecoder {
public:
ToonstruckSmackerDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType = Audio::Mixer::kSFXSoundType);
virtual ~ToonstruckSmackerDecoder() {}
void handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize);
bool loadFile(const Common::String &filename);
ToonstruckSmackerDecoder();
bool loadStream(Common::SeekableReadStream *stream);
bool isLowRes() { return _lowRes; }
protected:
bool _lowRes;
void handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize);
SmackerVideoTrack *createVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature) const;
private:
bool _lowRes;
};
class Movie {

View File

@ -51,7 +51,7 @@ void ToonEngine::init() {
_currentScriptRegion = 0;
_resources = new Resources(this);
_animationManager = new AnimationManager(this);
_moviePlayer = new Movie(this, new ToonstruckSmackerDecoder(_mixer));
_moviePlayer = new Movie(this, new ToonstruckSmackerDecoder());
_hotspots = new Hotspots(this);
_mainSurface = new Graphics::Surface();

View File

@ -204,8 +204,7 @@ BigHuffmanTree::BigHuffmanTree(Common::BitStream &bs, int allocSize)
delete _hiBytes;
}
BigHuffmanTree::~BigHuffmanTree()
{
BigHuffmanTree::~BigHuffmanTree() {
delete[] _tree;
}
@ -278,24 +277,17 @@ uint32 BigHuffmanTree::getCode(Common::BitStream &bs) {
return v;
}
SmackerDecoder::SmackerDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType)
: _audioStarted(false), _audioStream(0), _mixer(mixer), _soundType(soundType) {
_surface = 0;
SmackerDecoder::SmackerDecoder(Audio::Mixer::SoundType soundType) : _soundType(soundType) {
_fileStream = 0;
_dirtyPalette = false;
_firstFrameStart = 0;
_frameTypes = 0;
_frameSizes = 0;
}
SmackerDecoder::~SmackerDecoder() {
close();
}
uint32 SmackerDecoder::getTime() const {
if (_audioStream && _audioStarted)
return _mixer->getSoundElapsedTime(_audioHandle);
return FixedRateVideoDecoder::getTime();
}
bool SmackerDecoder::loadStream(Common::SeekableReadStream *stream) {
close();
@ -309,16 +301,17 @@ bool SmackerDecoder::loadStream(Common::SeekableReadStream *stream) {
uint32 width = _fileStream->readUint32LE();
uint32 height = _fileStream->readUint32LE();
_frameCount = _fileStream->readUint32LE();
int32 frameRate = _fileStream->readSint32LE();
uint32 frameCount = _fileStream->readUint32LE();
int32 frameDelay = _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);
// 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;
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.
@ -328,6 +321,9 @@ bool SmackerDecoder::loadStream(Common::SeekableReadStream *stream) {
// 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
@ -374,92 +370,78 @@ bool SmackerDecoder::loadStream(Common::SeekableReadStream *stream) {
warning("Unhandled Smacker v2 audio compression");
if (i == 0)
_audioStream = Audio::makeQueuingAudioStream(_header.audioInfo[0].sampleRate, _header.audioInfo[0].isStereo);
addTrack(new SmackerAudioTrack(_header.audioInfo[i], _soundType));
}
}
_header.dummy = _fileStream->readUint32LE();
_frameSizes = new uint32[_frameCount];
for (i = 0; i < _frameCount; ++i)
_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 = 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::BitStream8LSB bs(new Common::MemoryReadStream(huffmanTrees, _header.treesSize, DisposeAfterUse::YES), true);
videoTrack->readTrees(bs, _header.mMapSize, _header.mClrSize, _header.fullSize, _header.typeSize);
_MMapTree = new BigHuffmanTree(bs, _header.mMapSize);
_MClrTree = new BigHuffmanTree(bs, _header.mClrSize);
_FullTree = new BigHuffmanTree(bs, _header.fullSize);
_TypeTree = new BigHuffmanTree(bs, _header.typeSize);
_firstFrameStart = _fileStream->pos();
_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), Graphics::PixelFormat::createFormatCLUT8());
memset(_palette, 0, 3 * 256);
return true;
}
void SmackerDecoder::close() {
if (!_fileStream)
return;
if (_audioStream) {
if (_audioStarted) {
// The mixer will delete the stream.
_mixer->stopHandle(_audioHandle);
_audioStarted = false;
} else {
delete _audioStream;
}
_audioStream = 0;
}
AdvancedVideoDecoder::close();
delete _fileStream;
_fileStream = 0;
_surface->free();
delete _surface;
_surface = 0;
delete _MMapTree;
delete _MClrTree;
delete _FullTree;
delete _TypeTree;
delete[] _frameTypes;
_frameTypes = 0;
delete[] _frameSizes;
delete[] _frameTypes;
reset();
_frameSizes = 0;
}
const Graphics::Surface *SmackerDecoder::decodeNextFrame() {
bool SmackerDecoder::rewind() {
// Call the parent method to rewind the tracks first
// In particular, only videos without sound can be rewound
if (!AdvancedVideoDecoder::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();
_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;
}
if (_frameTypes[videoTrack->getCurFrame()] & 1)
videoTrack->unpackPalette(_fileStream);
// Load audio tracks
for (i = 0; i < 7; ++i) {
if (!(_frameTypes[_curFrame] & (2 << i)))
if (!(_frameTypes[videoTrack->getCurFrame()] & (2 << i)))
continue;
chunkSize = _fileStream->readUint32LE();
@ -475,29 +457,109 @@ const Graphics::Surface *SmackerDecoder::decodeNextFrame() {
handleAudioTrack(i, chunkSize, dataSizeUnpacked);
}
uint32 frameSize = _frameSizes[_curFrame] & ~3;
// uint32 remainder = _frameSizes[_curFrame] & 3;
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);
_frameData = (byte *)malloc(frameDataSize + 1);
byte *frameData = (byte *)malloc(frameDataSize + 1);
// Padding to keep the BigHuffmanTrees from reading past the data end
_frameData[frameDataSize] = 0x00;
frameData[frameDataSize] = 0x00;
_fileStream->read(_frameData, frameDataSize);
_fileStream->read(frameData, frameDataSize);
Common::BitStream8LSB bs(new Common::MemoryReadStream(_frameData, frameDataSize + 1, DisposeAfterUse::YES), true);
Common::BitStream8LSB bs(new Common::MemoryReadStream(frameData, frameDataSize + 1, DisposeAfterUse::YES), true);
videoTrack->decodeFrame(bs);
_fileStream->seek(startPos + frameSize);
}
void SmackerDecoder::handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize) {
if (_header.audioInfo[track].hasAudio && chunkSize > 0 && track == 0) {
// 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 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);
}
}
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::BitStream &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::BitStream &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 = _header.flags ? 2 : 1;
uint doubleY = _flags ? 2 : 1;
uint bw = getWidth() / 4;
uint bh = getHeight() / doubleY / 4;
@ -508,6 +570,7 @@ const Graphics::Surface *SmackerDecoder::decodeNextFrame() {
uint type, run, j, mode;
uint32 p1, p2, clr, map;
byte hi, lo;
uint i;
while (block < blocks) {
type = _TypeTree->getCode(bs);
@ -536,7 +599,7 @@ const Graphics::Surface *SmackerDecoder::decodeNextFrame() {
break;
case SMK_BLOCK_FULL:
// Smacker v2 has one mode, Smacker v4 has three
if (_header.signature == MKTAG('S','M','K','2')) {
if (_signature == MKTAG('S','M','K','2')) {
mode = 0;
} else {
// 00 - mode 0
@ -628,60 +691,75 @@ const Graphics::Surface *SmackerDecoder::decodeNextFrame() {
break;
}
}
_fileStream->seek(startPos + frameSize);
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 + 1);
// Padding to keep the SmallHuffmanTrees from reading past the data end
soundBuffer[chunkSize] = 0x00;
void SmackerDecoder::SmackerVideoTrack::unpackPalette(Common::SeekableReadStream *stream) {
uint startPos = stream->pos();
uint32 len = 4 * stream->readByte();
_fileStream->read(soundBuffer, chunkSize);
byte *chunk = (byte *)malloc(len);
stream->read(chunk, len);
byte *p = chunk;
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 + 1, 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;
byte oldPalette[3 * 256];
memcpy(oldPalette, _palette, 3 * 256);
_audioStream->queueBuffer(soundBuffer, chunkSize, DisposeAfterUse::YES, flags);
// The sound buffer will be deleted by QueuingAudioStream
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;
}
if (!_audioStarted) {
_mixer->playStream(_soundType, &_audioHandle, _audioStream, -1, getVolume(), getBalance());
_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);
}
stream->seek(startPos + len);
free(chunk);
_dirtyPalette = true;
}
void SmackerDecoder::queueCompressedBuffer(byte *buffer, uint32 bufferSize,
uint32 unpackedSize, int streamNum) {
SmackerDecoder::SmackerAudioTrack::SmackerAudioTrack(const AudioInfo &audioInfo, Audio::Mixer::SoundType soundType) :
_audioInfo(audioInfo), _soundType(soundType) {
_audioStream = Audio::makeQueuingAudioStream(_audioInfo.sampleRate, _audioInfo.isStereo);
}
SmackerDecoder::SmackerAudioTrack::~SmackerAudioTrack() {
delete _audioStream;
}
Audio::AudioStream *SmackerDecoder::SmackerAudioTrack::getAudioStream() const {
return _audioStream;
}
void SmackerDecoder::SmackerAudioTrack::queueCompressedBuffer(byte *buffer, uint32 bufferSize, uint32 unpackedSize) {
Common::BitStream8LSB audioBS(new Common::MemoryReadStream(buffer, bufferSize), true);
bool dataPresent = audioBS.getBit();
@ -689,9 +767,9 @@ void SmackerDecoder::queueCompressedBuffer(byte *buffer, uint32 bufferSize,
return;
bool isStereo = audioBS.getBit();
assert(isStereo == _header.audioInfo[streamNum].isStereo);
assert(isStereo == _audioInfo.isStereo);
bool is16Bits = audioBS.getBit();
assert(is16Bits == _header.audioInfo[streamNum].is16Bits);
assert(is16Bits == _audioInfo.is16Bits);
int numBytes = 1 * (isStereo ? 2 : 1) * (is16Bits ? 2 : 1);
@ -759,74 +837,21 @@ void SmackerDecoder::queueCompressedBuffer(byte *buffer, uint32 bufferSize,
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 (_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
if (_audioInfo.is16Bits)
flags |= Audio::FLAG_16BITS;
if (_audioInfo.isStereo)
flags |= Audio::FLAG_STEREO;
_audioStream->queueBuffer(buffer, bufferSize, DisposeAfterUse::YES, flags);
}
void SmackerDecoder::unpackPalette() {
uint startPos = _fileStream->pos();
uint32 len = 4 * _fileStream->readByte();
byte *chunk = (byte *)malloc(len);
_fileStream->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 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);
}
void SmackerDecoder::updateVolume() {
if (g_system->getMixer()->isSoundHandleActive(_audioHandle))
g_system->getMixer()->setChannelVolume(_audioHandle, getVolume());
}
void SmackerDecoder::updateBalance() {
if (g_system->getMixer()->isSoundHandleActive(_audioHandle))
g_system->getMixer()->setChannelBalance(_audioHandle, getBalance());
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

View File

@ -34,6 +34,7 @@ class QueuingAudioStream;
}
namespace Common {
class BitStream;
class SeekableReadStream;
}
@ -56,42 +57,72 @@ class BigHuffmanTree;
* - sword2
* - toon
*/
class SmackerDecoder : public FixedRateVideoDecoder {
class SmackerDecoder : public AdvancedVideoDecoder {
public:
SmackerDecoder(Audio::Mixer *mixer,
Audio::Mixer::SoundType soundType = Audio::Mixer::kSFXSoundType);
SmackerDecoder(Audio::Mixer::SoundType soundType = Audio::Mixer::kSFXSoundType);
virtual ~SmackerDecoder();
bool loadStream(Common::SeekableReadStream *stream);
virtual bool loadStream(Common::SeekableReadStream *stream);
void close();
bool isVideoLoaded() const { return _fileStream != 0; }
uint16 getWidth() const { return _surface->w; }
uint16 getHeight() const { return _surface->h; }
uint32 getFrameCount() const { return _frameCount; }
uint32 getTime() const;
const Graphics::Surface *decodeNextFrame();
Graphics::PixelFormat getPixelFormat() const { return Graphics::PixelFormat::createFormatCLUT8(); }
const byte *getPalette() { _dirtyPalette = false; return _palette; }
bool hasDirtyPalette() const { return _dirtyPalette; }
bool rewind();
protected:
void readNextPacket();
virtual void handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize);
protected:
class SmackerVideoTrack : public FixedRateVideoTrack {
public:
SmackerVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature);
~SmackerVideoTrack();
bool isRewindable() const { return true; }
bool rewind() { _curFrame = -1; return true; }
uint16 getWidth() const;
uint16 getHeight() const;
Graphics::PixelFormat getPixelFormat() const;
int getCurFrame() const { return _curFrame; }
int getFrameCount() const { return _frameCount; }
const Graphics::Surface *decodeNextFrame() { return _surface; }
const byte *getPalette() const { _dirtyPalette = false; return _palette; }
bool hasDirtyPalette() const { return _dirtyPalette; }
void readTrees(Common::BitStream &bs, uint32 mMapSize, uint32 mClrSize, uint32 fullSize, uint32 typeSize);
void increaseCurFrame() { _curFrame++; }
void decodeFrame(Common::BitStream &bs);
void unpackPalette(Common::SeekableReadStream *stream);
protected:
Common::Rational getFrameRate() const { return _frameRate; }
Graphics::Surface *_surface;
private:
Common::Rational _frameRate;
uint32 _flags, _signature;
byte _palette[3 * 256];
mutable bool _dirtyPalette;
int _curFrame;
uint32 _frameCount;
BigHuffmanTree *_MMapTree;
BigHuffmanTree *_MClrTree;
BigHuffmanTree *_FullTree;
BigHuffmanTree *_TypeTree;
// Possible runs of blocks
static uint getBlockRun(int index) { return (index <= 58) ? index + 1 : 128 << (index - 59); }
};
virtual SmackerVideoTrack *createVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature) const;
Common::SeekableReadStream *_fileStream;
// VideoDecoder API
void updateVolume();
void updateBalance();
// FixedRateVideoDecoder API
Common::Rational getFrameRate() const { return _frameRate; }
protected:
void unpackPalette();
// Possible runs of blocks
uint getBlockRun(int index) { return (index <= 58) ? index + 1 : 128 << (index - 59); }
void queueCompressedBuffer(byte *buffer, uint32 bufferSize, uint32 unpackedSize, int streamNum);
private:
enum AudioCompression {
kCompressionNone,
kCompressionDPCM,
@ -120,6 +151,25 @@ protected:
uint32 dummy;
} _header;
class SmackerAudioTrack : public AudioTrack {
public:
SmackerAudioTrack(const AudioInfo &audioInfo, Audio::Mixer::SoundType soundType);
~SmackerAudioTrack();
Audio::Mixer::SoundType getSoundType() const { return _soundType; }
void queueCompressedBuffer(byte *buffer, uint32 bufferSize, uint32 unpackedSize);
void queuePCM(byte *buffer, uint32 bufferSize);
protected:
Audio::AudioStream *getAudioStream() const;
private:
Audio::Mixer::SoundType _soundType;
Audio::QueuingAudioStream *_audioStream;
AudioInfo _audioInfo;
};
uint32 *_frameSizes;
// The FrameTypes section of a Smacker file contains an array of bytes, where
// the 8 bits of each byte describe the contents of the corresponding frame.
@ -127,25 +177,10 @@ protected:
// and so on), so there can be up to 7 different audio tracks. When the lowest bit
// (bit 0) is set, it denotes a frame that contains a palette record
byte *_frameTypes;
byte *_frameData;
// The RGB palette
byte _palette[3 * 256];
bool _dirtyPalette;
Common::Rational _frameRate;
uint32 _frameCount;
Graphics::Surface *_surface;
uint32 _firstFrameStart;
Audio::Mixer::SoundType _soundType;
Audio::Mixer *_mixer;
bool _audioStarted;
Audio::QueuingAudioStream *_audioStream;
Audio::SoundHandle _audioHandle;
BigHuffmanTree *_MMapTree;
BigHuffmanTree *_MClrTree;
BigHuffmanTree *_FullTree;
BigHuffmanTree *_TypeTree;
};
} // End of namespace Video