VIDEO: Rework HNM decoder to make it handle different versions of format

For now nothing new: HNM4(A) format is supported and HNM5 has a
placeholder.
This commit is contained in:
Le Philousophe 2022-01-08 11:58:03 +01:00
parent 6b1a3983f3
commit c6383c472e
2 changed files with 181 additions and 94 deletions

View File

@ -53,7 +53,8 @@ bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
uint32 tag = stream->readUint32BE();
/* For now, only HNM4 and UBB2, HNM6 in the future */
if (tag != MKTAG('H', 'N', 'M', '4') && tag != MKTAG('U', 'B', 'B', '2')) {
if (tag != MKTAG('H', 'N', 'M', '4') &&
tag != MKTAG('U', 'B', 'B', '2')) {
close();
return false;
}
@ -81,19 +82,32 @@ bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
frameCount = 0;
}
_videoTrack = new HNM4VideoTrack(width, height, frameSize, frameCount, _regularFrameDelay,
_initialPalette);
addTrack(_videoTrack);
if (tag == MKTAG('H', 'N', 'M', '4') && soundFormat == 2 && soundBits != 0) {
// HNM4 is Mono 22050Hz
_audioTrack = new DPCMAudioTrack(soundFormat, soundBits, 22050, false, getSoundType());
addTrack(_audioTrack);
} else if (tag == MKTAG('U', 'B', 'B', '2') && soundFormat == 2 && soundBits == 0) {
// UBB2 is Stereo 22050Hz
_audioTrack = new DPCMAudioTrack(soundFormat, 16, 22050, true, getSoundType());
addTrack(_audioTrack);
_videoTrack = nullptr;
_audioTrack = nullptr;
if (tag == MKTAG('H', 'N', 'M', '4')) {
_videoTrack = new HNM4VideoTrack(width, height, frameSize, frameCount, _regularFrameDelay,
_initialPalette);
if (soundFormat == 2 && soundBits != 0) {
// HNM4 is Mono 22050Hz
_audioTrack = new DPCMAudioTrack(soundFormat, soundBits, 22050, false, getSoundType());
}
} else if (tag == MKTAG('U', 'B', 'B', '2')) {
_videoTrack = new HNM5VideoTrack(width, height, frameSize, frameCount, _regularFrameDelay,
_initialPalette);
if (soundFormat == 2 && soundBits == 0) {
// UBB2 is Stereo 22050Hz
_audioTrack = new DPCMAudioTrack(soundFormat, 16, 22050, true, getSoundType());
}
} else {
_audioTrack = nullptr;
// We should never be here
close();
return false;
}
if (_videoTrack) {
addTrack(_videoTrack);
}
if (_audioTrack) {
addTrack(_audioTrack);
}
_stream = stream;
@ -132,23 +146,7 @@ void HNMDecoder::readNextPacket() {
uint16 chunkType = _stream->readUint16BE();
uint16 flags = _stream->readUint16LE();
if (chunkType == MKTAG16('P', 'L')) {
_videoTrack->decodePalette(_stream, chunkSize - 8);
} else if (chunkType == MKTAG16('I', 'Z')) {
_stream->skip(4);
_videoTrack->decodeIntraframe(_stream, chunkSize - 8 - 4);
_videoTrack->presentFrame(flags);
} else if (chunkType == MKTAG16('I', 'U')) {
if ((flags & 1) == 1) {
_videoTrack->decodeInterframeA(_stream, chunkSize - 8);
} else {
_videoTrack->decodeInterframe(_stream, chunkSize - 8);
}
_videoTrack->presentFrame(flags);
} else if (chunkType == MKTAG16('I', 'V')) {
_videoTrack->decodeInterframeIV(_stream, chunkSize - 8);
_videoTrack->presentFrame(flags);
} else if (chunkType == MKTAG16('S', 'D')) {
if (chunkType == MKTAG16('S', 'D')) {
if (_audioTrack) {
Audio::Timestamp duration = _audioTrack->decodeSound(_stream, chunkSize - 8);
_videoTrack->setFrameDelay(duration.msecs());
@ -157,20 +155,33 @@ void HNMDecoder::readNextPacket() {
_stream->skip(chunkSize - 8);
}
} else {
error("Got %d chunk: size %d", chunkType, chunkSize);
_videoTrack->decodeChunk(_stream, chunkSize - 8, chunkType, flags);
}
superchunkRemaining -= chunkSize;
}
}
HNMDecoder::HNM4VideoTrack::HNM4VideoTrack(uint32 width, uint32 height, uint32 frameSize,
uint32 frameCount, uint32 regularFrameDelay, const byte *initialPalette) :
_frameCount(frameCount), _regularFrameDelay(regularFrameDelay), _nextFrameStartTime(0) {
HNMDecoder::HNMVideoTrack::HNMVideoTrack(uint32 frameCount, uint32 regularFrameDelay) :
_frameCount(frameCount), _curFrame(-1),
_regularFrameDelay(regularFrameDelay), _nextFrameStartTime(0) {
restart();
}
void HNMDecoder::HNMVideoTrack::setFrameDelay(uint32 frameDelay) {
if (_nextFrameDelay == uint32(-1)) {
_nextFrameDelay = frameDelay;
} else if (_nextNextFrameDelay == uint32(-1)) {
_nextNextFrameDelay = frameDelay;
} else {
_nextNextFrameDelay += frameDelay;
}
}
HNMDecoder::HNM45VideoTrack::HNM45VideoTrack(uint32 width, uint32 height, uint32 frameSize,
uint32 frameCount, uint32 regularFrameDelay, const byte *initialPalette) :
HNMVideoTrack(frameCount, regularFrameDelay) {
_curFrame = -1;
// Get the currently loaded palette for undefined colors
if (initialPalette) {
memcpy(_palette, initialPalette, 256 * 3);
@ -183,37 +194,23 @@ HNMDecoder::HNM4VideoTrack::HNM4VideoTrack(uint32 width, uint32 height, uint32 f
error("Invalid frameSize: expected %d, got %d", width * height, frameSize);
}
_frameBufferF = new byte[frameSize]();
_frameBufferC = new byte[frameSize]();
_frameBufferP = new byte[frameSize]();
// We will use _frameBufferF/C/P as the surface pixels, just init there with nullptr to avoid unintended usage of surface
// We will use _frameBufferC/P as the surface pixels, just init there with nullptr to avoid unintended usage of surface
const Graphics::PixelFormat &f = Graphics::PixelFormat::createFormatCLUT8();
_surface.init(width, height, width * f.bytesPerPixel, nullptr, f);
}
HNMDecoder::HNM4VideoTrack::~HNM4VideoTrack() {
HNMDecoder::HNM45VideoTrack::~HNM45VideoTrack() {
// Don't free _surface as we didn't used create() but init()
delete[] _frameBufferF;
_frameBufferF = nullptr;
delete[] _frameBufferC;
_frameBufferC = nullptr;
delete[] _frameBufferP;
_frameBufferP = nullptr;
}
void HNMDecoder::HNM4VideoTrack::setFrameDelay(uint32 frameDelay) {
if (_nextFrameDelay == uint32(-1)) {
_nextFrameDelay = frameDelay;
} else if (_nextNextFrameDelay == uint32(-1)) {
_nextNextFrameDelay = frameDelay;
} else {
_nextNextFrameDelay += frameDelay;
}
}
void HNMDecoder::HNM4VideoTrack::decodePalette(Common::SeekableReadStream *stream, uint32 size) {
void HNMDecoder::HNM45VideoTrack::decodePalette(Common::SeekableReadStream *stream, uint32 size) {
while (true) {
if (size < 2) {
break;
@ -254,7 +251,41 @@ void HNMDecoder::HNM4VideoTrack::decodePalette(Common::SeekableReadStream *strea
}
}
void HNMDecoder::HNM4VideoTrack::decodeInterframe(Common::SeekableReadStream *stream, uint32 size) {
HNMDecoder::HNM4VideoTrack::HNM4VideoTrack(uint32 width, uint32 height, uint32 frameSize,
uint32 frameCount, uint32 regularFrameDelay, const byte *initialPalette) :
HNM45VideoTrack(width, height, frameSize, frameCount, regularFrameDelay, initialPalette) {
_frameBufferF = new byte[frameSize]();
}
HNMDecoder::HNM4VideoTrack::~HNM4VideoTrack() {
// Don't free _surface as we didn't used create() but init()
delete[] _frameBufferF;
_frameBufferF = nullptr;
}
void HNMDecoder::HNM4VideoTrack::decodeChunk(Common::SeekableReadStream *stream, uint32 size,
uint16 chunkType, uint16 flags) {
if (chunkType == MKTAG16('P', 'L')) {
decodePalette(stream, size);
} else if (chunkType == MKTAG16('I', 'Z')) {
stream->skip(4);
decodeIntraframe(stream, size - 4);
presentFrame(flags);
} else if (chunkType == MKTAG16('I', 'U')) {
if ((flags & 1) == 1) {
decodeInterframeA(stream, size);
} else {
decodeInterframe(stream, size);
}
presentFrame(flags);
} else {
error("HNM4: Got %d chunk: size %d", chunkType, size);
}
}
void HNMDecoder::HNM4VideoTrack::decodeInterframe(
Common::SeekableReadStream *stream, uint32 size) {
SWAP(_frameBufferC, _frameBufferP);
uint16 width = _surface.w;
@ -353,8 +384,9 @@ void HNMDecoder::HNM4VideoTrack::decodeInterframe(Common::SeekableReadStream *st
shft1 = 0;
shft2 = 1;
}
if (swap)
if (swap) {
SWAP(shft1, shft2);
}
int src_inc = backward ? -2 : 2;
@ -373,7 +405,8 @@ void HNMDecoder::HNM4VideoTrack::decodeInterframe(Common::SeekableReadStream *st
}
}
void HNMDecoder::HNM4VideoTrack::decodeInterframeA(Common::SeekableReadStream *stream, uint32 size) {
void HNMDecoder::HNM4VideoTrack::decodeInterframeA(
Common::SeekableReadStream *stream, uint32 size) {
SWAP(_frameBufferC, _frameBufferP);
uint16 width = _surface.w;
@ -465,16 +498,6 @@ void HNMDecoder::HNM4VideoTrack::decodeInterframeA(Common::SeekableReadStream *s
}
}
void HNMDecoder::HNM4VideoTrack::decodeInterframeIV(Common::SeekableReadStream *stream, uint32 size) {
SWAP(_frameBufferC, _frameBufferP);
// TODO: Implement this
if (size > 0) {
stream->skip(size);
}
}
void HNMDecoder::HNM4VideoTrack::decodeIntraframe(Common::SeekableReadStream *stream, uint32 size) {
Image::HLZDecoder::decodeFrameInPlace(*stream, size, _frameBufferC);
memcpy(_frameBufferP, _frameBufferC, (uint)_surface.w * (uint)_surface.h);
@ -500,11 +523,15 @@ void HNMDecoder::HNM4VideoTrack::presentFrame(uint16 flags) {
uint32 p4 = *input++;
#ifndef SCUMM_LITTLE_ENDIAN
*line0++ = ((p4 & 0xFF00) >> 8) | ((p4 & 0xFF000000) >> 16) | ((p0 & 0xFF00) << 8) | (p0 & 0xFF000000);
*line1++ = ((p0 & 0xFF0000) << 8) | ((p0 & 0xFF) << 16) | ((p4 & 0xFF0000) >> 8) | (p4 & 0xFF);
*line0++ = ((p4 & 0xFF00) >> 8) | ((p4 & 0xFF000000) >> 16) |
((p0 & 0xFF00) << 8) | (p0 & 0xFF000000);
*line1++ = ((p0 & 0xFF0000) << 8) | ((p0 & 0xFF) << 16) |
((p4 & 0xFF0000) >> 8) | (p4 & 0xFF);
#else
*line0++ = (p0 & 0xFF) | ((p0 & 0xFF0000) >> 8) | ((p4 & 0xFF) << 16) | ((p4 & 0xFF0000) << 8);
*line1++ = ((p0 & 0xFF00) >> 8) | ((p0 & 0xFF000000) >> 16) | ((p4 & 0xFF00) << 8) | (p4 & 0xFF000000);
*line0++ = (p0 & 0xFF) | ((p0 & 0xFF0000) >> 8) |
((p4 & 0xFF) << 16) | ((p4 & 0xFF0000) << 8);
*line1++ = ((p0 & 0xFF00) >> 8) | ((p0 & 0xFF000000) >> 16) |
((p4 & 0xFF00) << 8) | (p4 & 0xFF000000);
#endif
}
line0 += width / 4;
@ -522,6 +549,27 @@ void HNMDecoder::HNM4VideoTrack::presentFrame(uint16 flags) {
_nextNextFrameDelay = uint32(-1);
}
void HNMDecoder::HNM5VideoTrack::decodeChunk(Common::SeekableReadStream *stream, uint32 size,
uint16 chunkType, uint16 flags) {
if (chunkType == MKTAG16('P', 'L')) {
decodePalette(stream, size);
} else if (chunkType == MKTAG16('I', 'V')) {
decodeFrame(stream, size);
} else {
error("HNM5: Got %d chunk: size %d", chunkType, size);
}
}
void HNMDecoder::HNM5VideoTrack::decodeFrame(Common::SeekableReadStream *stream, uint32 size) {
SWAP(_frameBufferC, _frameBufferP);
// TODO: Implement this
if (size > 0) {
stream->skip(size);
}
}
HNMDecoder::DPCMAudioTrack::DPCMAudioTrack(uint16 format, uint16 bits, uint sampleRate, bool stereo,
Audio::Mixer::SoundType soundType) : AudioTrack(soundType), _audioStream(nullptr),
_gotLUT(false), _lastSample(0), _sampleRate(sampleRate), _stereo(stereo) {
@ -539,8 +587,8 @@ HNMDecoder::DPCMAudioTrack::~DPCMAudioTrack() {
delete _audioStream;
}
Audio::Timestamp HNMDecoder::DPCMAudioTrack::decodeSound(Common::SeekableReadStream *stream,
uint32 size) {
Audio::Timestamp HNMDecoder::DPCMAudioTrack::decodeSound(
Common::SeekableReadStream *stream, uint32 size) {
if (!_gotLUT) {
if (size < 256 * sizeof(*_lut)) {
error("Invalid first sound chunk");

View File

@ -52,38 +52,23 @@ public:
void setRegularFrameDelay(uint32 regularFrameDelay) { _regularFrameDelay = regularFrameDelay; }
private:
class HNM4VideoTrack : public VideoTrack {
class HNMVideoTrack : public VideoTrack {
public:
HNM4VideoTrack(uint32 width, uint32 height, uint32 frameSize, uint32 frameCount,
uint32 regularFrameDelay, const byte *initialPalette = nullptr);
~HNM4VideoTrack() override;
HNMVideoTrack(uint32 frameCount, uint32 regularFrameDelay);
// When _frameCount is 0, it means we are looping
bool endOfTrack() const override { return (_frameCount == 0) ? false : VideoTrack::endOfTrack(); }
uint16 getWidth() const override { return _surface.w; }
uint16 getHeight() const override { return _surface.h; }
Graphics::PixelFormat getPixelFormat() const override { return _surface.format; }
int getCurFrame() const override { return _curFrame; }
int getFrameCount() const override { return _frameCount; }
uint32 getNextFrameStartTime() const override { return _nextFrameStartTime; }
const Graphics::Surface *decodeNextFrame() override { return &_surface; }
const byte *getPalette() const override { _dirtyPalette = false; return _palette; }
bool hasDirtyPalette() const override { return _dirtyPalette; }
/** Decode a video chunk. */
void decodePalette(Common::SeekableReadStream *stream, uint32 size);
void decodeInterframe(Common::SeekableReadStream *stream, uint32 size);
void decodeInterframeA(Common::SeekableReadStream *stream, uint32 size);
void decodeInterframeIV(Common::SeekableReadStream *stream, uint32 size);
void decodeIntraframe(Common::SeekableReadStream *stream, uint32 size);
void presentFrame(uint16 flags);
void restart() { _nextFrameDelay = uint32(-1); _nextNextFrameDelay = uint32(-1); }
void setFrameDelay(uint32 frameDelay);
private:
Graphics::Surface _surface;
virtual void decodeChunk(Common::SeekableReadStream *stream, uint32 size,
uint16 chunkType, uint16 flags) = 0;
protected:
uint32 _regularFrameDelay;
uint32 _nextFrameDelay;
uint32 _nextNextFrameDelay;
@ -91,15 +76,69 @@ private:
uint32 _frameCount;
int _curFrame;
};
class HNM45VideoTrack : public HNMVideoTrack {
public:
// When _frameCount is 0, it means we are looping
uint16 getWidth() const override { return _surface.w; }
uint16 getHeight() const override { return _surface.h; }
Graphics::PixelFormat getPixelFormat() const override { return _surface.format; }
const Graphics::Surface *decodeNextFrame() override { return &_surface; }
const byte *getPalette() const override { _dirtyPalette = false; return _palette; }
bool hasDirtyPalette() const override { return _dirtyPalette; }
protected:
HNM45VideoTrack(uint32 width, uint32 height, uint32 frameSize, uint32 frameCount,
uint32 regularFrameDelay, const byte *initialPalette = nullptr);
~HNM45VideoTrack() override;
/** Decode a video chunk. */
void decodePalette(Common::SeekableReadStream *stream, uint32 size);
Graphics::Surface _surface;
byte _palette[256 * 3];
mutable bool _dirtyPalette;
byte *_frameBufferF;
byte *_frameBufferC;
byte *_frameBufferP;
};
class HNM4VideoTrack : public HNM45VideoTrack {
public:
HNM4VideoTrack(uint32 width, uint32 height, uint32 frameSize, uint32 frameCount,
uint32 regularFrameDelay, const byte *initialPalette = nullptr);
~HNM4VideoTrack() override;
/** Decode a video chunk. */
void decodeChunk(Common::SeekableReadStream *stream, uint32 size,
uint16 chunkType, uint16 flags) override;
protected:
/* Really decode */
void decodeInterframe(Common::SeekableReadStream *stream, uint32 size);
void decodeInterframeA(Common::SeekableReadStream *stream, uint32 size);
void decodeIntraframe(Common::SeekableReadStream *stream, uint32 size);
void presentFrame(uint16 flags);
byte *_frameBufferF;
};
class HNM5VideoTrack : public HNM45VideoTrack {
public:
HNM5VideoTrack(uint32 width, uint32 height, uint32 frameSize, uint32 frameCount,
uint32 regularFrameDelay, const byte *initialPalette = nullptr) :
HNM45VideoTrack(width, height, frameSize, frameCount, regularFrameDelay, initialPalette) {}
/** Decode a video chunk. */
void decodeChunk(Common::SeekableReadStream *stream, uint32 size,
uint16 chunkType, uint16 flags) override;
protected:
/** Really decode */
void decodeFrame(Common::SeekableReadStream *stream, uint32 size);
};
class DPCMAudioTrack : public AudioTrack {
public:
DPCMAudioTrack(uint16 format, uint16 bits, uint sampleRate, bool stereo,
@ -123,7 +162,7 @@ private:
uint32 _regularFrameDelay;
// These two pointer are owned by VideoDecoder
HNM4VideoTrack *_videoTrack;
HNMVideoTrack *_videoTrack;
DPCMAudioTrack *_audioTrack;
Common::SeekableReadStream *_stream;