mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-19 00:15:30 +00:00
CRYOMNI3D: Add HNM6 image and video codec
This also adds a Cryo APC decoder in shared code.
This commit is contained in:
parent
644103e012
commit
399e02e2a5
185
audio/decoders/apc.cpp
Normal file
185
audio/decoders/apc.cpp
Normal file
@ -0,0 +1,185 @@
|
||||
/* 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/inttypes.h"
|
||||
#include "common/ptr.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#include "audio/decoders/adpcm_intern.h"
|
||||
#include "audio/decoders/apc.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class APCStreamImpl : public APCStream {
|
||||
public:
|
||||
// These parameters are completely optional and only used in HNM videos to make sure data is consistent
|
||||
APCStreamImpl(uint sampleRate = uint(-1), byte stereo = byte(-1));
|
||||
|
||||
bool init(Common::SeekableReadStream &header) override;
|
||||
|
||||
// AudioStream API
|
||||
int readBuffer(int16 *buffer, const int numSamples) override { return _audStream->readBuffer(buffer, numSamples); }
|
||||
bool isStereo() const override { return _audStream->isStereo(); }
|
||||
int getRate() const override { return _audStream->getRate(); }
|
||||
bool endOfData() const override { return _audStream->endOfData(); }
|
||||
bool endOfStream() const override { return _audStream->endOfStream(); }
|
||||
|
||||
// PacketizedAudioStream API
|
||||
void queuePacket(Common::SeekableReadStream *data) override;
|
||||
void finish() override { _audStream->finish(); }
|
||||
|
||||
private:
|
||||
struct Status {
|
||||
int32 last;
|
||||
uint32 stepIndex;
|
||||
int16 step;
|
||||
};
|
||||
|
||||
FORCEINLINE static int16 decodeIMA(byte code, Status *status);
|
||||
|
||||
Common::ScopedPtr<QueuingAudioStream> _audStream;
|
||||
byte _stereo;
|
||||
uint32 _sampleRate;
|
||||
|
||||
Status _status[2];
|
||||
};
|
||||
|
||||
APCStreamImpl::APCStreamImpl(uint32 sampleRate, byte stereo) :
|
||||
_sampleRate(sampleRate), _stereo(stereo) {
|
||||
if (_sampleRate != uint32(-1) && _stereo != byte(-1)) {
|
||||
_audStream.reset(makeQueuingAudioStream(_sampleRate, _stereo));
|
||||
}
|
||||
}
|
||||
|
||||
bool APCStreamImpl::init(Common::SeekableReadStream &header) {
|
||||
// Header size
|
||||
if (header.size() < 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read magic and version at once
|
||||
byte magicVersion[12];
|
||||
if (header.read(magicVersion, sizeof(magicVersion)) != sizeof(magicVersion)) {
|
||||
return false;
|
||||
}
|
||||
if (memcmp(magicVersion, "CRYO_APC1.20", sizeof(magicVersion))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//uint32 num_samples = header.readUint32LE();
|
||||
header.skip(4);
|
||||
|
||||
uint32 samplerate = header.readUint32LE();
|
||||
if (_sampleRate != uint32(-1) && _sampleRate != samplerate) {
|
||||
error("Samplerate mismatch");
|
||||
return false;
|
||||
}
|
||||
_sampleRate = samplerate;
|
||||
|
||||
uint32 leftSample = header.readSint32LE();
|
||||
uint32 rightSample = header.readSint32LE();
|
||||
|
||||
uint32 audioFlags = header.readUint32LE();
|
||||
byte stereo = (audioFlags & 1);
|
||||
if (_stereo != byte(-1) && _stereo != stereo) {
|
||||
error("Channels mismatch");
|
||||
return false;
|
||||
|
||||
}
|
||||
_stereo = stereo;
|
||||
|
||||
_status[0].last = leftSample;
|
||||
_status[1].last = rightSample;
|
||||
_status[0].stepIndex = _status[1].stepIndex = 0;
|
||||
_status[0].step = _status[1].step = Ima_ADPCMStream::_imaTable[0];
|
||||
|
||||
if (!_audStream) {
|
||||
_audStream.reset(makeQueuingAudioStream(_sampleRate, _stereo));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void APCStreamImpl::queuePacket(Common::SeekableReadStream *data) {
|
||||
Common::ScopedPtr<Common::SeekableReadStream> packet(data);
|
||||
|
||||
uint32 size = packet->size() - packet->pos();
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// From 4-bits samples to 16-bits
|
||||
int16 *outputBuffer = (int16 *)malloc(size * 4);
|
||||
int16 *outputPtr = outputBuffer;
|
||||
|
||||
int channelOffset = (_stereo ? 1 : 0);
|
||||
|
||||
for (uint32 counter = size; counter > 0; counter--) {
|
||||
byte word = packet->readByte();
|
||||
*(outputPtr++) = decodeIMA((word >> 4) & 0x0f, _status);
|
||||
*(outputPtr++) = decodeIMA((word >> 0) & 0x0f, _status + channelOffset);
|
||||
}
|
||||
|
||||
byte flags = FLAG_16BITS;
|
||||
if (_stereo) {
|
||||
flags |= FLAG_STEREO;
|
||||
}
|
||||
#ifdef SCUMM_LITTLE_ENDIAN
|
||||
flags |= Audio::FLAG_LITTLE_ENDIAN;
|
||||
#endif
|
||||
_audStream->queueBuffer((byte *)outputBuffer, size * 4, DisposeAfterUse::YES, flags);
|
||||
}
|
||||
|
||||
int16 APCStreamImpl::decodeIMA(byte code, Status *status) {
|
||||
int32 E = (2 * (code & 0x7) + 1) * status->step / 8;
|
||||
int32 diff = (code & 0x08) ? -E : E;
|
||||
// In Cryo APC data is only truncated and not CLIPed as expected
|
||||
int16 samp = (status->last + diff);
|
||||
|
||||
int32 index = status->stepIndex + Ima_ADPCMStream::_stepAdjustTable[code];
|
||||
index = CLIP<int32>(index, 0, ARRAYSIZE(Ima_ADPCMStream::_imaTable) - 1);
|
||||
|
||||
status->last = samp;
|
||||
status->stepIndex = index;
|
||||
status->step = Ima_ADPCMStream::_imaTable[index];
|
||||
|
||||
return samp;
|
||||
}
|
||||
|
||||
PacketizedAudioStream *makeAPCStream(Common::SeekableReadStream &header) {
|
||||
Common::ScopedPtr<APCStream> stream(new APCStreamImpl());
|
||||
if (!stream->init(header)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return stream.release();
|
||||
}
|
||||
|
||||
APCStream *makeAPCStream(uint sampleRate, bool stereo) {
|
||||
assert((sampleRate % 11025) == 0);
|
||||
return new APCStreamImpl(sampleRate, stereo ? 1 : 0);
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
|
61
audio/decoders/apc.h
Normal file
61
audio/decoders/apc.h
Normal file
@ -0,0 +1,61 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_DECODERS_APC_H
|
||||
#define AUDIO_DECODERS_APC_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "audio/audiostream.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
} // End of namespace Common
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class APCStream : public PacketizedAudioStream {
|
||||
public:
|
||||
virtual bool init(Common::SeekableReadStream &header) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a PacketizedAudioStream that decodes Cryo APC sound from stream
|
||||
*
|
||||
* @param header The stream containing the header
|
||||
* queuePacket must be called after
|
||||
* @return A new PacketizedAudioStream, or nullptr on error
|
||||
*/
|
||||
PacketizedAudioStream *makeAPCStream(Common::SeekableReadStream &header);
|
||||
|
||||
/**
|
||||
* Create a PacketizedAudioStream that decodes Cryo APC sound using predefined settings
|
||||
* This is used by HNM6 video decoder and shouldn't be called elsewhere.
|
||||
*
|
||||
* @param sampleRate The sample rate of the stream
|
||||
* @param stereo Whether the stream will be stereo
|
||||
* @return A new APCStream
|
||||
*/
|
||||
APCStream *makeAPCStream(uint sampleRate, bool stereo);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
|
@ -27,6 +27,7 @@ MODULE_OBJS := \
|
||||
decoders/aac.o \
|
||||
decoders/adpcm.o \
|
||||
decoders/aiff.o \
|
||||
decoders/apc.o \
|
||||
decoders/asf.o \
|
||||
decoders/flac.o \
|
||||
decoders/g711.o \
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include "cryomni3d/datstream.h"
|
||||
|
||||
#include "cryomni3d/image/hlz.h"
|
||||
#include "cryomni3d/image/hnm.h"
|
||||
#include "video/hnm_decoder.h"
|
||||
|
||||
namespace CryOmni3D {
|
||||
@ -134,11 +135,15 @@ void CryOmni3DEngine::playHNM(const Common::String &filename, Audio::Mixer::Soun
|
||||
const char *const extensions[] = { "hns", "hnm", "ubb", nullptr };
|
||||
Common::String fname(prepareFileName(filename, extensions));
|
||||
|
||||
byte *currentPalette = new byte[256 * 3];
|
||||
g_system->getPaletteManager()->grabPalette(currentPalette, 0, 256);
|
||||
Graphics::PixelFormat screenFormat = g_system->getScreenFormat();
|
||||
byte *currentPalette = nullptr;
|
||||
if (screenFormat.bytesPerPixel == 1) {
|
||||
currentPalette = new byte[256 * 3];
|
||||
g_system->getPaletteManager()->grabPalette(currentPalette, 0, 256);
|
||||
}
|
||||
|
||||
// Pass the ownership of currentPalette to HNMDecoder
|
||||
Video::VideoDecoder *videoDecoder = new Video::HNMDecoder(false, currentPalette);
|
||||
Video::VideoDecoder *videoDecoder = new Video::HNMDecoder(screenFormat, false, currentPalette);
|
||||
videoDecoder->setSoundType(soundType);
|
||||
|
||||
if (!videoDecoder->loadFile(fname)) {
|
||||
|
116
engines/cryomni3d/image/hnm.cpp
Normal file
116
engines/cryomni3d/image/hnm.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
/* 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 "cryomni3d/image/hnm.h"
|
||||
|
||||
#include "common/stream.h"
|
||||
#include "common/substream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "graphics/pixelformat.h"
|
||||
#include "graphics/surface.h"
|
||||
#include "image/codecs/codec.h"
|
||||
|
||||
#include "image/codecs/hnm.h"
|
||||
|
||||
namespace Image {
|
||||
|
||||
HNMFileDecoder::HNMFileDecoder(const Graphics::PixelFormat &format) :
|
||||
_format(format), _surface(nullptr), _codec(nullptr) {
|
||||
}
|
||||
|
||||
HNMFileDecoder::~HNMFileDecoder() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
void HNMFileDecoder::destroy() {
|
||||
delete _codec;
|
||||
_codec = nullptr;
|
||||
_surface = nullptr;
|
||||
}
|
||||
|
||||
bool HNMFileDecoder::loadStream(Common::SeekableReadStream &stream) {
|
||||
destroy();
|
||||
|
||||
uint32 tag = stream.readUint32BE();
|
||||
|
||||
/* Only HNM6 for HNM images */
|
||||
if (tag != MKTAG('H', 'N', 'M', '6')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//uint16 ukn = stream.readUint16BE();
|
||||
//byte audioflags = stream.readByte();
|
||||
//byte bpp = stream.readByte();
|
||||
stream.skip(4);
|
||||
uint16 width = stream.readUint16LE();
|
||||
uint16 height = stream.readUint16LE();
|
||||
//uint32 filesize = stream.readUint32LE();
|
||||
//uint32 numframes = stream.readUint32LE();
|
||||
//uint32 ukn2 = stream.readUint32LE();
|
||||
//uint16 speed = stream.readUint16LE();
|
||||
//uint16 maxbuffer = stream.readUint16LE();
|
||||
//uint32 buffer_size = stream.readUint32LE();
|
||||
//byte unknownStr[16];
|
||||
//byte copyright[16];
|
||||
//stream.read(unknownStr, sizeof(unknownStr));
|
||||
//stream.read(copyright, sizeof(copyright));
|
||||
|
||||
stream.skip(52);
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read frame header
|
||||
uint32 frameSize = stream.readUint32LE();
|
||||
if (frameSize < 12) {
|
||||
return false;
|
||||
}
|
||||
frameSize -= 4;
|
||||
|
||||
// Read chunk header
|
||||
uint32 chunkSize = stream.readUint32LE();
|
||||
uint16 chunkTag = stream.readUint16BE();
|
||||
//uint16 chunkUkn = stream.readUint16LE();
|
||||
stream.skip(2);
|
||||
|
||||
if (frameSize < chunkSize ||
|
||||
chunkSize < 8 + 24) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool warp;
|
||||
if (chunkTag == MKTAG16('I', 'W')) {
|
||||
warp = true;
|
||||
} else if (chunkTag == MKTAG16('I', 'X')) {
|
||||
warp = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// buffer_size is not reliable on IW and we already have the real size of the image source
|
||||
_codec = createHNM6Decoder(width, height, _format, chunkSize, false);
|
||||
_codec->setWarpMode(warp);
|
||||
_surface = _codec->decodeFrame(stream);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // End of namespace Image
|
59
engines/cryomni3d/image/hnm.h
Normal file
59
engines/cryomni3d/image/hnm.h
Normal file
@ -0,0 +1,59 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CRYOMNI3D_IMAGE_HNM_H
|
||||
#define CRYOMNI3D_IMAGE_HNM_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/str.h"
|
||||
#include "graphics/pixelformat.h"
|
||||
#include "image/image_decoder.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Graphics {
|
||||
struct Surface;
|
||||
}
|
||||
|
||||
namespace Image {
|
||||
class HNM6Decoder;
|
||||
|
||||
class HNMFileDecoder : public ImageDecoder {
|
||||
public:
|
||||
HNMFileDecoder(const Graphics::PixelFormat &format);
|
||||
~HNMFileDecoder() override;
|
||||
|
||||
// ImageDecoder API
|
||||
void destroy() override;
|
||||
bool loadStream(Common::SeekableReadStream &stream) override;
|
||||
const Graphics::Surface *getSurface() const override { return _surface; }
|
||||
|
||||
private:
|
||||
Graphics::PixelFormat _format;
|
||||
HNM6Decoder *_codec;
|
||||
const Graphics::Surface *_surface;
|
||||
};
|
||||
|
||||
} // End of namespace Image
|
||||
|
||||
#endif
|
@ -4,6 +4,7 @@ MODULE_OBJS = \
|
||||
fonts/cryoextfont.o \
|
||||
fonts/cryofont.o \
|
||||
image/hlz.o \
|
||||
image/hnm.o \
|
||||
cryomni3d.o \
|
||||
datstream.o \
|
||||
dialogs_manager.o \
|
||||
|
@ -104,7 +104,7 @@ void Versailles_DialogsManager::playDialog(const Common::String &video, const Co
|
||||
}
|
||||
soundFName = _engine->prepareFileName(soundFName, "wav");
|
||||
|
||||
Video::HNMDecoder *videoDecoder = new Video::HNMDecoder(true);
|
||||
Video::HNMDecoder *videoDecoder = new Video::HNMDecoder(g_system->getScreenFormat(), true);
|
||||
|
||||
if (!videoDecoder->loadFile(videoFName)) {
|
||||
warning("Failed to open movie file %s/%s", video.c_str(), videoFName.c_str());
|
||||
@ -355,7 +355,7 @@ uint Versailles_DialogsManager::askPlayerQuestions(const Common::String &video,
|
||||
void Versailles_DialogsManager::loadFrame(const Common::String &video) {
|
||||
Common::String videoFName(_engine->prepareFileName(video, "hnm"));
|
||||
|
||||
Video::HNMDecoder *videoDecoder = new Video::HNMDecoder();
|
||||
Video::HNMDecoder *videoDecoder = new Video::HNMDecoder(g_system->getScreenFormat());
|
||||
|
||||
if (!videoDecoder->loadFile(videoFName)) {
|
||||
warning("Failed to open movie file %s/%s", video.c_str(), videoFName.c_str());
|
||||
|
1404
image/codecs/hnm.cpp
Normal file
1404
image/codecs/hnm.cpp
Normal file
File diff suppressed because it is too large
Load Diff
56
image/codecs/hnm.h
Normal file
56
image/codecs/hnm.h
Normal file
@ -0,0 +1,56 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IMAGE_CODECS_HNM6_H
|
||||
#define IMAGE_CODECS_HNM6_H
|
||||
|
||||
#include "image/codecs/codec.h"
|
||||
|
||||
namespace Image {
|
||||
|
||||
/**
|
||||
* HNM6 image decoder interface.
|
||||
*
|
||||
* Used by HNM6 image and video formats.
|
||||
*/
|
||||
class HNM6Decoder : public Codec {
|
||||
public:
|
||||
uint16 getWidth() const { return _width; }
|
||||
uint16 getHeight() const { return _height; }
|
||||
Graphics::PixelFormat getPixelFormat() const override { return _format; }
|
||||
|
||||
void setWarpMode(bool warpMode) { assert(!warpMode || !_videoMode); _warpMode = warpMode; }
|
||||
protected:
|
||||
HNM6Decoder(uint16 width, uint16 height, const Graphics::PixelFormat &format,
|
||||
bool videoMode = false) : Codec(),
|
||||
_width(width), _height(height), _format(format), _videoMode(videoMode), _warpMode(false) { }
|
||||
|
||||
uint16 _width, _height;
|
||||
Graphics::PixelFormat _format;
|
||||
bool _warpMode, _videoMode;
|
||||
};
|
||||
|
||||
HNM6Decoder *createHNM6Decoder(uint16 width, uint16 height, const Graphics::PixelFormat &format,
|
||||
uint32 bufferSize, bool videoMode = false);
|
||||
|
||||
} // End of namespace Image
|
||||
|
||||
#endif
|
@ -15,6 +15,7 @@ MODULE_OBJS := \
|
||||
codecs/cinepak.o \
|
||||
codecs/codec.o \
|
||||
codecs/hlz.o \
|
||||
codecs/hnm.o \
|
||||
codecs/indeo3.o \
|
||||
codecs/indeo4.o \
|
||||
codecs/indeo5.o \
|
||||
|
@ -27,16 +27,23 @@
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "audio/decoders/raw.h"
|
||||
#include "audio/decoders/apc.h"
|
||||
|
||||
#include "image/codecs/hlz.h"
|
||||
#include "image/codecs/hnm.h"
|
||||
|
||||
#include "video/hnm_decoder.h"
|
||||
#include "image/codecs/hlz.h"
|
||||
|
||||
namespace Video {
|
||||
|
||||
// When no sound display a frame every 80ms
|
||||
HNMDecoder::HNMDecoder(bool loop, byte *initialPalette) : _regularFrameDelayMs(80),
|
||||
_videoTrack(nullptr), _audioTrack(nullptr), _stream(nullptr),
|
||||
_loop(loop), _initialPalette(initialPalette), _dataBuffer(nullptr), _dataBufferAlloc(0) {
|
||||
HNMDecoder::HNMDecoder(const Graphics::PixelFormat &format, bool loop,
|
||||
byte *initialPalette) : _regularFrameDelayMs(uint32(-1)),
|
||||
_videoTrack(nullptr), _audioTrack(nullptr), _stream(nullptr), _format(format),
|
||||
_loop(loop), _initialPalette(initialPalette), _alignedChunks(false),
|
||||
_dataBuffer(nullptr), _dataBufferAlloc(0) {
|
||||
if (initialPalette && format.bytesPerPixel >= 2) {
|
||||
error("Invalid pixel format while initial palette is set");
|
||||
}
|
||||
}
|
||||
|
||||
HNMDecoder::~HNMDecoder() {
|
||||
@ -50,17 +57,31 @@ HNMDecoder::~HNMDecoder() {
|
||||
bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
|
||||
close();
|
||||
|
||||
// Take the ownership of stream immediately
|
||||
_stream = stream;
|
||||
|
||||
uint32 tag = stream->readUint32BE();
|
||||
|
||||
/* For now, only HNM4 and UBB2, HNM6 in the future */
|
||||
/* All HNM versions are supported */
|
||||
if (tag != MKTAG('H', 'N', 'M', '4') &&
|
||||
tag != MKTAG('U', 'B', 'B', '2')) {
|
||||
tag != MKTAG('U', 'B', 'B', '2') &&
|
||||
tag != MKTAG('H', 'N', 'M', '6')) {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
//uint32 ukn = stream->readUint32BE();
|
||||
stream->skip(4);
|
||||
byte audioflags = 0;
|
||||
uint16 soundBits = 0, soundFormat = 0;
|
||||
if (tag == MKTAG('H', 'N', 'M', '6')) {
|
||||
//uint16 ukn6_1 = stream->readUint16LE();
|
||||
stream->skip(2);
|
||||
audioflags = stream->readByte();
|
||||
//byte bpp = stream->readByte();
|
||||
stream->skip(1);
|
||||
} else {
|
||||
//uint32 ukn = stream->readUint32BE();
|
||||
stream->skip(4);
|
||||
}
|
||||
uint16 width = stream->readUint16LE();
|
||||
uint16 height = stream->readUint16LE();
|
||||
//uint32 filesize = stream->readUint32LE();
|
||||
@ -68,8 +89,15 @@ bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
|
||||
uint32 frameCount = stream->readUint32LE();
|
||||
//uint32 tabOffset = stream->readUint32LE();
|
||||
stream->skip(4);
|
||||
uint16 soundBits = stream->readUint16LE();
|
||||
uint16 soundFormat = stream->readUint16LE();
|
||||
if (tag == MKTAG('H', 'N', 'M', '4') ||
|
||||
tag == MKTAG('U', 'B', 'B', '2')) {
|
||||
soundBits = stream->readUint16LE();
|
||||
soundFormat = stream->readUint16LE();
|
||||
} else {
|
||||
//uint16 ukn6_2 = stream->readUint16LE();
|
||||
//uint16 ukn6_3 = stream->readUint16LE();
|
||||
stream->skip(4);
|
||||
}
|
||||
uint32 frameSize = stream->readUint32LE();
|
||||
|
||||
byte unknownStr[16];
|
||||
@ -84,10 +112,26 @@ bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
|
||||
|
||||
// When no audio use a factor of 1 for audio timestamp
|
||||
uint32 audioSampleRate = 1;
|
||||
// When no sound display a frame every X ms if not overriden
|
||||
if (_regularFrameDelayMs == uint32(-1)) {
|
||||
if (tag == MKTAG('H', 'N', 'M', '4') ||
|
||||
tag == MKTAG('U', 'B', 'B', '2')) {
|
||||
// For HNM4&5 it's every 80 ms
|
||||
_regularFrameDelayMs = 80;
|
||||
} else if (tag == MKTAG('H', 'N', 'M', '6')) {
|
||||
// For HNM6 it's every 66 ms
|
||||
_regularFrameDelayMs = 66;
|
||||
}
|
||||
}
|
||||
|
||||
_videoTrack = nullptr;
|
||||
_audioTrack = nullptr;
|
||||
if (tag == MKTAG('H', 'N', 'M', '4')) {
|
||||
if (_format.bytesPerPixel >= 2) {
|
||||
// Bad constructor used
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
if (soundFormat == 2 && soundBits != 0) {
|
||||
// HNM4 is Mono 22050Hz
|
||||
_audioTrack = new DPCMAudioTrack(soundFormat, soundBits, 22050, false, getSoundType());
|
||||
@ -97,6 +141,11 @@ bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
|
||||
_regularFrameDelayMs, audioSampleRate,
|
||||
_initialPalette);
|
||||
} else if (tag == MKTAG('U', 'B', 'B', '2')) {
|
||||
if (_format.bytesPerPixel >= 2) {
|
||||
// Bad constructor used
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
if (soundFormat == 2 && soundBits == 0) {
|
||||
// UBB2 is Stereo 22050Hz
|
||||
_audioTrack = new DPCMAudioTrack(soundFormat, 16, 22050, true, getSoundType());
|
||||
@ -105,6 +154,21 @@ bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
|
||||
_videoTrack = new HNM5VideoTrack(width, height, frameSize, frameCount,
|
||||
_regularFrameDelayMs, audioSampleRate,
|
||||
_initialPalette);
|
||||
} else if (tag == MKTAG('H', 'N', 'M', '6')) {
|
||||
if (_format.bytesPerPixel < 2) {
|
||||
// Bad constructor used or bad format given
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
_alignedChunks = true;
|
||||
if (audioflags & 1) {
|
||||
byte stereo = (audioflags >> 7) & 1;
|
||||
audioSampleRate = ((audioflags >> 4) & 6) * 11025;
|
||||
_audioTrack = new APCAudioTrack(audioSampleRate, stereo, getSoundType());
|
||||
}
|
||||
_videoTrack = new HNM6VideoTrack(width, height, frameSize, frameCount,
|
||||
_regularFrameDelayMs, audioSampleRate,
|
||||
_format);
|
||||
} else {
|
||||
// We should never be here
|
||||
close();
|
||||
@ -115,8 +179,6 @@ bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
|
||||
addTrack(_audioTrack);
|
||||
}
|
||||
|
||||
_stream = stream;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -183,9 +245,11 @@ void HNMDecoder::readNextPacket() {
|
||||
error("Chunk has a bogus size");
|
||||
}
|
||||
|
||||
if (chunkType == MKTAG16('S', 'D')) {
|
||||
if (chunkType == MKTAG16('S', 'D') ||
|
||||
chunkType == MKTAG16('A', 'A') ||
|
||||
chunkType == MKTAG16('B', 'B')) {
|
||||
if (_audioTrack) {
|
||||
audioNumSamples = _audioTrack->decodeSound(data_p, chunkSize - 8);
|
||||
audioNumSamples = _audioTrack->decodeSound(chunkType, data_p, chunkSize - 8);
|
||||
} else {
|
||||
warning("Got audio data without an audio track");
|
||||
}
|
||||
@ -193,6 +257,10 @@ void HNMDecoder::readNextPacket() {
|
||||
_videoTrack->decodeChunk(data_p, chunkSize - 8, chunkType, flags);
|
||||
}
|
||||
|
||||
if (_alignedChunks) {
|
||||
chunkSize = ((chunkSize + 3) / 4) * 4;
|
||||
}
|
||||
|
||||
data_p += (chunkSize - 8);
|
||||
superchunkRemaining -= chunkSize;
|
||||
}
|
||||
@ -206,27 +274,6 @@ HNMDecoder::HNMVideoTrack::HNMVideoTrack(uint32 frameCount,
|
||||
restart();
|
||||
}
|
||||
|
||||
void HNMDecoder::HNMVideoTrack::newFrame(uint32 frameDelay) {
|
||||
// Frame done
|
||||
_curFrame++;
|
||||
|
||||
// Add frameDelay if we had no sound
|
||||
// We can't rely on a detection in the header as some soundless HNM indicate they have
|
||||
if (frameDelay == uint32(-1)) {
|
||||
_nextFrameStartTime = _nextFrameStartTime.addMsecs(_regularFrameDelayMs);
|
||||
return;
|
||||
}
|
||||
|
||||
// HNM decoders use sound double buffering to pace the frames
|
||||
// First frame is loaded in first buffer, second frame in second buffer
|
||||
// It's only for third frame that we wait for the first buffer to be free
|
||||
// It's presentation time is then delayed by the number of sound samples in the first frame
|
||||
if (_lastFrameDelaySamps) {
|
||||
_nextFrameStartTime = _nextFrameStartTime.addFrames(_lastFrameDelaySamps);
|
||||
}
|
||||
_lastFrameDelaySamps = frameDelay;
|
||||
}
|
||||
|
||||
HNMDecoder::HNM45VideoTrack::HNM45VideoTrack(uint32 width, uint32 height, uint32 frameSize,
|
||||
uint32 frameCount, uint32 regularFrameDelayMs, uint32 audioSampleRate,
|
||||
const byte *initialPalette) :
|
||||
@ -260,6 +307,27 @@ HNMDecoder::HNM45VideoTrack::~HNM45VideoTrack() {
|
||||
_frameBufferP = nullptr;
|
||||
}
|
||||
|
||||
void HNMDecoder::HNM45VideoTrack::newFrame(uint32 frameDelay) {
|
||||
// Frame done
|
||||
_curFrame++;
|
||||
|
||||
// Add regular frame delay if we had no sound
|
||||
// We can't rely on a detection in the header as some soundless HNM indicate they have
|
||||
if (frameDelay == uint32(-1)) {
|
||||
_nextFrameStartTime = _nextFrameStartTime.addMsecs(_regularFrameDelayMs);
|
||||
return;
|
||||
}
|
||||
|
||||
// HNM decoders use sound double buffering to pace the frames
|
||||
// First frame is loaded in first buffer, second frame in second buffer
|
||||
// It's only for third frame that we wait for the first buffer to be free
|
||||
// It's presentation time is then delayed by the number of sound samples in the first frame
|
||||
if (_lastFrameDelaySamps) {
|
||||
_nextFrameStartTime = _nextFrameStartTime.addFrames(_lastFrameDelaySamps);
|
||||
}
|
||||
_lastFrameDelaySamps = frameDelay;
|
||||
}
|
||||
|
||||
void HNMDecoder::HNM45VideoTrack::decodePalette(byte *data, uint32 size) {
|
||||
while (true) {
|
||||
if (size < 2) {
|
||||
@ -972,8 +1040,67 @@ void HNMDecoder::HNM5VideoTrack::decodeFrame(byte *data, uint32 size) {
|
||||
_surface.setPixels(_frameBufferC);
|
||||
}
|
||||
|
||||
HNMDecoder::HNM6VideoTrack::HNM6VideoTrack(uint32 width, uint32 height, uint32 frameSize,
|
||||
uint32 frameCount, uint32 regularFrameDelayMs, uint32 audioSampleRate,
|
||||
const Graphics::PixelFormat &format) :
|
||||
HNMVideoTrack(frameCount, regularFrameDelayMs, audioSampleRate),
|
||||
_decoder(Image::createHNM6Decoder(width, height, format, frameSize, true)),
|
||||
_surface(nullptr) {
|
||||
}
|
||||
|
||||
HNMDecoder::HNM6VideoTrack::~HNM6VideoTrack() {
|
||||
delete _decoder;
|
||||
}
|
||||
|
||||
uint16 HNMDecoder::HNM6VideoTrack::getWidth() const {
|
||||
return _decoder->getWidth();
|
||||
}
|
||||
|
||||
uint16 HNMDecoder::HNM6VideoTrack::getHeight() const {
|
||||
return _decoder->getHeight();
|
||||
}
|
||||
|
||||
Graphics::PixelFormat HNMDecoder::HNM6VideoTrack::getPixelFormat() const {
|
||||
return _decoder->getPixelFormat();
|
||||
}
|
||||
|
||||
void HNMDecoder::HNM6VideoTrack::decodeChunk(byte *data, uint32 size,
|
||||
uint16 chunkType, uint16 flags) {
|
||||
if (chunkType == MKTAG16('I', 'X') ||
|
||||
chunkType == MKTAG16('I', 'W')) {
|
||||
Common::MemoryReadStream stream(data, size);
|
||||
_surface = _decoder->decodeFrame(stream);
|
||||
} else {
|
||||
error("HNM6: Got %d chunk: size %d", chunkType, size);
|
||||
}
|
||||
}
|
||||
|
||||
void HNMDecoder::HNM6VideoTrack::newFrame(uint32 frameDelay) {
|
||||
// Frame done
|
||||
_curFrame++;
|
||||
|
||||
// In HNM6 first frame contains the sound for the 32 following frames (pre-buffering)
|
||||
// Then other frames contain constant size sound chunks except for the 32 last ones
|
||||
if (!_lastFrameDelaySamps) {
|
||||
// Add regular frame delay if we had no sound
|
||||
if (frameDelay == uint32(-1)) {
|
||||
_nextFrameStartTime = _nextFrameStartTime.addMsecs(_regularFrameDelayMs);
|
||||
return;
|
||||
}
|
||||
|
||||
assert((frameDelay & 31) == 0);
|
||||
_lastFrameDelaySamps = frameDelay / 32;
|
||||
_nextFrameStartTime = _nextFrameStartTime.addFrames(_lastFrameDelaySamps);
|
||||
} else {
|
||||
if (frameDelay != uint32(-1)) {
|
||||
assert(frameDelay == _lastFrameDelaySamps);
|
||||
}
|
||||
_nextFrameStartTime = _nextFrameStartTime.addFrames(_lastFrameDelaySamps);
|
||||
}
|
||||
}
|
||||
|
||||
HNMDecoder::DPCMAudioTrack::DPCMAudioTrack(uint16 format, uint16 bits, uint sampleRate, bool stereo,
|
||||
Audio::Mixer::SoundType soundType) : AudioTrack(soundType), _audioStream(nullptr),
|
||||
Audio::Mixer::SoundType soundType) : HNMAudioTrack(soundType), _audioStream(nullptr),
|
||||
_gotLUT(false), _lastSampleL(0), _lastSampleR(0), _sampleRate(sampleRate), _stereo(stereo) {
|
||||
if (bits != 16) {
|
||||
error("Unsupported audio bits");
|
||||
@ -989,7 +1116,7 @@ HNMDecoder::DPCMAudioTrack::~DPCMAudioTrack() {
|
||||
delete _audioStream;
|
||||
}
|
||||
|
||||
uint32 HNMDecoder::DPCMAudioTrack::decodeSound(byte *data, uint32 size) {
|
||||
uint32 HNMDecoder::DPCMAudioTrack::decodeSound(uint16 chunkType, byte *data, uint32 size) {
|
||||
if (!_gotLUT) {
|
||||
if (size < 256 * sizeof(*_lut)) {
|
||||
error("Invalid first sound chunk");
|
||||
@ -1051,4 +1178,39 @@ uint32 HNMDecoder::DPCMAudioTrack::decodeSound(byte *data, uint32 size) {
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
HNMDecoder::APCAudioTrack::APCAudioTrack(uint sampleRate, byte stereo,
|
||||
Audio::Mixer::SoundType soundType) : HNMAudioTrack(soundType),
|
||||
_audioStream(Audio::makeAPCStream(sampleRate, stereo)) {
|
||||
}
|
||||
|
||||
HNMDecoder::APCAudioTrack::~APCAudioTrack() {
|
||||
delete _audioStream;
|
||||
}
|
||||
|
||||
Audio::AudioStream *HNMDecoder::APCAudioTrack::getAudioStream() const {
|
||||
return _audioStream;
|
||||
}
|
||||
|
||||
uint32 HNMDecoder::APCAudioTrack::decodeSound(uint16 chunkType, byte *data, uint32 size) {
|
||||
if (chunkType == MKTAG16('A', 'A')) {
|
||||
Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, size);
|
||||
if (!_audioStream->init(*stream)) {
|
||||
error("Invalid APC stream");
|
||||
}
|
||||
// Remove header for sample counting
|
||||
size -= stream->pos();
|
||||
_audioStream->queuePacket(stream);
|
||||
// stream is freed now
|
||||
stream = nullptr;
|
||||
} else if (chunkType == MKTAG16('B', 'B')) {
|
||||
_audioStream->queuePacket(new Common::MemoryReadStream(data, size));
|
||||
}
|
||||
// 2 4-bit samples in one byte
|
||||
uint32 numSamples = size;
|
||||
if (!_audioStream->isStereo()) {
|
||||
numSamples *= 2;
|
||||
}
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
} // End of namespace Video
|
||||
|
@ -22,16 +22,24 @@
|
||||
#ifndef VIDEO_HNM_DECODER_H
|
||||
#define VIDEO_HNM_DECODER_H
|
||||
|
||||
#include "common/rational.h"
|
||||
#include "graphics/pixelformat.h"
|
||||
#include "video/video_decoder.h"
|
||||
#include "graphics/surface.h"
|
||||
#include "audio/audiostream.h"
|
||||
#include "common/rational.h"
|
||||
#include "graphics/surface.h"
|
||||
#include "video/video_decoder.h"
|
||||
|
||||
|
||||
namespace Audio {
|
||||
class APCStream;
|
||||
}
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Image {
|
||||
class HNM6Decoder;
|
||||
}
|
||||
|
||||
namespace Video {
|
||||
|
||||
/**
|
||||
@ -43,7 +51,7 @@ namespace Video {
|
||||
*/
|
||||
class HNMDecoder : public VideoDecoder {
|
||||
public:
|
||||
HNMDecoder(bool loop = false, byte *initialPalette = nullptr);
|
||||
HNMDecoder(const Graphics::PixelFormat &format, bool loop = false, byte *initialPalette = nullptr);
|
||||
~HNMDecoder() override;
|
||||
bool loadStream(Common::SeekableReadStream *stream) override;
|
||||
void readNextPacket() override;
|
||||
@ -63,8 +71,8 @@ private:
|
||||
uint32 getNextFrameStartTime() const override { return _nextFrameStartTime.msecs(); }
|
||||
|
||||
void restart() { _lastFrameDelaySamps = 0; }
|
||||
void newFrame(uint32 frameDelay);
|
||||
|
||||
virtual void newFrame(uint32 frameDelay) = 0;
|
||||
virtual void decodeChunk(byte *data, uint32 size,
|
||||
uint16 chunkType, uint16 flags) = 0;
|
||||
|
||||
@ -87,6 +95,8 @@ private:
|
||||
const byte *getPalette() const override { _dirtyPalette = false; return _palette; }
|
||||
bool hasDirtyPalette() const override { return _dirtyPalette; }
|
||||
|
||||
virtual void newFrame(uint32 frameDelay) override;
|
||||
|
||||
protected:
|
||||
HNM45VideoTrack(uint32 width, uint32 height, uint32 frameSize, uint32 frameCount,
|
||||
uint32 regularFrameDelayMs, uint32 audioSampleRate,
|
||||
@ -142,13 +152,41 @@ private:
|
||||
void decodeFrame(byte *data, uint32 size);
|
||||
};
|
||||
|
||||
class DPCMAudioTrack : public AudioTrack {
|
||||
class HNM6VideoTrack : public HNMVideoTrack {
|
||||
public:
|
||||
HNM6VideoTrack(uint32 width, uint32 height, uint32 frameSize, uint32 frameCount,
|
||||
uint32 regularFrameDelayMs, uint32 audioSampleRate,
|
||||
const Graphics::PixelFormat &format);
|
||||
~HNM6VideoTrack() override;
|
||||
|
||||
uint16 getWidth() const override;
|
||||
uint16 getHeight() const override;
|
||||
Graphics::PixelFormat getPixelFormat() const override;
|
||||
const Graphics::Surface *decodeNextFrame() override { return _surface; }
|
||||
|
||||
virtual void newFrame(uint32 frameDelay) override;
|
||||
/** Decode a video chunk. */
|
||||
void decodeChunk(byte *data, uint32 size,
|
||||
uint16 chunkType, uint16 flags) override;
|
||||
private:
|
||||
Image::HNM6Decoder *_decoder;
|
||||
const Graphics::Surface *_surface;
|
||||
};
|
||||
|
||||
class HNMAudioTrack : public AudioTrack {
|
||||
public:
|
||||
HNMAudioTrack(Audio::Mixer::SoundType soundType) : AudioTrack(soundType) {}
|
||||
|
||||
virtual uint32 decodeSound(uint16 chunkType, byte *data, uint32 size) = 0;
|
||||
};
|
||||
|
||||
class DPCMAudioTrack : public HNMAudioTrack {
|
||||
public:
|
||||
DPCMAudioTrack(uint16 format, uint16 bits, uint sampleRate, bool stereo,
|
||||
Audio::Mixer::SoundType soundType);
|
||||
~DPCMAudioTrack() override;
|
||||
|
||||
uint32 decodeSound(byte *data, uint32 size);
|
||||
uint32 decodeSound(uint16 chunkType, byte *data, uint32 size) override;
|
||||
protected:
|
||||
Audio::AudioStream *getAudioStream() const override { return _audioStream; }
|
||||
private:
|
||||
@ -161,15 +199,30 @@ private:
|
||||
bool _stereo;
|
||||
};
|
||||
|
||||
class APCAudioTrack : public HNMAudioTrack {
|
||||
public:
|
||||
APCAudioTrack(uint sampleRate, byte stereo,
|
||||
Audio::Mixer::SoundType soundType);
|
||||
~APCAudioTrack() override;
|
||||
|
||||
uint32 decodeSound(uint16 chunkType, byte *data, uint32 size) override;
|
||||
protected:
|
||||
Audio::AudioStream *getAudioStream() const override;
|
||||
private:
|
||||
Audio::APCStream *_audioStream;
|
||||
};
|
||||
|
||||
Graphics::PixelFormat _format;
|
||||
bool _loop;
|
||||
byte *_initialPalette;
|
||||
|
||||
uint32 _regularFrameDelayMs;
|
||||
// These two pointer are owned by VideoDecoder
|
||||
HNMVideoTrack *_videoTrack;
|
||||
DPCMAudioTrack *_audioTrack;
|
||||
HNMAudioTrack *_audioTrack;
|
||||
|
||||
Common::SeekableReadStream *_stream;
|
||||
bool _alignedChunks;
|
||||
byte *_dataBuffer;
|
||||
uint32 _dataBufferAlloc;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user