aboutsummaryrefslogtreecommitdiff
path: root/engines/cryomni3d/video/hnm_decoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/cryomni3d/video/hnm_decoder.cpp')
-rw-r--r--engines/cryomni3d/video/hnm_decoder.cpp392
1 files changed, 392 insertions, 0 deletions
diff --git a/engines/cryomni3d/video/hnm_decoder.cpp b/engines/cryomni3d/video/hnm_decoder.cpp
new file mode 100644
index 0000000000..1e52744c59
--- /dev/null
+++ b/engines/cryomni3d/video/hnm_decoder.cpp
@@ -0,0 +1,392 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/debug.h"
+#include "common/endian.h"
+#include "common/system.h"
+#include "common/stream.h"
+#include "common/file.h"
+#include "common/textconsole.h"
+
+#include "audio/decoders/raw.h"
+
+#include "cryomni3d/video/hnm_decoder.h"
+#include "cryomni3d/image/codecs/hlz.h"
+
+namespace Video {
+
+// When no sound display a frame every 80ms
+HNMDecoder::HNMDecoder(bool loop) : _regularFrameDelay(80), _videoTrack(nullptr),
+ _audioTrack(nullptr), _stream(nullptr), _loop(loop) {
+}
+
+HNMDecoder::~HNMDecoder() {
+ close();
+
+ // We don't deallocate _videoTrack and _audioTrack as they are owned by base class
+}
+
+bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
+ close();
+
+ uint32 tag = stream->readUint32BE();
+
+ /* For now, only HNM4, HNM6 in the future */
+ if (tag != MKTAG('H', 'N', 'M', '4')) {
+ close();
+ return false;
+ }
+
+ //uint32 ukn = stream->readUint32BE();
+ stream->skip(4);
+ uint16 width = stream->readUint16LE();
+ uint16 height = stream->readUint16LE();
+ //uint32 filesize = stream->readUint32LE();
+ stream->skip(4);
+ uint32 frameCount = stream->readUint32LE();
+ //uint32 tabOffset = stream->readUint32LE();
+ stream->skip(4);
+ uint16 soundBits = stream->readUint16LE();
+ uint16 soundChannels = stream->readUint16LE();
+ uint32 frameSize = stream->readUint32LE();
+
+ char unknownStr[16];
+ char copyright[16];
+ stream->read(unknownStr, sizeof(unknownStr));
+ stream->read(copyright, sizeof(copyright));
+
+ if (_loop) {
+ // This will force loop mode
+ frameCount = 0;
+ }
+
+ _videoTrack = new HNM4VideoTrack(width, height, frameSize, frameCount, _regularFrameDelay);
+ if (soundBits != 0 && soundChannels != 0) {
+ // HNM4 is 22050Hz
+ _audioTrack = new DPCMAudioTrack(soundChannels, soundBits, 22050, getSoundType());
+ } else {
+ _audioTrack = nullptr;
+ }
+ addTrack(_videoTrack);
+ addTrack(_audioTrack);
+
+ _stream = stream;
+
+ return true;
+}
+
+void HNMDecoder::close() {
+ VideoDecoder::close();
+ // Tracks are cleant by VideoDecoder::close
+ _videoTrack = nullptr;
+ _audioTrack = nullptr;
+
+ delete _stream;
+ _stream = nullptr;
+}
+
+void HNMDecoder::readNextPacket() {
+ // We are called to feed a frame
+ // Each chunk is packetized and a packet seems to contain only one frame
+ uint32 superchunkRemaining = _stream->readUint32LE();
+ if (!superchunkRemaining) {
+ if (!_loop) {
+ error("End of file but still requesting data");
+ } else {
+ // Looping: read back from start of file, skip header and read a new super chunk header
+ _videoTrack->restart();
+ _stream->seek(64, SEEK_SET);
+ superchunkRemaining = _stream->readUint32LE();
+ }
+ }
+ superchunkRemaining = (superchunkRemaining & 0x00ffffff) - 4;
+
+ while (superchunkRemaining) {
+ uint32 chunkSize = _stream->readUint32LE();
+ uint16 chunkType = _stream->readUint16BE();
+ //uint16 ukn = _stream->readUint16LE();
+ _stream->skip(2);
+
+ 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);
+ } else if (chunkType == MKTAG16('I', 'U')) {
+ _videoTrack->decodeInterframe(_stream, chunkSize - 8);
+ } else if (chunkType == MKTAG16('S', 'D')) {
+ if (_audioTrack) {
+ Audio::Timestamp duration = _audioTrack->decodeSound(_stream, chunkSize - 8);
+ _videoTrack->setFrameDelay(duration.msecs());
+ } else {
+ error("Shouldn't have audio data");
+ }
+ } else {
+ error("Got %d chunk: size %d", chunkType, chunkSize);
+ }
+
+ superchunkRemaining -= chunkSize;
+ }
+}
+
+HNMDecoder::HNM4VideoTrack::HNM4VideoTrack(uint32 width, uint32 height, uint32 frameSize,
+ uint32 frameCount, uint32 regularFrameDelay) :
+ _frameCount(frameCount), _regularFrameDelay(regularFrameDelay), _nextFrameStartTime(0) {
+
+ restart();
+
+ _curFrame = -1;
+ memset(_palette, 0, 256 * 3);
+
+ if (width * height != frameSize) {
+ error("Invalid frameSize");
+ }
+
+ // 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);
+ _frameBufferC = new byte[frameSize];
+ memset(_frameBufferC, 0, frameSize);
+ _frameBufferP = new byte[frameSize];
+ memset(_frameBufferP, 0, frameSize);
+}
+
+HNMDecoder::HNM4VideoTrack::~HNM4VideoTrack() {
+ // Don't free _surface as we didn't used create() but init()
+ delete[] _frameBufferC;
+ _frameBufferC = nullptr;
+ delete[] _frameBufferP;
+ _frameBufferP = nullptr;
+}
+
+void HNMDecoder::HNM4VideoTrack::setFrameDelay(uint32 frameDelay) {
+ if (_nextFrameDelay == -1u) {
+ _nextFrameDelay = frameDelay;
+ } else if (_nextNextFrameDelay == -1u) {
+ _nextNextFrameDelay = frameDelay;
+ } else {
+ _nextNextFrameDelay += frameDelay;
+ }
+}
+
+
+void HNMDecoder::HNM4VideoTrack::decodePalette(Common::SeekableReadStream *stream, uint32 size) {
+ while (true) {
+ if (size < 2) {
+ break;
+ }
+ unsigned int start = stream->readByte();
+ unsigned int count = stream->readByte();
+ size -= 2;
+
+ if (start == 255 && count == 255) {
+ break;
+ }
+ if (count == 0) {
+ count = 256;
+ }
+
+ if (size < count * 3) {
+ error("Invalid palette chunk data");
+ }
+ if (start + count > 256) {
+ error("Invalid palette start/count values");
+ }
+
+ size -= count * 3;
+ byte *palette_ptr = &_palette[start * 3];
+ for (; count > 0; count--) {
+ byte r = stream->readByte();
+ byte g = stream->readByte();
+ byte b = stream->readByte();
+ *(palette_ptr++) = r * 4;
+ *(palette_ptr++) = g * 4;
+ *(palette_ptr++) = b * 4;
+ }
+ }
+ _dirtyPalette = true;
+
+ if (size > 0) {
+ stream->skip(size);
+ }
+}
+
+void HNMDecoder::HNM4VideoTrack::decodeInterframe(Common::SeekableReadStream *stream, uint32 size) {
+ SWAP(_frameBufferC, _frameBufferP);
+
+ uint16 width = _surface.w;
+ bool eop = false;
+
+ unsigned int currentPos = 0;
+
+ while (!eop) {
+ if (size < 1) {
+ warning("Not enough data in chunk for interframe block");
+ break;
+ }
+ byte countFlgs = stream->readByte();
+ size -= 1;
+ byte count = countFlgs & 0x3f;
+ byte flgs = (countFlgs >> 6) & 0x3;
+
+ if (count == 0) {
+ byte c;
+ switch (flgs) {
+ case 0:
+ if (size < 1) {
+ error("Not enough data for case 0");
+ }
+ // Move in image
+ c = stream->readByte();
+ currentPos += c;
+ size -= 1;
+ break;
+ case 1:
+ if (size < 1) {
+ error("Not enough data for case 1");
+ }
+ // New pixels
+ c = stream->readByte();
+ _frameBufferC[currentPos] = c;
+ c = stream->readByte();
+ _frameBufferC[currentPos + width] = c;
+ currentPos++;
+ size -= 2;
+ break;
+ case 2:
+ // New line
+ currentPos += width;
+ break;
+ case 3:
+ // End of picture
+ eop = true;
+ break;
+ }
+ } else {
+ if (size < 2) {
+ error("Not enough data for count > 0");
+ }
+
+ bool negative = (flgs & 0x2) != 0;
+ bool previous = (flgs & 0x1) != 0;
+ int offset = stream->readUint16LE();
+ size -= 2;
+
+ if (negative) {
+ offset -= 0x10000;
+ }
+ offset += currentPos;
+ if (offset < 0) {
+ error("Invalid offset");
+ }
+
+ byte *ptr;
+ if (previous) {
+ ptr = _frameBufferP;
+ } else {
+ ptr = _frameBufferC;
+ }
+ for (; count > 0; count--) {
+ _frameBufferC[currentPos] = ptr[offset];
+ _frameBufferC[currentPos + width] = ptr[offset + width];
+ currentPos++;
+ offset++;
+ }
+ }
+ }
+ _surface.setPixels(_frameBufferC);
+
+ _curFrame++;
+ _nextFrameStartTime += _nextFrameDelay != -1u ? _nextFrameDelay : _regularFrameDelay;
+ _nextFrameDelay = _nextNextFrameDelay;
+ _nextNextFrameDelay = -1u;
+
+ if (size > 0) {
+ stream->skip(size);
+ }
+}
+
+void HNMDecoder::HNM4VideoTrack::decodeIntraframe(Common::SeekableReadStream *stream, uint32 size) {
+ Image::HLZDecoder::decodeFrameInPlace(*stream, size, _frameBufferC);
+ memcpy(_frameBufferP, _frameBufferC, _surface.w * _surface.h);
+ _surface.setPixels(_frameBufferC);
+
+ _curFrame++;
+ _nextFrameStartTime += _nextFrameDelay != -1u ? _nextFrameDelay : _regularFrameDelay;
+ _nextFrameDelay = _nextNextFrameDelay;
+ _nextNextFrameDelay = -1u;
+}
+
+HNMDecoder::DPCMAudioTrack::DPCMAudioTrack(uint16 channels, uint16 bits, unsigned int sampleRate,
+ Audio::Mixer::SoundType soundType) : AudioTrack(soundType), _channels(channels), _bits(bits),
+ _audioStream(nullptr), _gotLUT(false), _lastSample(0) {
+ if (bits != 16) {
+ error("Unsupported audio bits");
+ }
+ if (channels != 2) {
+ warning("Unsupported %d audio channels", channels);
+ }
+ _audioStream = Audio::makeQueuingAudioStream(sampleRate, false);
+}
+
+HNMDecoder::DPCMAudioTrack::~DPCMAudioTrack() {
+ delete _audioStream;
+}
+
+Audio::Timestamp HNMDecoder::DPCMAudioTrack::decodeSound(Common::SeekableReadStream *stream,
+ uint32 size) {
+ if (!_gotLUT) {
+ if (size < 256 * sizeof(*_lut)) {
+ error("Invalid first sound chunk");
+ }
+ stream->read(_lut, 256 * sizeof(*_lut));
+ size -= 256 * sizeof(*_lut);
+#ifndef SCUMM_LITTLE_ENDIAN
+ for (unsigned int i = 0; i < 256; i++) {
+ _lut[i] = FROM_LE_16(_lut[i]);
+ }
+#endif
+ _gotLUT = true;
+ }
+
+ if (size > 0) {
+ uint16 *out = (uint16 *)malloc(size * sizeof(*out));
+ uint16 *p = out;
+ uint16 sample = _lastSample;
+ for (uint32 i = 0; i < size; i++, p++) {
+ byte deltaId = stream->readByte();
+ sample += _lut[deltaId];
+ *p = sample;
+ }
+ _lastSample = sample;
+
+ byte flags = Audio::FLAG_16BITS;
+#ifdef SCUMM_LITTLE_ENDIAN
+ flags |= Audio::FLAG_LITTLE_ENDIAN;
+#endif
+ // channels is 2 but we are MONO!
+ _audioStream->queueBuffer((byte *)out, size * sizeof(*out), DisposeAfterUse::YES, flags);
+ }
+ return Audio::Timestamp(0, size, 22050);
+}
+
+} // End of namespace Video