739 lines
19 KiB
C++
739 lines
19 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/endian.h"
|
|
#include "common/events.h"
|
|
#include "common/file.h"
|
|
#include "common/rational.h"
|
|
#include "common/system.h"
|
|
#include "common/timer.h"
|
|
#include "common/memstream.h"
|
|
|
|
#include "audio/audiostream.h"
|
|
#include "audio/mixer.h"
|
|
#include "audio/decoders/raw.h"
|
|
|
|
#include "engines/grim/debug.h"
|
|
#include "engines/grim/movie/codecs/codec48.h"
|
|
#include "engines/grim/movie/codecs/blocky8.h"
|
|
#include "engines/grim/movie/codecs/blocky16.h"
|
|
#include "engines/grim/movie/codecs/smush_decoder.h"
|
|
#include "engines/grim/movie/codecs/vima.h"
|
|
|
|
namespace Grim {
|
|
|
|
#define ANNO_HEADER "MakeAnim animation type 'Bl16' parameters: "
|
|
#define BUFFER_SIZE 16385
|
|
#define SMUSH_SPEED 66667
|
|
|
|
bool SmushDecoder::_demo = false;
|
|
|
|
static uint16 smushDestTable[5786];
|
|
|
|
SmushDecoder::SmushDecoder() {
|
|
_file = nullptr;
|
|
|
|
_videoLooping = false;
|
|
_startPos = 0;
|
|
_frames = nullptr;
|
|
|
|
_videoTrack = nullptr;
|
|
_audioTrack = nullptr;
|
|
_videoPause = false;
|
|
}
|
|
|
|
SmushDecoder::~SmushDecoder() {
|
|
delete _videoTrack;
|
|
delete _audioTrack;
|
|
delete[] _frames;
|
|
}
|
|
|
|
void SmushDecoder::init() {
|
|
_videoTrack->init();
|
|
_audioTrack->init();
|
|
}
|
|
|
|
void SmushDecoder::initFrames() {
|
|
delete[] _frames;
|
|
_frames = new Frame[_videoTrack->getFrameCount()];
|
|
|
|
int seekPos = _file->pos();
|
|
int curFrame = -1;
|
|
_file->seek(_startPos, SEEK_SET);
|
|
while (curFrame < _videoTrack->getFrameCount() - 1) {
|
|
Frame &frame = _frames[++curFrame];
|
|
frame.frame = curFrame;
|
|
frame.pos = _file->pos();
|
|
frame.keyframe = false;
|
|
|
|
uint32 tag = _file->readUint32BE();
|
|
uint32 size;
|
|
if (tag == MKTAG('A', 'N', 'N', 'O')) {
|
|
size = _file->readUint32BE();
|
|
_file->seek(size, SEEK_CUR);
|
|
tag = _file->readUint32BE();
|
|
}
|
|
assert(tag == MKTAG('F', 'R', 'M', 'E'));
|
|
size = _file->readUint32BE();
|
|
|
|
while (size > 0) {
|
|
uint32 subType = _file->readUint32BE();
|
|
uint32 subSize = _file->readUint32BE();
|
|
int32 subPos = _file->pos();
|
|
|
|
if (subType == MKTAG('B', 'l', '1', '6')) {
|
|
_file->seek(18, SEEK_CUR);
|
|
if (_file->readByte() == 0) {
|
|
frame.keyframe = true;
|
|
}
|
|
}
|
|
|
|
size -= subSize + 8 + (subSize & 1);
|
|
_file->seek(subPos + subSize + (subSize & 1), SEEK_SET);
|
|
}
|
|
|
|
_file->seek(size, SEEK_CUR);
|
|
}
|
|
|
|
_file->seek(seekPos, SEEK_SET);
|
|
}
|
|
|
|
void SmushDecoder::close() {
|
|
VideoDecoder::close();
|
|
_audioTrack = nullptr;
|
|
_videoTrack = nullptr;
|
|
_videoLooping = false;
|
|
_startPos = 0;
|
|
delete[] _frames;
|
|
_frames = nullptr;
|
|
if (_file) {
|
|
delete _file;
|
|
_file = nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
bool SmushDecoder::readHeader() {
|
|
if (!_file) {
|
|
return false;
|
|
}
|
|
|
|
uint32 mainTag = _file->readUint32BE();
|
|
uint32 pos = _file->pos();
|
|
uint32 expectedTag = 0;
|
|
uint32 size = _file->readUint32BE(); // file-size
|
|
|
|
// Verify that we have the correct combination of headers.
|
|
if (mainTag == MKTAG('A', 'N', 'I', 'M')) { // Demo
|
|
expectedTag = MKTAG('A', 'H', 'D', 'R');
|
|
} else if (mainTag == MKTAG('S', 'A', 'N', 'M')) { // Retail
|
|
expectedTag = MKTAG('S', 'H', 'D', 'R');
|
|
} else {
|
|
error("Invalid SMUSH-header");
|
|
}
|
|
|
|
uint32 tag = _file->readUint32BE();
|
|
size = _file->readUint32BE();
|
|
pos = _file->pos();
|
|
|
|
assert(tag == expectedTag);
|
|
(void)expectedTag;
|
|
|
|
if (tag == MKTAG('A', 'H', 'D', 'R')) { // Demo
|
|
uint32 version = _file->readUint16LE();
|
|
uint16 nbFrames = _file->readUint16LE();
|
|
_file->readUint16BE(); // unknown
|
|
|
|
int width = -1;
|
|
int height = -1;
|
|
_videoLooping = false;
|
|
_startPos = 0;
|
|
|
|
_videoTrack = new SmushVideoTrack(width, height, SMUSH_SPEED, nbFrames, false);
|
|
_videoTrack->_x = -1;
|
|
_videoTrack->_y = -1;
|
|
addTrack(_videoTrack);
|
|
_file->read(_videoTrack->getPal(), 0x300);
|
|
|
|
int audioRate = 11025;
|
|
if (version == 2) {
|
|
|
|
_file->readUint32LE(); // framerate
|
|
_file->readUint32LE();
|
|
audioRate = _file->readUint32LE();
|
|
}
|
|
|
|
_file->readUint32BE();
|
|
_file->readUint32BE();
|
|
_audioTrack = new SmushAudioTrack(getSoundType(), false, audioRate, 2);
|
|
addTrack(_audioTrack);
|
|
return true;
|
|
|
|
} else if (tag == MKTAG('S', 'H', 'D', 'R')) { // Retail
|
|
_file->readUint16LE();
|
|
uint16 nbFrames = _file->readUint32LE();
|
|
_file->readUint16LE();
|
|
int width = _file->readUint16LE();
|
|
int height = _file->readUint16LE();
|
|
_file->readUint16LE();
|
|
int frameRate = _file->readUint32LE();
|
|
|
|
int16 flags = _file->readUint16LE();
|
|
// Output information for checking out the flags
|
|
if (Debug::isChannelEnabled(Debug::Movie | Debug::Info)) {
|
|
warning("SMUSH Flags:");
|
|
for (int i = 0; i < 16; i++) {
|
|
warning(" %d", (flags & (1 << i)) != 0);
|
|
}
|
|
}
|
|
|
|
_file->seek(pos + size + (size & 1), SEEK_SET);
|
|
|
|
_videoLooping = true;
|
|
// If the video is NOT looping, setLooping will set the speed to the proper value
|
|
_videoTrack = new SmushVideoTrack(width, height, frameRate, nbFrames, true);
|
|
addTrack(_videoTrack);
|
|
return handleFramesHeader();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SmushDecoder::handleFramesHeader() {
|
|
uint32 tag;
|
|
int32 size;
|
|
int pos = 0;
|
|
int freq = 0;
|
|
int channels = 0;
|
|
|
|
tag = _file->readUint32BE();
|
|
if (tag != MKTAG('F', 'L', 'H', 'D')) {
|
|
return false;
|
|
}
|
|
size = _file->readUint32BE();
|
|
byte *f_header = new byte[size];
|
|
_file->read(f_header, size);
|
|
|
|
do {
|
|
if (READ_BE_UINT32(f_header + pos) == MKTAG('B', 'l', '1', '6')) {
|
|
pos += READ_BE_UINT32(f_header + pos + 4) + 8;
|
|
} else if (READ_BE_UINT32(f_header + pos) == MKTAG('W', 'a', 'v', 'e')) {
|
|
freq = READ_LE_UINT32(f_header + pos + 8);
|
|
channels = READ_LE_UINT32(f_header + pos + 12);
|
|
pos += 20;
|
|
} else {
|
|
error("SmushDecoder::handleFramesHeader() unknown tag");
|
|
}
|
|
} while (pos < size);
|
|
delete[] f_header;
|
|
|
|
_audioTrack = new SmushAudioTrack(getSoundType(), true, freq, channels);
|
|
addTrack(_audioTrack);
|
|
return true;
|
|
}
|
|
|
|
bool SmushDecoder::loadStream(Common::SeekableReadStream *stream) {
|
|
close();
|
|
|
|
_file = stream;
|
|
|
|
// Load the video
|
|
if (!readHeader()) {
|
|
warning("Failure loading SMUSH-file");
|
|
return false;
|
|
}
|
|
|
|
_startPos = _file->pos();
|
|
|
|
init();
|
|
return true;
|
|
}
|
|
|
|
const Graphics::Surface *SmushDecoder::decodeNextFrame() {
|
|
handleFrame();
|
|
|
|
// We might be interested in getting the last frame even after the video ends:
|
|
if (endOfVideo()) {
|
|
return _videoTrack->decodeNextFrame();
|
|
}
|
|
return VideoDecoder::decodeNextFrame();
|
|
}
|
|
|
|
void SmushDecoder::setLooping(bool l) {
|
|
_videoLooping = l;
|
|
|
|
if (!_videoLooping) {
|
|
_videoTrack->setMsPerFrame(SMUSH_SPEED);
|
|
}
|
|
}
|
|
|
|
void SmushDecoder::handleFrame() {
|
|
uint32 tag;
|
|
int32 size;
|
|
|
|
if (isPaused()) {
|
|
return;
|
|
}
|
|
|
|
if (_videoTrack->endOfTrack()) { // Looping is handled outside, by rewinding the video.
|
|
_audioTrack->stop(); // HACK: Avoids the movie playing past the last frame
|
|
// pauseVideo(true);
|
|
return;
|
|
}
|
|
|
|
tag = _file->readUint32BE();
|
|
size = _file->readUint32BE();
|
|
if (tag == MKTAG('A', 'N', 'N', 'O')) {
|
|
char *anno;
|
|
byte *data;
|
|
|
|
data = new byte[size];
|
|
_file->read(data, size);
|
|
anno = (char *)data;
|
|
if (strncmp(anno, ANNO_HEADER, sizeof(ANNO_HEADER) - 1) == 0) {
|
|
//char *annoData = anno + sizeof(ANNO_HEADER);
|
|
|
|
// Examples:
|
|
// Water streaming around boat from Manny's balcony
|
|
// MakeAnim animation type 'Bl16' parameters: 10000;12000;100;1;0;0;0;0;25;0;
|
|
// Water in front of the Blue Casket
|
|
// MakeAnim animation type 'Bl16' parameters: 20000;25000;100;1;0;0;0;0;25;0;
|
|
// Scrimshaw exterior:
|
|
// MakeAnim animation type 'Bl16' parameters: 6000;8000;100;0;0;0;0;0;2;0;
|
|
// Lola engine room (loops a limited number of times?):
|
|
// MakeAnim animation type 'Bl16' parameters: 6000;8000;90;1;0;0;0;0;2;0;
|
|
Debug::debug(Debug::Movie, "Announcement data: %s\n", anno);
|
|
// It looks like the announcement data is actually for setting some of the
|
|
// header parameters, not for any looping purpose
|
|
} else {
|
|
Debug::debug(Debug::Movie, "Announcement header not understood: %s\n", anno);
|
|
}
|
|
delete[] anno;
|
|
tag = _file->readUint32BE();
|
|
size = _file->readUint32BE();
|
|
}
|
|
|
|
assert(tag == MKTAG('F', 'R', 'M', 'E'));
|
|
handleFRME(_file, size);
|
|
|
|
_videoTrack->finishFrame();
|
|
}
|
|
|
|
void SmushDecoder::handleFRME(Common::SeekableReadStream *stream, uint32 size) {
|
|
int blockSize = size;
|
|
|
|
byte *block = new byte[size];
|
|
stream->read(block, size);
|
|
|
|
Common::MemoryReadStream *memStream = new Common::MemoryReadStream(block, size, DisposeAfterUse::NO);
|
|
while (size > 0) {
|
|
uint32 subType = memStream->readUint32BE();
|
|
uint32 subSize = memStream->readUint32BE();
|
|
uint32 subPos = memStream->pos();
|
|
|
|
switch (subType) {
|
|
// Retail only:
|
|
case MKTAG('B', 'l', '1', '6'):
|
|
_videoTrack->handleBlocky16(memStream, subSize);
|
|
break;
|
|
case MKTAG('W', 'a', 'v', 'e'):
|
|
_audioTrack->handleVIMA(memStream, blockSize);
|
|
break;
|
|
// Demo only:
|
|
case MKTAG('F', 'O', 'B', 'J'):
|
|
_videoTrack->handleFrameObject(memStream, subSize);
|
|
break;
|
|
case MKTAG('I', 'A', 'C', 'T'):
|
|
_audioTrack->handleIACT(memStream, subSize);
|
|
break;
|
|
case MKTAG('X', 'P', 'A', 'L'):
|
|
_videoTrack->handleDeltaPalette(memStream, subSize);
|
|
break;
|
|
default:
|
|
Debug::error(Debug::Movie, "SmushDecoder::handleFrame() unknown tag");
|
|
}
|
|
size -= subSize + 8 + (subSize & 1);
|
|
memStream->seek(subPos + subSize + (subSize & 1), SEEK_SET);
|
|
}
|
|
delete memStream;
|
|
delete[] block;
|
|
}
|
|
|
|
bool SmushDecoder::rewind() {
|
|
return seekToFrame(0);
|
|
}
|
|
|
|
bool SmushDecoder::seekIntern(const Audio::Timestamp &time) {
|
|
int32 wantedFrame = (uint32)((time.msecs() / 1000.0f) * _videoTrack->getFrameRate().toDouble());
|
|
if (wantedFrame != 0) {
|
|
Debug::debug(Debug::Movie, "Seek to time: %d, frame: %d", time.msecs(), wantedFrame);
|
|
Debug::debug(Debug::Movie, "Current frame: %d", _videoTrack->getCurFrame());
|
|
}
|
|
|
|
if (wantedFrame > _videoTrack->getFrameCount()) {
|
|
return false;
|
|
}
|
|
|
|
if (!_frames) {
|
|
initFrames();
|
|
}
|
|
|
|
// Track down the keyframe
|
|
int keyframe = 0;
|
|
for (int i = wantedFrame; i >= 0; --i) {
|
|
if (_frames[i].keyframe) {
|
|
keyframe = i;
|
|
break;
|
|
}
|
|
}
|
|
_videoTrack->setFrameStart(keyframe);
|
|
|
|
// VIMA frames are 50 frames ahead of time, so we have to make sure we have 50 frames
|
|
// of audio before the wantedFrame. Here we use 51 to have a bit of safe margin
|
|
if (wantedFrame - keyframe < 51) {
|
|
keyframe = wantedFrame - 51;
|
|
}
|
|
if (keyframe < 0) {
|
|
keyframe = 0;
|
|
}
|
|
|
|
_file->seek(_frames[keyframe].pos, SEEK_SET);
|
|
_videoTrack->setCurFrame(keyframe - 1);
|
|
|
|
while (_videoTrack->getCurFrame() < wantedFrame - 1) {
|
|
decodeNextFrame();
|
|
}
|
|
|
|
// As said, VIMA is 50 frames ahead of time. Every frame it pushes 1470 samples, and 50 * 1470 = 73500.
|
|
// The first frame, instead of 1470, it pushes 73500 samples to have this 50-frames-time.
|
|
// So if we have used frame 0 as keyframe we can remove safely time * rate samples, and we will
|
|
// still have the 50 frames margin. If we have used a later frame as keyframe we don't have the 73500
|
|
// samples pushed the first frame, so we have to be careful not to remove too much data,
|
|
// otherwise the audio will start at a later point. (72030 == 73500 - 1470)
|
|
int offset = (keyframe == 0 ? 0 : 72030);
|
|
|
|
// Skip decoded audio between the keyframe and the target frame
|
|
Audio::Timestamp delay = 0;
|
|
if (_videoTrack->getCurFrame() > 0) {
|
|
delay = _videoTrack->getFrameTime(_videoTrack->getCurFrame());
|
|
}
|
|
if (keyframe > 0) {
|
|
delay = delay - _videoTrack->getFrameTime(keyframe);
|
|
}
|
|
|
|
int32 sampleCount = (delay.msecs() / 1000.f) * _audioTrack->getRate() - offset;
|
|
_audioTrack->skipSamples(sampleCount);
|
|
|
|
VideoDecoder::seekIntern(time);
|
|
return true;
|
|
}
|
|
|
|
SmushDecoder::SmushVideoTrack::SmushVideoTrack(int width, int height, int fps, int numFrames, bool is16Bit) {
|
|
if (!is16Bit) { // Demo
|
|
_format = Graphics::PixelFormat::createFormatCLUT8();
|
|
_codec48 = new Codec48Decoder();
|
|
_blocky8 = new Blocky8();
|
|
_blocky16 = nullptr;
|
|
} else {
|
|
_format = Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
|
|
_codec48 = nullptr;
|
|
_blocky8 = nullptr;
|
|
_blocky16 = new Blocky16();
|
|
_blocky16->init(width, height);
|
|
}
|
|
_width = width;
|
|
_height = height;
|
|
_nbframes = numFrames;
|
|
_is16Bit = is16Bit;
|
|
_x = 0;
|
|
_y = 0;
|
|
setMsPerFrame(fps);
|
|
_curFrame = 0;
|
|
for (int i = 0; i < 0x300; i++) {
|
|
_palette[i] = 0;
|
|
_deltaPal[i] = 0;
|
|
_dirtyPalette = false;
|
|
}
|
|
_frameStart = 0;
|
|
}
|
|
|
|
SmushDecoder::SmushVideoTrack::~SmushVideoTrack() {
|
|
delete _codec48;
|
|
delete _blocky8;
|
|
delete _blocky16;
|
|
_surface.free();
|
|
}
|
|
|
|
void SmushDecoder::SmushVideoTrack::init() {
|
|
_curFrame = -1;
|
|
_frameStart = -1;
|
|
if (_is16Bit) { // Retail only
|
|
_surface.create(_width, _height, _format);
|
|
}
|
|
}
|
|
|
|
void SmushDecoder::SmushVideoTrack::finishFrame() {
|
|
_curFrame++;
|
|
}
|
|
|
|
void SmushDecoder::SmushVideoTrack::setFrameStart(int frame) {
|
|
_frameStart = frame - 1;
|
|
}
|
|
|
|
void SmushDecoder::SmushVideoTrack::handleBlocky16(Common::SeekableReadStream *stream, uint32 size) {
|
|
if (_curFrame < _frameStart) {
|
|
return;
|
|
}
|
|
|
|
assert(_is16Bit);
|
|
byte *ptr = new byte[size];
|
|
stream->read(ptr, size);
|
|
|
|
_blocky16->decode((byte *)_surface.getPixels(), ptr);
|
|
delete[] ptr;
|
|
}
|
|
|
|
void SmushDecoder::SmushVideoTrack::handleFrameObject(Common::SeekableReadStream *stream, uint32 size) {
|
|
if (_curFrame < _frameStart) {
|
|
return;
|
|
}
|
|
|
|
assert(!_is16Bit);
|
|
assert(size >= 14);
|
|
byte codec = stream->readByte();
|
|
assert(codec == 47 || codec == 48);
|
|
/* byte codecParam = */ stream->readByte();
|
|
_x = stream->readSint16LE();
|
|
_y = stream->readSint16LE();
|
|
uint16 width = stream->readUint16LE();
|
|
uint16 height = stream->readUint16LE();
|
|
if (width != _width || height != _height) {
|
|
_width = width;
|
|
_height = height;
|
|
_surface.create(_width, _height, _format);
|
|
_codec48->init(_width, _height);
|
|
_blocky8->init(_width, _height);
|
|
}
|
|
stream->readUint16LE();
|
|
stream->readUint16LE();
|
|
|
|
size -= 14;
|
|
byte *ptr = new byte[size];
|
|
stream->read(ptr, size);
|
|
|
|
if (codec == 47) {
|
|
_blocky8->decode((byte *)_surface.getPixels(), ptr);
|
|
} else if (codec == 48) {
|
|
_codec48->decode((byte *)_surface.getPixels(), ptr);
|
|
}
|
|
delete[] ptr;
|
|
}
|
|
|
|
static byte delta_color(byte org_color, int16 delta_color) {
|
|
int t = (org_color * 129 + delta_color) / 128;
|
|
return CLIP(t, 0, 255);
|
|
}
|
|
|
|
void SmushDecoder::SmushVideoTrack::handleDeltaPalette(Common::SeekableReadStream *stream, int32 size) {
|
|
if (size == 0x300 * 3 + 4) {
|
|
stream->seek(4, SEEK_CUR);
|
|
for (int i = 0; i < 0x300; i++) {
|
|
_deltaPal[i] = stream->readUint16LE();
|
|
}
|
|
stream->read(_palette, 0x300);
|
|
_dirtyPalette = true;
|
|
} else if (size == 6) {
|
|
for (int i = 0; i < 0x300; i++) {
|
|
_palette[i] = delta_color(_palette[i], _deltaPal[i]);
|
|
}
|
|
_dirtyPalette = true;
|
|
} else {
|
|
error("SmushDecoder::handleDeltaPalette() Wrong size for DeltaPalette");
|
|
}
|
|
}
|
|
|
|
Graphics::Surface *SmushDecoder::SmushVideoTrack::decodeNextFrame() {
|
|
return &_surface;
|
|
}
|
|
|
|
void SmushDecoder::SmushVideoTrack::setMsPerFrame(int ms) {
|
|
_frameRate = Common::Rational(1000000, ms);
|
|
}
|
|
SmushDecoder::SmushAudioTrack::SmushAudioTrack(Audio::Mixer::SoundType soundType, bool isVima, int freq, int channels) :
|
|
AudioTrack(soundType) {
|
|
_isVima = isVima;
|
|
_channels = channels;
|
|
_freq = freq;
|
|
_queueStream = Audio::makeQueuingAudioStream(_freq, (_channels == 2));
|
|
_IACTpos = 0;
|
|
}
|
|
|
|
SmushDecoder::SmushAudioTrack::~SmushAudioTrack() {
|
|
delete _queueStream;
|
|
}
|
|
|
|
void SmushDecoder::SmushAudioTrack::init() {
|
|
_IACTpos = 0;
|
|
|
|
if (_isVima) {
|
|
vimaInit(smushDestTable);
|
|
}
|
|
}
|
|
|
|
void SmushDecoder::SmushAudioTrack::handleVIMA(Common::SeekableReadStream *stream, uint32 size) {
|
|
if (size < 8)
|
|
return;
|
|
int decompressedSize = stream->readUint32BE();
|
|
if (decompressedSize == MKTAG('P', 'S', 'A', 'D')) {
|
|
decompressedSize = stream->readUint32BE();
|
|
if (decompressedSize > (int)size - 8)
|
|
decompressedSize = size - 8;
|
|
if (decompressedSize < 10)
|
|
return;
|
|
stream->skip(10);
|
|
decompressedSize -= 10;
|
|
byte *src = (byte *)malloc(decompressedSize);
|
|
stream->read(src, decompressedSize);
|
|
|
|
int flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
|
|
if (_channels == 2) {
|
|
flags |= Audio::FLAG_STEREO;
|
|
}
|
|
|
|
if (!_queueStream) {
|
|
_queueStream = Audio::makeQueuingAudioStream(_freq, (_channels == 2));
|
|
}
|
|
_queueStream->queueBuffer(src, decompressedSize, DisposeAfterUse::YES, flags);
|
|
|
|
return;
|
|
}
|
|
if (decompressedSize < 0) {
|
|
stream->readUint32BE();
|
|
decompressedSize = stream->readUint32BE();
|
|
}
|
|
|
|
byte *src = new byte[size];
|
|
stream->read(src, size);
|
|
|
|
// this will be deleted using free() by the stream, so allocate it using malloc().
|
|
int16 *dst = (int16 *)malloc(decompressedSize * _channels * 2);
|
|
decompressVima(src, dst, decompressedSize * _channels * 2, smushDestTable, true);
|
|
|
|
int flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
|
|
if (_channels == 2) {
|
|
flags |= Audio::FLAG_STEREO;
|
|
}
|
|
|
|
if (!_queueStream) {
|
|
_queueStream = Audio::makeQueuingAudioStream(_freq, (_channels == 2));
|
|
}
|
|
_queueStream->queueBuffer((byte *)dst, decompressedSize * _channels * 2, DisposeAfterUse::YES, flags);
|
|
delete[] src;
|
|
}
|
|
|
|
void SmushDecoder::SmushAudioTrack::handleIACT(Common::SeekableReadStream *stream, int32 size) {
|
|
byte *src = new byte[size];
|
|
stream->read(src, size);
|
|
|
|
int32 bsize = size - 18;
|
|
const byte *d_src = src + 18;
|
|
|
|
while (bsize > 0) {
|
|
if (_IACTpos >= 2) {
|
|
int32 len = READ_BE_UINT16(_IACToutput) + 2;
|
|
len -= _IACTpos;
|
|
if (len > bsize) {
|
|
memcpy(_IACToutput + _IACTpos, d_src, bsize);
|
|
_IACTpos += bsize;
|
|
bsize = 0;
|
|
} else {
|
|
// this will be deleted using free() by the stream, so allocate it using malloc().
|
|
byte *output_data = (byte *)malloc(4096);
|
|
memcpy(_IACToutput + _IACTpos, d_src, len);
|
|
byte *dst = output_data;
|
|
byte *d_src2 = _IACToutput;
|
|
d_src2 += 2;
|
|
int32 count = 1024;
|
|
byte variable1 = *d_src2++;
|
|
byte variable2 = variable1 / 16;
|
|
variable1 &= 0x0f;
|
|
do {
|
|
byte value;
|
|
value = *(d_src2++);
|
|
if (value == 0x80) {
|
|
*dst++ = *d_src2++;
|
|
*dst++ = *d_src2++;
|
|
} else {
|
|
int16 val = (int8)value << variable2;
|
|
*dst++ = val >> 8;
|
|
*dst++ = (byte)(val);
|
|
}
|
|
value = *(d_src2++);
|
|
if (value == 0x80) {
|
|
*dst++ = *d_src2++;
|
|
*dst++ = *d_src2++;
|
|
} else {
|
|
int16 val = (int8)value << variable1;
|
|
*dst++ = val >> 8;
|
|
*dst++ = (byte)(val);
|
|
}
|
|
} while (--count);
|
|
|
|
if (!_queueStream) {
|
|
_queueStream = Audio::makeQueuingAudioStream(22050, true);
|
|
}
|
|
_queueStream->queueBuffer(output_data, 0x1000, DisposeAfterUse::YES, Audio::FLAG_STEREO | Audio::FLAG_16BITS);
|
|
|
|
bsize -= len;
|
|
d_src += len;
|
|
_IACTpos = 0;
|
|
}
|
|
} else {
|
|
if (bsize > 1 && _IACTpos == 0) {
|
|
*(_IACToutput + 0) = *d_src++;
|
|
_IACTpos = 1;
|
|
bsize--;
|
|
}
|
|
*(_IACToutput + _IACTpos) = *d_src++;
|
|
_IACTpos++;
|
|
bsize--;
|
|
}
|
|
}
|
|
delete[] src;
|
|
}
|
|
|
|
bool SmushDecoder::SmushAudioTrack::seek(const Audio::Timestamp &time) {
|
|
return true;
|
|
}
|
|
|
|
void SmushDecoder::SmushAudioTrack::skipSamples(int sampleCount) {
|
|
if (sampleCount <= 0)
|
|
return;
|
|
|
|
if (_queueStream->isStereo())
|
|
sampleCount *= 2;
|
|
|
|
int16 *tempBuffer = new int16[sampleCount];
|
|
_queueStream->readBuffer(tempBuffer, sampleCount);
|
|
delete[] tempBuffer;
|
|
}
|
|
|
|
} // end of namespace Grim
|