mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-13 12:39:56 +00:00
8744e2300b
Currently only handles the audio track.
588 lines
15 KiB
C++
588 lines
15 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 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 "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) : _regularFrameDelay(80),
|
|
_videoTrack(nullptr), _audioTrack(nullptr), _stream(nullptr),
|
|
_loop(loop), _initialPalette(initialPalette) {
|
|
}
|
|
|
|
HNMDecoder::~HNMDecoder() {
|
|
close();
|
|
|
|
delete[] _initialPalette;
|
|
|
|
// 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 and UBB2, HNM6 in the future */
|
|
if (tag != MKTAG('H', 'N', 'M', '4') && tag != MKTAG('U', 'B', 'B', '2')) {
|
|
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 soundFormat = stream->readUint16LE();
|
|
uint32 frameSize = stream->readUint32LE();
|
|
|
|
byte unknownStr[16];
|
|
byte 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,
|
|
_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);
|
|
} else {
|
|
_audioTrack = nullptr;
|
|
}
|
|
|
|
_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 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 (_audioTrack) {
|
|
Audio::Timestamp duration = _audioTrack->decodeSound(_stream, chunkSize - 8);
|
|
_videoTrack->setFrameDelay(duration.msecs());
|
|
} else {
|
|
warning("Got audio data without an audio track");
|
|
_stream->skip(chunkSize - 8);
|
|
}
|
|
} else {
|
|
error("Got %d chunk: size %d", chunkType, chunkSize);
|
|
}
|
|
|
|
superchunkRemaining -= chunkSize;
|
|
}
|
|
}
|
|
|
|
HNMDecoder::HNM4VideoTrack::HNM4VideoTrack(uint32 width, uint32 height, uint32 frameSize,
|
|
uint32 frameCount, uint32 regularFrameDelay, const byte *initialPalette) :
|
|
_frameCount(frameCount), _regularFrameDelay(regularFrameDelay), _nextFrameStartTime(0) {
|
|
|
|
restart();
|
|
|
|
_curFrame = -1;
|
|
// Get the currently loaded palette for undefined colors
|
|
if (initialPalette) {
|
|
memcpy(_palette, initialPalette, 256 * 3);
|
|
} else {
|
|
memset(_palette, 0, 256 * 3);
|
|
}
|
|
_dirtyPalette = true;
|
|
|
|
if (width * height > frameSize) {
|
|
error("Invalid frameSize: expected %d, got %d", width * height, frameSize);
|
|
}
|
|
|
|
_frameBufferF = new byte[frameSize];
|
|
memset(_frameBufferF, 0, frameSize);
|
|
_frameBufferC = new byte[frameSize];
|
|
memset(_frameBufferC, 0, frameSize);
|
|
_frameBufferP = new byte[frameSize];
|
|
memset(_frameBufferP, 0, frameSize);
|
|
|
|
// We will use _frameBufferF/C/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() {
|
|
// 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) {
|
|
while (true) {
|
|
if (size < 2) {
|
|
break;
|
|
}
|
|
uint start = stream->readByte();
|
|
uint 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;
|
|
|
|
uint 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 & 0x1f;
|
|
byte flgs = (countFlgs >> 5) & 0x7;
|
|
|
|
if (count == 0) {
|
|
int c, fill;
|
|
switch (flgs) {
|
|
case 0:
|
|
if (size < 2) {
|
|
error("Not enough data for case 0");
|
|
}
|
|
// Copy next two bytes of input to the output
|
|
c = stream->readByte();
|
|
_frameBufferC[currentPos++] = c;
|
|
c = stream->readByte();
|
|
_frameBufferC[currentPos++] = c;
|
|
size -= 2;
|
|
break;
|
|
case 1:
|
|
if (size < 1) {
|
|
error("Not enough data for case 1");
|
|
}
|
|
// Skip (next byte of input) * 2 bytes of output
|
|
c = stream->readByte() * 2;
|
|
currentPos += c;
|
|
size -= 1;
|
|
break;
|
|
case 2:
|
|
if (size < 2) {
|
|
error("Not enough data for case 2");
|
|
}
|
|
// Skip (next word of input) * 2 bytes of output
|
|
c = stream->readUint16LE() * 2;
|
|
currentPos += c;
|
|
size -= 2;
|
|
break;
|
|
case 3:
|
|
if (size < 2) {
|
|
error("Not enough data for case 3");
|
|
}
|
|
// Fill (next byte of input) * 2 of output with (next byte of input)
|
|
c = stream->readByte() * 2;
|
|
fill = stream->readByte();
|
|
memset(&_frameBufferC[currentPos], fill, c);
|
|
currentPos += c;
|
|
size -= 2;
|
|
break;
|
|
default:
|
|
// End of picture
|
|
eop = true;
|
|
break;
|
|
}
|
|
} else {
|
|
if (size < 2) {
|
|
error("Not enough data for count > 0");
|
|
}
|
|
|
|
bool backward = (flgs & 0x4) != 0;
|
|
bool backline = (flgs & 0x2) != 0;
|
|
bool previous = (flgs & 0x1) != 0;
|
|
int offset = stream->readUint16LE();
|
|
bool swap = (offset & 0x1) != 0;
|
|
size -= 2;
|
|
|
|
offset = currentPos + (offset & 0xFFFE) - 0x8000;
|
|
if (offset < 0) {
|
|
error("Invalid offset");
|
|
}
|
|
|
|
byte *ptr;
|
|
if (previous) {
|
|
ptr = _frameBufferP;
|
|
} else {
|
|
ptr = _frameBufferC;
|
|
}
|
|
|
|
int shft1, shft2;
|
|
if (backline) {
|
|
const int twolinesabove = -(width * 2);
|
|
shft1 = twolinesabove + 1;
|
|
shft2 = 0;
|
|
} else {
|
|
shft1 = 0;
|
|
shft2 = 1;
|
|
}
|
|
if (swap)
|
|
SWAP(shft1, shft2);
|
|
|
|
int src_inc = backward ? -2 : 2;
|
|
|
|
while (count--) {
|
|
byte b0 = ptr[offset + shft1];
|
|
byte b1 = ptr[offset + shft2];
|
|
_frameBufferC[currentPos++] = b0;
|
|
_frameBufferC[currentPos++] = b1;
|
|
|
|
offset += src_inc;
|
|
}
|
|
}
|
|
}
|
|
if (size > 0) {
|
|
stream->skip(size);
|
|
}
|
|
}
|
|
|
|
void HNMDecoder::HNM4VideoTrack::decodeInterframeA(Common::SeekableReadStream *stream, uint32 size) {
|
|
SWAP(_frameBufferC, _frameBufferP);
|
|
|
|
uint16 width = _surface.w;
|
|
bool eop = false;
|
|
|
|
uint 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;
|
|
default:
|
|
error("BUG: Shouldn't be here");
|
|
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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (size > 0) {
|
|
stream->skip(size);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void HNMDecoder::HNM4VideoTrack::presentFrame(uint16 flags) {
|
|
int width = _surface.w;
|
|
int height = _surface.h;
|
|
|
|
if ((flags & 1) == 1) {
|
|
// High resolution HNM4A: no deinterlacing, use current image directly
|
|
_surface.setPixels(_frameBufferC);
|
|
} else if ((width % 4) == 0) {
|
|
// HNM4: deinterlacing must not alter the framebuffer as it will get reused as previous source for next frame
|
|
uint32 *input = (uint32 *)_frameBufferC;
|
|
uint32 *line0 = (uint32 *)_frameBufferF;
|
|
uint32 *line1 = (uint32 *)(_frameBufferF + width);
|
|
int count = (height) / 2;
|
|
while (count--) {
|
|
int i;
|
|
for (i = 0; i < width / 4; i++) {
|
|
uint32 p0 = *input++;
|
|
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);
|
|
#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);
|
|
#endif
|
|
}
|
|
line0 += width / 4;
|
|
line1 += width / 4;
|
|
}
|
|
_surface.setPixels(_frameBufferF);
|
|
} else {
|
|
error("HNMDecoder::HNM4VideoTrack::postprocess(%x): Unexpected width: %d", flags, width);
|
|
}
|
|
|
|
// Frame done
|
|
_curFrame++;
|
|
_nextFrameStartTime += _nextFrameDelay != uint32(-1) ? _nextFrameDelay : _regularFrameDelay;
|
|
_nextFrameDelay = _nextNextFrameDelay;
|
|
_nextNextFrameDelay = uint32(-1);
|
|
}
|
|
|
|
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) {
|
|
if (bits != 16) {
|
|
error("Unsupported audio bits");
|
|
}
|
|
if (format != 2) {
|
|
warning("Unsupported %d audio format", format);
|
|
}
|
|
// Format 2 is 16-bits DPCM
|
|
_audioStream = Audio::makeQueuingAudioStream(_sampleRate, _stereo);
|
|
}
|
|
|
|
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 (uint 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;
|
|
|
|
if (_audioStream->isStereo())
|
|
flags |= Audio::FLAG_STEREO;
|
|
|
|
#ifdef SCUMM_LITTLE_ENDIAN
|
|
flags |= Audio::FLAG_LITTLE_ENDIAN;
|
|
#endif
|
|
|
|
_audioStream->queueBuffer((byte *)out, size * sizeof(*out), DisposeAfterUse::YES, flags);
|
|
}
|
|
return Audio::Timestamp(0, _audioStream->isStereo() ? size / 2 : size, _sampleRate);
|
|
}
|
|
|
|
} // End of namespace Video
|