CRYOMNI3D: Add HNM6 image and video codec

This also adds a Cryo APC decoder in shared code.
This commit is contained in:
Le Philousophe 2022-08-29 18:34:25 +02:00
parent 644103e012
commit 399e02e2a5
13 changed files with 2156 additions and 52 deletions

185
audio/decoders/apc.cpp Normal file
View 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
View 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

View File

@ -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 \

View File

@ -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)) {

View 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

View 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

View File

@ -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 \

View File

@ -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

File diff suppressed because it is too large Load Diff

56
image/codecs/hnm.h Normal file
View 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

View File

@ -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 \

View File

@ -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

View File

@ -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;
};