Files
scummvm-cursorfix/engines/alg/video.cpp
2026-02-02 04:50:13 +01:00

310 lines
8.3 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 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/textconsole.h"
#include "graphics/surface.h"
#include "audio/decoders/raw.h"
#include "alg/video.h"
namespace Alg {
AlgVideoDecoder::AlgVideoDecoder() {
_frame = nullptr;
_audioStream = nullptr;
}
AlgVideoDecoder::~AlgVideoDecoder() {
if (_frame) {
_frame->free();
delete _frame;
}
delete _audioStream;
}
void AlgVideoDecoder::loadVideoFromStream(uint32 offset) {
_input->seek(offset);
_size = _input->readUint32LE();
_currentFrame = 0;
uint16 chunkType = _input->readUint16LE();
uint32 chunkSize = _input->readUint32LE();
_numChunks = _input->readUint16LE();
_frameRate = _input->readUint16LE();
_videoMode = _input->readUint16LE();
_width = _input->readUint16LE();
_height = _input->readUint16LE();
uint16 typeRaw = _input->readUint16LE();
uint16 typeInter = _input->readUint16LE();
uint16 typeIntraHh = _input->readUint16LE();
uint16 typeInterHh = _input->readUint16LE();
uint16 typeIntraHhv = _input->readUint16LE();
uint16 typeInterHhv = _input->readUint16LE();
(void)chunkType;
(void)typeRaw;
(void)typeInter;
(void)typeIntraHh;
(void)typeInterHh;
(void)typeIntraHhv;
(void)typeInterHhv;
if (chunkSize == 0x18) {
_audioType = _input->readUint16LE();
}
assert(chunkType == 0x00);
assert(chunkSize == 0x16 || chunkSize == 0x18);
assert(_frameRate == 10);
assert(_videoMode == 0x13);
assert(typeRaw == 0x02);
assert(typeInter == 0x05);
assert(typeIntraHh == 0x0c);
assert(typeInterHh == 0x0d);
assert(typeIntraHhv == 0x0e);
assert(typeInterHhv == 0x0f);
_currentChunk = 0;
_bytesLeft = _size - chunkSize - 6;
if (_frame) {
_frame->free();
delete _frame;
}
delete _audioStream;
_frame = new Graphics::Surface();
_frame->create(_width, _height, Graphics::PixelFormat::createFormatCLUT8());
_audioStream = makePacketizedRawStream(8000, Audio::FLAG_UNSIGNED);
g_system->getMixer()->stopHandle(_audioHandle);
g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_audioHandle, _audioStream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
}
void AlgVideoDecoder::skipNumberOfFrames(uint32 num) {
uint32 videoFramesSkipped = 0;
while (videoFramesSkipped < num && _bytesLeft > 0) {
uint16 chunkType = _input->readUint16LE();
uint32 chunkSize = _input->readUint32LE();
_currentChunk++;
switch (chunkType) {
case MKTAG16(0x00, 0x08):
case MKTAG16(0x00, 0x0c):
case MKTAG16(0x00, 0x0e):
case MKTAG16(0x00, 0x05):
case MKTAG16(0x00, 0x0d):
case MKTAG16(0x00, 0x0f):
case MKTAG16(0x00, 0x02):
videoFramesSkipped++;
_currentFrame++;
break;
}
_input->skip(chunkSize);
_bytesLeft -= chunkSize + 6;
}
// find next keyframe
bool nextKeyframeFound = false;
while (!nextKeyframeFound && _bytesLeft > 0) {
uint16 chunkType = _input->readUint16LE();
uint32 chunkSize = _input->readUint32LE();
_currentChunk++;
switch (chunkType) {
case MKTAG16(0x00, 0x08):
case MKTAG16(0x00, 0x0c):
case MKTAG16(0x00, 0x0e):
nextKeyframeFound = true;
_input->seek(-6, SEEK_CUR);
break;
case MKTAG16(0x00, 0x05):
case MKTAG16(0x00, 0x0d):
case MKTAG16(0x00, 0x0f):
case MKTAG16(0x00, 0x02):
_input->skip(chunkSize);
_bytesLeft -= chunkSize + 6;
videoFramesSkipped++;
_currentFrame++;
break;
default:
_input->skip(chunkSize);
_bytesLeft -= chunkSize + 6;
}
}
}
void AlgVideoDecoder::readNextChunk() {
uint16 chunkType = _input->readUint16LE();
uint32 chunkSize = _input->readUint32LE();
_currentChunk++;
switch (chunkType) {
case MKTAG16(0x00, 0x00):
error("AlgVideoDecoder::readNextChunk(): got repeated header chunk");
break;
case MKTAG16(0x00, 0x30):
updatePalette(chunkSize, false);
break;
case MKTAG16(0x00, 0x31):
updatePalette(chunkSize, true);
break;
case MKTAG16(0x00, 0x15):
readAudioData(chunkSize, 8000);
break;
case MKTAG16(0x00, 0x16):
readAudioData(chunkSize, 11000);
break;
case MKTAG16(0x00, 0x08):
decodeIntraFrame(chunkSize, 0, 0);
_gotVideoFrame = true;
break;
case MKTAG16(0x00, 0x0c):
decodeIntraFrame(chunkSize, 1, 0);
_gotVideoFrame = true;
break;
case MKTAG16(0x00, 0x0e):
decodeIntraFrame(chunkSize, 1, 1);
_gotVideoFrame = true;
break;
case MKTAG16(0x00, 0x05):
decodeInterFrame(chunkSize, 0, 0);
_gotVideoFrame = true;
break;
case MKTAG16(0x00, 0x0d):
decodeInterFrame(chunkSize, 1, 0);
_gotVideoFrame = true;
break;
case MKTAG16(0x00, 0x0f):
decodeInterFrame(chunkSize, 1, 1);
_gotVideoFrame = true;
break;
case MKTAG16(0x00, 0x02):
warning("AlgVideoDecoder::readNextChunk(): raw video not supported");
_input->skip(chunkSize);
break;
default:
error("AlgVideoDecoder::readNextChunk(): Unknown chunk encountered: %d", chunkType);
}
_bytesLeft -= chunkSize + 6;
}
void AlgVideoDecoder::getNextFrame() {
_paletteDirty = false;
_gotVideoFrame = false;
while (!_gotVideoFrame && _bytesLeft > 0) {
readNextChunk();
}
_currentFrame++;
}
void AlgVideoDecoder::decodeIntraFrame(uint32 size, uint8 hh, uint8 hv) {
uint16 x = 0, y = 0;
int32 bytesRemaining = size;
int32 runLength = 0;
uint8 readByte, color = 0;
while (bytesRemaining > 0) {
readByte = _input->readByte();
if (readByte & 0x80) {
runLength = 1;
color = readByte;
bytesRemaining--;
} else {
runLength = (readByte & 0x7F) + 2;
color = _input->readByte();
bytesRemaining -= 2;
}
if (color > 0) {
memset(_frame->getBasePtr(x, y), color, runLength * (1 + hh));
if (hv) {
memset(_frame->getBasePtr(x, y + 1), color, runLength * (1 + hh));
}
}
x += runLength + (hh * runLength);
if (x >= _width) {
x = 0;
y += 1 + hv;
}
}
assert(bytesRemaining == 0);
(void)bytesRemaining;
}
void AlgVideoDecoder::decodeInterFrame(uint32 size, uint8 hh, uint8 hv) {
uint32 bytesRead = 0;
uint16 length = 0, x = 0, y = 0, replacementBytesLeft = 0;
replacementBytesLeft = _input->readUint16LE();
bytesRead += 2;
if (replacementBytesLeft == 0) {
_input->skip(size - 2);
return;
}
Common::SeekableReadStream *replacement = _input->readStream(replacementBytesLeft);
bytesRead += replacementBytesLeft;
while (replacementBytesLeft > 1) {
length = replacement->readByte();
x = replacement->readByte() + ((length & 0x80) << 1);
length &= 0x7F;
replacementBytesLeft -= 2;
if (length == 0) {
y += x;
continue;
}
for (uint32 i = 0; i < length; i++) {
uint8 replaceArray = replacement->readByte();
for (uint8 j = 0x80; j > 0; j = j >> 1) {
if (replaceArray & j) {
uint8 color = _input->readByte();
bytesRead++;
memset(_frame->getBasePtr(x, y), color, (1 + hh));
if (hv) {
memset(_frame->getBasePtr(x, y + 1), color, (1 + hh));
}
}
x += 1 + hh;
}
}
y += 1 + hv;
}
delete replacement;
assert(bytesRead == size);
(void)bytesRead;
}
void AlgVideoDecoder::updatePalette(uint32 size, bool partial) {
_paletteDirty = true;
uint32 bytesRead = 0;
uint16 start = 0, count = 256;
if (partial) {
start = _input->readUint16LE();
count = _input->readUint16LE();
bytesRead += 4;
}
uint16 paletteIndex = start * 3;
for (uint16 i = 0; i < count; i++) {
uint8 r = _input->readByte() * 4;
uint8 g = _input->readByte() * 4;
uint8 b = _input->readByte() * 4;
_palette[paletteIndex++] = r;
_palette[paletteIndex++] = g;
_palette[paletteIndex++] = b;
bytesRead += 3;
}
assert(bytesRead == size);
(void)bytesRead;
}
void AlgVideoDecoder::readAudioData(uint32 size, uint16 rate) {
assert(_audioType == 21);
(void)_audioType;
_audioStream->queuePacket(_input->readStream(size));
}
} // End of namespace Alg