Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

484
video/3do_decoder.cpp Normal file
View File

@@ -0,0 +1,484 @@
/* 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/scummsys.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "audio/audiostream.h"
#include "audio/decoders/3do.h"
#include "video/3do_decoder.h"
#include "image/codecs/cinepak.h"
// for Test-Code
#include "common/system.h"
#include "common/events.h"
#include "common/keyboard.h"
#include "engines/engine.h"
#include "engines/util.h"
#include "graphics/pixelformat.h"
#include "graphics/surface.h"
namespace Video {
ThreeDOMovieDecoder::ThreeDOMovieDecoder()
: _stream(0), _videoTrack(0) {
_streamVideoOffset = 0;
_streamAudioOffset = 0;
}
ThreeDOMovieDecoder::~ThreeDOMovieDecoder() {
close();
}
VideoDecoder::AudioTrack* ThreeDOMovieDecoder::getAudioTrack(int index) {
return _audioTracks[index];
}
bool ThreeDOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
uint32 videoSubType = 0;
uint32 videoCodecTag = 0;
uint32 videoHeight = 0;
uint32 videoWidth = 0;
uint32 videoFrameCount = 0;
uint32 audioSubType = 0;
uint32 audioCodecTag = 0;
uint32 audioChannels = 0;
uint32 audioSampleRate = 0;
uint32 audioTrackId = 0;
close();
_stream = stream;
_streamVideoOffset = 0;
_streamAudioOffset = 0;
// Look for packets that we care about
static const int maxPacketCheckCount = 20;
for (int i = 0; i < maxPacketCheckCount; i++) {
uint32 chunkTag = _stream->readUint32BE();
uint32 chunkSize = _stream->readUint32BE() - 8;
// Bail out if done
if (_stream->eos())
break;
uint32 dataStartOffset = _stream->pos();
switch (chunkTag) {
case MKTAG('F','I','L','M'): {
// See if this is a FILM header
_stream->skip(4); // time stamp (based on 240 per second)
_stream->skip(4); // Unknown 0x00000000
videoSubType = _stream->readUint32BE();
switch (videoSubType) {
case MKTAG('F', 'H', 'D', 'R'):
// FILM header found
if (_videoTrack) {
warning("3DO movie: Multiple FILM headers found");
close();
return false;
}
_stream->readUint32BE();
videoCodecTag = _stream->readUint32BE();
videoHeight = _stream->readUint32BE();
videoWidth = _stream->readUint32BE();
_stream->skip(4); // time scale
videoFrameCount = _stream->readUint32BE();
_videoTrack = new StreamVideoTrack(videoWidth, videoHeight, videoCodecTag, videoFrameCount);
addTrack(_videoTrack);
break;
case MKTAG('F', 'R', 'M', 'E'):
break;
default:
warning("3DO movie: Unknown subtype inside FILM packet");
close();
return false;
}
break;
}
case MKTAG('S','N','D','S'): {
_stream->readUint32BE(); // Unknown
audioTrackId = _stream->readUint32BE();
audioSubType = _stream->readUint32BE();
switch (audioSubType) {
case MKTAG('S', 'H', 'D', 'R'): {
// Audio header
// OK, this is the start of a audio stream
_stream->readUint32BE(); // Version, always 0x00000000
_stream->readUint32BE(); // Unknown 0x00000008 ?!
_stream->readUint32BE(); // Unknown 0x00007500
_stream->readUint32BE(); // Unknown 0x00004000
_stream->readUint32BE(); // Unknown 0x00000000
_stream->readUint32BE(); // Unknown 0x00000010
audioSampleRate = _stream->readUint32BE();
audioChannels = _stream->readUint32BE();
audioCodecTag = _stream->readUint32BE();
_stream->readUint32BE(); // Unknown 0x00000004 compression ratio?
_stream->readUint32BE(); // Unknown 0x00000A2C
StreamAudioTrack *track = new StreamAudioTrack(audioCodecTag, audioSampleRate, audioChannels,
getSoundType(), audioTrackId);
addTrack(track);
_audioTracks.push_back(track);
break;
}
case MKTAG('S', 'S', 'M', 'P'):
// Audio data
break;
default:
warning("3DO movie: Unknown subtype inside FILM packet");
close();
return false;
}
break;
}
case MKTAG('C','T','R','L'):
case MKTAG('F','I','L','L'): // filler chunk, fills to certain boundary
case MKTAG('D','A','C','Q'):
case MKTAG('J','O','I','N'): // add cel data (not used in sherlock)
// Ignore these chunks
break;
case MKTAG('S','H','D','R'):
// Happens for EA logo, seems to be garbage data right at the start of the file
break;
default:
warning("Unknown chunk-tag '%s' inside 3DO movie", tag2str(chunkTag));
close();
return false;
}
if ((_videoTrack) && (!_audioTracks.empty()))
break;
// Seek to next chunk
_stream->seek(dataStartOffset + chunkSize);
}
// Bail if we didn't find video + audio
if ((!_videoTrack) || (_audioTracks.empty())) {
close();
return false;
}
// Rewind back to the beginning
_stream->seek(0);
return true;
}
void ThreeDOMovieDecoder::close() {
Video::VideoDecoder::close();
delete _stream; _stream = 0;
_videoTrack = 0;
}
// We try to at least decode 1 frame
// and also try to get at least 0.5 seconds of audio queued up
void ThreeDOMovieDecoder::readNextPacket() {
uint32 currentMovieTime = getTime();
uint32 wantedAudioQueued = currentMovieTime + 500; // always try to be 0.500 seconds in front of movie time
int32 chunkOffset = 0;
int32 dataStartOffset = 0;
int32 nextChunkOffset = 0;
uint32 chunkTag = 0;
uint32 chunkSize = 0;
uint32 videoSubType = 0;
uint32 videoTimeStamp = 0;
uint32 videoFrameSize = 0;
uint32 audioSubType = 0;
uint32 audioBytes = 0;
uint32 audioTrackId = 0;
bool videoGotFrame = false;
bool videoDone = false;
bool audioDone = false;
// Seek to smallest stream offset
if (_streamVideoOffset <= _streamAudioOffset) {
_stream->seek(_streamVideoOffset);
} else {
_stream->seek(_streamAudioOffset);
}
if (wantedAudioQueued <= _audioTracks[0]->getTotalAudioQueued()) {
// already got enough audio queued up
audioDone = true;
}
while (1) {
chunkOffset = _stream->pos();
assert(chunkOffset >= 0);
// Read chunk header
chunkTag = _stream->readUint32BE();
chunkSize = _stream->readUint32BE() - 8;
// Calculate offsets
dataStartOffset = _stream->pos();
assert(dataStartOffset >= 0);
nextChunkOffset = dataStartOffset + chunkSize;
//warning("offset %lx - tag %lx", dataStartOffset, tag);
if (_stream->eos())
break;
switch (chunkTag) {
case MKTAG('F','I','L','M'):
videoTimeStamp = _stream->readUint32BE();
_stream->skip(4); // Unknown
videoSubType = _stream->readUint32BE();
switch (videoSubType) {
case MKTAG('F', 'H', 'D', 'R'):
// Ignore video header
break;
case MKTAG('F', 'R', 'M', 'E'):
// Found frame data
if (_streamVideoOffset <= chunkOffset) {
// We are at an offset that is still relevant to video decoding
if (!videoDone) {
if (!videoGotFrame) {
// We haven't decoded any frame yet, so do so now
_stream->readUint32BE();
videoFrameSize = _stream->readUint32BE();
_videoTrack->decodeFrame(_stream->readStream(videoFrameSize), videoTimeStamp);
_streamVideoOffset = nextChunkOffset;
videoGotFrame = true;
} else {
// Already decoded a frame, so get timestamp of follow-up frame
// and then we are done with video
// Calculate next frame time
// 3DO clock time for movies runs at 240Hh, that's why timestamps are based on 240.
uint32 currentFrameStartTime = _videoTrack->getNextFrameStartTime();
uint32 nextFrameStartTime = videoTimeStamp * 1000 / 240;
assert(currentFrameStartTime <= nextFrameStartTime);
(void)currentFrameStartTime;
_videoTrack->setNextFrameStartTime(nextFrameStartTime);
// next time we want to start at the current chunk
_streamVideoOffset = chunkOffset;
videoDone = true;
}
}
}
break;
default:
error("3DO movie: Unknown subtype inside FILM packet");
break;
}
break;
case MKTAG('S','N','D','S'):
_stream->readUint32BE();
audioTrackId = _stream->readUint32BE();
audioSubType = _stream->readUint32BE();
switch (audioSubType) {
case MKTAG('S', 'H', 'D', 'R'):
// Ignore the audio header
break;
case MKTAG('S', 'S', 'M', 'P'):
// Got audio chunk
if (_streamAudioOffset <= chunkOffset) {
// We are at an offset that is still relevant to audio decoding
if (!audioDone) {
uint queued = 0;
audioBytes = _stream->readUint32BE();
for (uint i = 0; i < _audioTracks.size(); i++)
if (_audioTracks[i]->matchesId(audioTrackId)) {
_audioTracks[i]->queueAudio(_stream, audioBytes);
queued = _audioTracks[i]->getTotalAudioQueued();
}
_streamAudioOffset = nextChunkOffset;
if (wantedAudioQueued <= queued) {
// Got enough audio
audioDone = true;
}
}
}
break;
default:
error("3DO movie: Unknown subtype inside SNDS packet");
break;
}
break;
case MKTAG('C','T','R','L'):
case MKTAG('F','I','L','L'): // filler chunk, fills to certain boundary
case MKTAG('D','A','C','Q'):
case MKTAG('J','O','I','N'): // add cel data (not used in sherlock)
// Ignore these chunks
break;
case MKTAG('S','H','D','R'):
// Happens for EA logo, seems to be garbage data right at the start of the file
break;
default:
error("Unknown chunk-tag '%s' inside 3DO movie", tag2str(chunkTag));
}
// Always seek to end of chunk
// Sometimes not all of the chunk is filled with audio
_stream->seek(nextChunkOffset);
if ((videoDone) && (audioDone)) {
return;
}
}
}
ThreeDOMovieDecoder::StreamVideoTrack::StreamVideoTrack(uint32 width, uint32 height, uint32 codecTag, uint32 frameCount) {
_width = width;
_height = height;
_frameCount = frameCount;
_curFrame = -1;
_nextFrameStartTime = 0;
// Create the Cinepak decoder, if we're using it
if (codecTag == MKTAG('c', 'v', 'i', 'd'))
_codec = new Image::CinepakDecoder();
else
error("Unsupported 3DO movie video codec tag '%s'", tag2str(codecTag));
}
ThreeDOMovieDecoder::StreamVideoTrack::~StreamVideoTrack() {
delete _codec;
}
bool ThreeDOMovieDecoder::StreamVideoTrack::endOfTrack() const {
return getCurFrame() >= getFrameCount() - 1;
}
Graphics::PixelFormat ThreeDOMovieDecoder::StreamVideoTrack::getPixelFormat() const {
return _codec->getPixelFormat();
}
bool ThreeDOMovieDecoder::StreamVideoTrack::setOutputPixelFormat(const Graphics::PixelFormat &format) {
return _codec->setOutputPixelFormat(format);
}
void ThreeDOMovieDecoder::StreamVideoTrack::decodeFrame(Common::SeekableReadStream *stream, uint32 videoTimeStamp) {
_surface = _codec->decodeFrame(*stream);
_curFrame++;
}
ThreeDOMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels, Audio::Mixer::SoundType soundType, uint32 trackId) :
AudioTrack(soundType) {
switch (codecTag) {
case MKTAG('A','D','P','4'):
case MKTAG('S','D','X','2'):
// ADP4 + SDX2 are both allowed
break;
default:
error("Unsupported 3DO movie audio codec tag '%s'", tag2str(codecTag));
}
_totalAudioQueued = 0; // currently 0 milliseconds queued
_codecTag = codecTag;
_sampleRate = sampleRate;
_trackId = trackId;
switch (channels) {
case 1:
_stereo = false;
break;
case 2:
_stereo = true;
break;
default:
error("Unsupported 3DO movie audio channels %d", channels);
}
_audioStream = Audio::makeQueuingAudioStream(sampleRate, _stereo);
// reset audio decoder persistent spaces
memset(&_ADP4_PersistentSpace, 0, sizeof(_ADP4_PersistentSpace));
memset(&_SDX2_PersistentSpace, 0, sizeof(_SDX2_PersistentSpace));
}
ThreeDOMovieDecoder::StreamAudioTrack::~StreamAudioTrack() {
delete _audioStream;
// free(_ADP4_PersistentSpace);
// free(_SDX2_PersistentSpace);
}
bool ThreeDOMovieDecoder::StreamAudioTrack::matchesId(uint tid) {
return _trackId == tid;
}
void ThreeDOMovieDecoder::StreamAudioTrack::queueAudio(Common::SeekableReadStream *stream, uint32 size) {
Common::SeekableReadStream *compressedAudioStream = 0;
Audio::RewindableAudioStream *audioStream = 0;
uint32 audioLengthMSecs = 0;
// Read the specified chunk into memory
compressedAudioStream = stream->readStream(size);
switch(_codecTag) {
case MKTAG('A','D','P','4'):
audioStream = Audio::make3DO_ADP4AudioStream(compressedAudioStream, _sampleRate, _stereo, &audioLengthMSecs, DisposeAfterUse::YES, &_ADP4_PersistentSpace);
break;
case MKTAG('S','D','X','2'):
audioStream = Audio::make3DO_SDX2AudioStream(compressedAudioStream, _sampleRate, _stereo, &audioLengthMSecs, DisposeAfterUse::YES, &_SDX2_PersistentSpace);
break;
default:
break;
}
if (audioStream) {
_totalAudioQueued += audioLengthMSecs;
_audioStream->queueAudioStream(audioStream, DisposeAfterUse::YES);
} else {
// in case there was an error
delete compressedAudioStream;
}
}
Audio::AudioStream *ThreeDOMovieDecoder::StreamAudioTrack::getAudioStream() const {
return _audioStream;
}
} // End of namespace Video

136
video/3do_decoder.h Normal file
View File

@@ -0,0 +1,136 @@
/* 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 SHERLOCK_SCALPEL_3DO_MOVIE_DECODER_H
#define SHERLOCK_SCALPEL_3DO_MOVIE_DECODER_H
#include "common/rect.h"
#include "video/video_decoder.h"
#include "audio/decoders/3do.h"
namespace Audio {
class QueuingAudioStream;
}
namespace Common {
class SeekableReadStream;
}
namespace Image {
class Codec;
}
namespace Video {
/**
* Decoder for 3DO videos.
*
* Video decoder used in engines:
* - sherlock
* - plumbers
*/
class ThreeDOMovieDecoder : public Video::VideoDecoder {
public:
ThreeDOMovieDecoder();
~ThreeDOMovieDecoder() override;
bool loadStream(Common::SeekableReadStream *stream) override;
void close() override;
protected:
void readNextPacket() override;
bool supportsAudioTrackSwitching() const override { return true; }
AudioTrack *getAudioTrack(int index) override;
private:
int32 _streamVideoOffset; /* current stream offset for video decoding */
int32 _streamAudioOffset; /* current stream offset for audio decoding */
private:
class StreamVideoTrack : public VideoTrack {
public:
StreamVideoTrack(uint32 width, uint32 height, uint32 codecTag, uint32 frameCount);
~StreamVideoTrack() override;
bool endOfTrack() const override;
uint16 getWidth() const override { return _width; }
uint16 getHeight() const override { return _height; }
Graphics::PixelFormat getPixelFormat() const override;
bool setOutputPixelFormat(const Graphics::PixelFormat &format) override;
int getCurFrame() const override { return _curFrame; }
int getFrameCount() const override { return _frameCount; }
void setNextFrameStartTime(uint32 nextFrameStartTime) { _nextFrameStartTime = nextFrameStartTime; }
uint32 getNextFrameStartTime() const override { return _nextFrameStartTime; }
const Graphics::Surface *decodeNextFrame() override { return _surface; }
void decodeFrame(Common::SeekableReadStream *stream, uint32 videoTimeStamp);
private:
const Graphics::Surface *_surface;
int _curFrame;
uint32 _frameCount;
uint32 _nextFrameStartTime;
Image::Codec *_codec;
uint16 _width, _height;
};
class StreamAudioTrack : public AudioTrack {
public:
StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels, Audio::Mixer::SoundType soundType, uint32 trackId);
~StreamAudioTrack() override;
void queueAudio(Common::SeekableReadStream *stream, uint32 size);
bool matchesId(uint trackId);
protected:
Audio::AudioStream *getAudioStream() const override;
private:
Audio::QueuingAudioStream *_audioStream;
uint32 _totalAudioQueued; /* total amount of milliseconds of audio, that we queued up already */
public:
uint32 getTotalAudioQueued() const { return _totalAudioQueued; }
private:
int16 decodeSample(uint8 dataNibble);
uint32 _codecTag;
uint16 _sampleRate;
bool _stereo;
uint32 _trackId;
Audio::audio_3DO_ADP4_PersistentSpace _ADP4_PersistentSpace;
Audio::audio_3DO_SDX2_PersistentSpace _SDX2_PersistentSpace;
};
Common::SeekableReadStream *_stream;
StreamVideoTrack *_videoTrack;
Common::Array<StreamAudioTrack *> _audioTracks;
};
} // End of namespace Sherlock
#endif

1250
video/avi_decoder.cpp Normal file

File diff suppressed because it is too large Load Diff

375
video/avi_decoder.h Normal file
View File

@@ -0,0 +1,375 @@
/* 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 VIDEO_AVI_DECODER_H
#define VIDEO_AVI_DECODER_H
#include "common/array.h"
#include "common/rational.h"
#include "common/rect.h"
#include "common/str.h"
#include "graphics/palette.h"
#include "video/video_decoder.h"
#include "audio/mixer.h"
namespace Audio {
class AudioStream;
class PacketizedAudioStream;
}
namespace Common {
class SeekableReadStream;
}
namespace Graphics {
struct PixelFormat;
}
namespace Image {
class Codec;
}
namespace Video {
/**
* Decoder for AVI videos.
*
* Video decoder used in engines:
* - petka
* - sci
* - sword1
* - sword2
* - titanic
* - vcruise
* - zvision
*/
class AVIDecoder : public VideoDecoder {
public:
AVIDecoder();
AVIDecoder(const Common::Rational &frameRateOverride);
virtual ~AVIDecoder();
bool loadStream(Common::SeekableReadStream *stream);
void close();
uint16 getWidth() const { return _header.width; }
uint16 getHeight() const { return _header.height; }
bool rewind();
bool isRewindable() const { return true; }
bool isSeekable() const;
/**
* Decode the next frame into a surface and return the latter.
*
* A subclass may override this, but must still call this function. As an
* example, a subclass may do this to apply some global video scale to
* individual track's frame.
*
* Note that this will call readNextPacket() internally first before calling
* the next video track's decodeNextFrame() function.
*
* @return a surface containing the decoded frame, or 0
* @note Ownership of the returned surface stays with the VideoDecoder,
* hence the caller must *not* free it.
* @note this may return 0, in which case the last frame should be kept on screen
*/
virtual const Graphics::Surface *decodeNextFrame();
/**
* Decodes the next transparency track frame
*/
const Graphics::Surface *decodeNextTransparency();
protected:
// VideoDecoder API
void readNextPacket();
bool seekIntern(const Audio::Timestamp &time);
bool supportsAudioTrackSwitching() const { return true; }
AudioTrack *getAudioTrack(int index);
/**
* Define a track to be used by this class.
*
* The pointer is then owned by this base class.
*
* @param track The track to add
* @param isExternal Is this an external track not found by loadStream()?
*/
void addTrack(Track *track, bool isExternal = false);
struct BitmapInfoHeader {
uint32 size;
uint32 width;
uint32 height;
uint16 planes;
uint16 bitCount;
uint32 compression;
uint32 sizeImage;
uint32 xPelsPerMeter;
uint32 yPelsPerMeter;
uint32 clrUsed;
uint32 clrImportant;
};
struct WaveFormat {
uint16 tag;
uint16 channels;
uint32 samplesPerSec;
uint32 avgBytesPerSec;
uint16 blockAlign;
};
struct PCMWaveFormat : public WaveFormat {
uint16 size;
};
struct WaveFormatEX : public WaveFormat {
uint16 bitsPerSample;
uint16 size;
};
struct OldIndex {
uint32 id;
uint32 flags;
uint32 offset;
uint32 size;
};
// Index Flags
enum IndexFlags {
AVIIF_INDEX = 0x10
};
struct AVIHeader {
uint32 size;
uint32 microSecondsPerFrame;
uint32 maxBytesPerSecond;
uint32 padding;
uint32 flags;
uint32 totalFrames;
uint32 initialFrames;
uint32 streams;
uint32 bufferSize;
uint32 width;
uint32 height;
};
// Flags from the AVIHeader
enum AVIFlags {
AVIF_HASINDEX = 0x00000010,
AVIF_MUSTUSEINDEX = 0x00000020,
AVIF_ISINTERLEAVED = 0x00000100,
AVIF_TRUSTCKTYPE = 0x00000800,
AVIF_WASCAPTUREFILE = 0x00010000,
AVIF_WASCOPYRIGHTED = 0x00020000
};
struct AVIStreamHeader {
uint32 size;
uint32 streamType;
uint32 streamHandler;
uint32 flags;
uint16 priority;
uint16 language;
uint32 initialFrames;
uint32 scale;
uint32 rate;
uint32 start;
uint32 length;
uint32 bufferSize;
uint32 quality;
uint32 sampleSize;
Common::Rect frame;
Common::String name;
};
class AVIVideoTrack : public FixedRateVideoTrack {
public:
AVIVideoTrack(int frameCount, const AVIStreamHeader &streamHeader, const BitmapInfoHeader &bitmapInfoHeader, byte *initialPalette, Image::CodecAccuracy accuracy);
~AVIVideoTrack();
void decodeFrame(Common::SeekableReadStream *stream);
void forceTrackEnd();
uint16 getWidth() const { return _bmInfo.width; }
uint16 getHeight() const { return _bmInfo.height; }
uint16 getBitCount() const { return _bmInfo.bitCount; }
Graphics::PixelFormat getPixelFormat() const;
bool setOutputPixelFormat(const Graphics::PixelFormat &format);
void setCodecAccuracy(Image::CodecAccuracy accuracy);
int getCurFrame() const { return _curFrame; }
int getFrameCount() const { return _frameCount; }
Common::String &getName() { return _vidsHeader.name; }
const Graphics::Surface *decodeNextFrame() { return _lastFrame; }
const byte *getPalette() const;
bool hasDirtyPalette() const;
void setCurFrame(int frame) { _curFrame = frame; }
void loadPaletteFromChunk(Common::SeekableReadStream *chunk);
void loadPaletteFromChunkRaw(Common::SeekableReadStream *chunk, int firstEntry, int numEntries);
void useInitialPalette();
bool canDither() const;
void setDither(const byte *palette);
bool isValid() const { return _videoCodec != nullptr; }
bool isTruemotion1() const;
void forceDimensions(uint16 width, uint16 height);
bool isRewindable() const { return true; }
bool rewind();
/**
* Set the video track to play in reverse or forward.
*
* By default, a VideoTrack must decode forward.
*
* @param reverse true for reverse, false for forward
* @return true for success, false for failure
*/
virtual bool setReverse(bool reverse);
/**
* Is the video track set to play in reverse?
*/
virtual bool isReversed() const { return _reversed; }
/**
* Returns true if at the end of the video track
*/
virtual bool endOfTrack() const;
/**
* Get track frame rate
*/
Common::Rational getFrameRate() const { return Common::Rational(_vidsHeader.rate, _vidsHeader.scale); }
/**
* Force sets a new frame rate
*/
void setFrameRate(const Common::Rational &r) {
_vidsHeader.rate = r.getNumerator();
_vidsHeader.scale = r.getDenominator();
}
private:
AVIStreamHeader _vidsHeader;
BitmapInfoHeader _bmInfo;
Graphics::Palette _palette;
byte *_initialPalette;
mutable bool _dirtyPalette;
int _frameCount, _curFrame;
bool _reversed;
Image::Codec *_videoCodec;
const Graphics::Surface *_lastFrame;
Image::CodecAccuracy _accuracy;
Image::Codec *createCodec();
};
class AVIAudioTrack : public AudioTrack {
public:
AVIAudioTrack(const AVIStreamHeader &streamHeader, const PCMWaveFormat &waveFormat, Audio::Mixer::SoundType soundType);
~AVIAudioTrack();
virtual void createAudioStream();
virtual void queueSound(Common::SeekableReadStream *stream);
void skipAudio(const Audio::Timestamp &time, const Audio::Timestamp &frameTime);
virtual void resetStream();
uint32 getCurChunk() const { return _curChunk; }
Common::String &getName() { return _audsHeader.name; }
void setCurChunk(uint32 chunk) { _curChunk = chunk; }
bool isRewindable() const { return true; }
bool rewind();
protected:
Audio::AudioStream *getAudioStream() const { return _audioStream; }
AVIStreamHeader _audsHeader;
PCMWaveFormat _wvInfo;
Audio::AudioStream *_audioStream;
Audio::PacketizedAudioStream *_packetStream;
uint32 _curChunk;
};
struct TrackStatus {
TrackStatus();
Track *track;
uint32 index;
uint32 chunkSearchOffset;
};
class IndexEntries : public Common::Array<OldIndex> {
public:
OldIndex *find(uint index, uint frameNumber);
};
AVIHeader _header;
void readOldIndex(uint32 size);
IndexEntries _indexEntries;
Common::SeekableReadStream *_fileStream;
bool _decodedHeader;
bool _foundMovieList;
uint32 _movieListStart, _movieListEnd;
Common::Rational _frameRateOverride;
int _videoTrackCounter, _audioTrackCounter;
Track *_lastAddedTrack;
void initCommon();
bool parseNextChunk();
void skipChunk(uint32 size);
void handleList(uint32 listSize);
bool handleStreamHeader(uint32 size);
void readStreamName(uint32 size);
void readPalette8(uint32 size);
uint16 getStreamType(uint32 tag) const { return tag & 0xFFFF; }
static byte getStreamIndex(uint32 tag);
void checkTruemotion1();
uint getVideoTrackOffset(uint trackIndex, uint frameNumber = 0);
void handleNextPacket(TrackStatus& status);
bool shouldQueueAudio(TrackStatus& status);
void seekTransparencyFrame(int frame);
Common::Array<TrackStatus> _videoTracks, _audioTracks;
TrackStatus _transparencyTrack;
public:
virtual AVIAudioTrack *createAudioTrack(AVIStreamHeader sHeader, PCMWaveFormat wvInfo);
/**
* Seek to a given frame.
*
* This only works when the video track(s) supports getFrameTime().
* This calls seek() internally.
*/
virtual bool seekToFrame(uint frame);
};
} // End of namespace Video
#endif

1747
video/bink_decoder.cpp Normal file

File diff suppressed because it is too large Load Diff

392
video/bink_decoder.h Normal file
View File

@@ -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 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/>.
*
*/
// Based on eos' Bink decoder which is in turn
// based quite heavily on the Bink decoder found in FFmpeg.
// Many thanks to Kostya Shishkov for doing the hard work.
#include "common/scummsys.h"
#ifdef USE_BINK
#ifndef VIDEO_BINK_DECODER_H
#define VIDEO_BINK_DECODER_H
#include "common/array.h"
#include "common/bitstream.h"
#include "common/rational.h"
#include "video/video_decoder.h"
#include "graphics/surface.h"
namespace Audio {
class AudioStream;
class QueuingAudioStream;
}
namespace Common {
class SeekableReadStream;
template <class BITSTREAM>
class Huffman;
}
namespace Math {
class RDFT;
class DCT;
}
namespace Graphics {
struct Surface;
}
namespace Video {
/**
* Decoder for Bink videos.
*
* Video decoder used in engines:
* - scumm (he)
*/
class BinkDecoder : public VideoDecoder {
public:
BinkDecoder();
~BinkDecoder();
bool loadStream(Common::SeekableReadStream *stream);
void close();
Common::Rational getFrameRate();
protected:
void readNextPacket();
bool supportsAudioTrackSwitching() const { return true; }
AudioTrack *getAudioTrack(int index);
bool seekIntern(const Audio::Timestamp &time);
uint32 findKeyFrame(uint32 frame) const;
private:
static const int kAudioChannelsMax = 2;
static const int kAudioBlockSizeMax = (kAudioChannelsMax << 11);
enum AudioCodec {
kAudioCodecDCT,
kAudioCodecRDFT
};
/** An audio track. */
struct AudioInfo {
uint16 flags;
uint32 sampleRate;
uint8 channels;
uint32 outSampleRate;
uint8 outChannels;
AudioCodec codec;
uint32 sampleCount;
Common::BitStream32LELSB *bits;
bool first;
uint32 frameLen;
uint32 overlapLen;
uint32 blockSize;
uint32 bandCount;
uint32 *bands;
float root;
float coeffs[16 * kAudioBlockSizeMax];
int16 prevCoeffs[kAudioBlockSizeMax];
float *coeffsPtr[kAudioChannelsMax];
Math::RDFT *rdft;
Math::DCT *dct;
AudioInfo();
~AudioInfo();
};
/** A video frame. */
struct VideoFrame {
bool keyFrame;
uint32 offset;
uint32 size;
Common::BitStream32LELSB *bits;
VideoFrame();
~VideoFrame();
};
class BinkVideoTrack : public FixedRateVideoTrack {
public:
BinkVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, bool swapPlanes, bool hasAlpha, uint32 id);
~BinkVideoTrack();
uint16 getWidth() const override { return _width; }
uint16 getHeight() const override{ return _height; }
Graphics::PixelFormat getPixelFormat() const override { return _pixelFormat; }
bool setOutputPixelFormat(const Graphics::PixelFormat &format) override {
if (format.bytesPerPixel != 2 && format.bytesPerPixel != 4)
return false;
_pixelFormat = format;
return true;
}
int getCurFrame() const override { return _curFrame; }
int getFrameCount() const override { return _frameCount; }
const Graphics::Surface *decodeNextFrame() override { return _surface; }
bool isSeekable() const override{ return true; }
bool seek(const Audio::Timestamp &time) override { return true; }
bool rewind() override;
void setCurFrame(uint32 frame) { _curFrame = frame; }
/** Decode a video packet. */
void decodePacket(VideoFrame &frame);
Common::Rational getFrameRate() const override { return _frameRate; }
private:
/** A decoder state. */
struct DecodeContext {
VideoFrame *video;
uint32 planeIdx;
uint32 blockX;
uint32 blockY;
byte *dest;
byte *prev;
byte *destStart, *destEnd;
byte *prevStart, *prevEnd;
uint32 pitch;
int coordMap[64];
int coordScaledMap1[64];
int coordScaledMap2[64];
int coordScaledMap3[64];
int coordScaledMap4[64];
};
/** IDs for different data types used in Bink video codec. */
enum Source {
kSourceBlockTypes = 0, ///< 8x8 block types.
kSourceSubBlockTypes , ///< 16x16 block types (a subset of 8x8 block types).
kSourceColors , ///< Pixel values used for different block types.
kSourcePattern , ///< 8-bit values for 2-color pattern fill.
kSourceXOff , ///< X components of motion value.
kSourceYOff , ///< Y components of motion value.
kSourceIntraDC , ///< DC values for intrablocks with DCT.
kSourceInterDC , ///< DC values for interblocks with DCT.
kSourceRun , ///< Run lengths for special fill block.
kSourceMAX
};
/** Bink video block types. */
enum BlockType {
kBlockSkip = 0, ///< Skipped block.
kBlockScaled , ///< Block has size 16x16.
kBlockMotion , ///< Block is copied from previous frame with some offset.
kBlockRun , ///< Block is composed from runs of colors with custom scan order.
kBlockResidue , ///< Motion block with some difference added.
kBlockIntra , ///< Intra DCT block.
kBlockFill , ///< Block is filled with single color.
kBlockInter , ///< Motion block with DCT applied to the difference.
kBlockPattern , ///< Block is filled with two colors following custom pattern.
kBlockRaw ///< Uncoded 8x8 block.
};
/** Data structure for decoding and tranlating Huffman'd data. */
struct Huffman {
int index; ///< Index of the Huffman codebook to use.
byte symbols[16]; ///< Huffman symbol => Bink symbol tranlation list.
};
/** Data structure used for decoding a single Bink data type. */
struct Bundle {
int countLengths[2]; ///< Lengths of number of entries to decode (in bits).
int countLength; ///< Length of number of entries to decode (in bits) for the current plane.
Huffman huffman; ///< Huffman codebook.
byte *data; ///< Buffer for decoded symbols.
byte *dataEnd; ///< Buffer end.
byte *curDec; ///< Pointer to the data that wasn't yet decoded.
byte *curPtr; ///< Pointer to the data that wasn't yet read.
};
int _curFrame;
int _frameCount;
Graphics::Surface *_surface;
Graphics::PixelFormat _pixelFormat;
uint16 _width;
uint16 _height;
int _surfaceWidth; ///< The actual surface width
int _surfaceHeight; ///< The actual surface height
uint32 _id; ///< The BIK FourCC.
bool _hasAlpha; ///< Do video frames have alpha?
bool _swapPlanes; ///< Are the planes ordered (A)YVU instead of (A)YUV?
Common::Rational _frameRate;
Bundle _bundles[kSourceMAX]; ///< Bundles for decoding all data types.
Common::Huffman<Common::BitStream32LELSB> *_huffman[16]; ///< The 16 Huffman codebooks used in Bink decoding.
/** Huffman codebooks to use for decoding high nibbles in color data types. */
Huffman _colHighHuffman[16];
/** Value of the last decoded high nibble in color data types. */
int _colLastVal;
uint32 _yBlockWidth; ///< Width of the Y plane in blocks
uint32 _yBlockHeight; ///< Height of the Y plane in blocks
uint32 _uvBlockWidth; ///< Width of the U and V planes in blocks
uint32 _uvBlockHeight; ///< Height of the U and V planes in blocks
byte *_curPlanes[4]; ///< The 4 color planes, YUVA, current frame.
byte *_oldPlanes[4]; ///< The 4 color planes, YUVA, last frame.
/** Initialize the bundles. */
void initBundles();
/** Deinitialize the bundles. */
void deinitBundles();
/** Initialize the Huffman decoders. */
void initHuffman();
/** Decode a plane. */
void decodePlane(VideoFrame &video, int planeIdx, bool isChroma);
/** Read/Initialize a bundle for decoding a plane. */
void readBundle(VideoFrame &video, Source source);
/** Read the symbols for a Huffman code. */
void readHuffman(VideoFrame &video, Huffman &huffman);
/** Merge two Huffman symbol lists. */
void mergeHuffmanSymbols(VideoFrame &video, byte *dst, const byte *src, int size);
/** Read and translate a symbol out of a Huffman code. */
byte getHuffmanSymbol(VideoFrame &video, Huffman &huffman);
/** Get a direct value out of a bundle. */
int32 getBundleValue(Source source);
/** Read a count value out of a bundle. */
uint32 readBundleCount(VideoFrame &video, Bundle &bundle);
// Handle the block types
void blockSkip (DecodeContext &ctx);
void blockScaledSkip (DecodeContext &ctx);
void blockScaledRun (DecodeContext &ctx);
void blockScaledIntra (DecodeContext &ctx);
void blockScaledFill (DecodeContext &ctx);
void blockScaledPattern(DecodeContext &ctx);
void blockScaledRaw (DecodeContext &ctx);
void blockScaled (DecodeContext &ctx);
void blockMotion (DecodeContext &ctx);
void blockRun (DecodeContext &ctx);
void blockResidue (DecodeContext &ctx);
void blockIntra (DecodeContext &ctx);
void blockFill (DecodeContext &ctx);
void blockInter (DecodeContext &ctx);
void blockPattern (DecodeContext &ctx);
void blockRaw (DecodeContext &ctx);
// Read the bundles
void readRuns (VideoFrame &video, Bundle &bundle);
void readMotionValues(VideoFrame &video, Bundle &bundle);
void readBlockTypes (VideoFrame &video, Bundle &bundle);
void readPatterns (VideoFrame &video, Bundle &bundle);
void readColors (VideoFrame &video, Bundle &bundle);
template<int startBits, bool hasSign>
void readDCS (VideoFrame &video, Bundle &bundle);
void readDCTCoeffs (VideoFrame &video, int32 *block, bool isIntra);
void readResidue (VideoFrame &video, int16 *block, int masksCount);
// Bink video IDCT
void IDCT(int32 *block);
void IDCTPut(DecodeContext &ctx, int32 *block);
void IDCTAdd(DecodeContext &ctx, int32 *block);
};
class BinkAudioTrack : public AudioTrack {
public:
BinkAudioTrack(AudioInfo &audio, Audio::Mixer::SoundType soundType);
~BinkAudioTrack();
/** Decode an audio packet. */
void decodePacket();
bool seek(const Audio::Timestamp &time);
bool isSeekable() const { return true; }
void skipSamples(const Audio::Timestamp &length);
int getRate();
protected:
Audio::AudioStream *getAudioStream() const;
private:
AudioInfo *_audioInfo;
Audio::QueuingAudioStream *_audioStream;
float getFloat();
/** Decode an audio block. */
void audioBlock(int16 *out);
/** Decode a DCT'd audio block. */
void audioBlockDCT();
/** Decode a RDFT'd audio block. */
void audioBlockRDFT();
void readAudioCoeffs(float *coeffs);
static void floatToInt16Interleave(int16 *dst, const float **src, uint32 length, uint8 channels);
};
Common::SeekableReadStream *_bink;
Common::Array<AudioInfo> _audioTracks; ///< All audio tracks.
Common::Array<VideoFrame> _frames; ///< All video frames.
void initAudioTrack(AudioInfo &audio);
};
} // End of namespace Video
#endif // VIDEO_BINK_DECODER_H
#endif // USE_BINK

577
video/binkdata.h Normal file
View File

@@ -0,0 +1,577 @@
/* 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 VIDEO_BINKDATA_H
#define VIDEO_BINKDATA_H
#include "common/scummsys.h"
namespace Video {
static const uint16 binkCriticalFreqs[25] = {
100, 200, 300, 400, 510, 630, 770, 920,
1080, 1270, 1480, 1720, 2000, 2320, 2700, 3150,
3700, 4400, 5300, 6400, 7700, 9500, 12000, 15500,
24500,
};
/** Bink DCT and residue 8x8 block scan order */
static const uint8 binkScan[64] = {
0, 1, 8, 9, 2, 3, 10, 11,
4, 5, 12, 13, 6, 7, 14, 15,
20, 21, 28, 29, 22, 23, 30, 31,
16, 17, 24, 25, 32, 33, 40, 41,
34, 35, 42, 43, 48, 49, 56, 57,
50, 51, 58, 59, 18, 19, 26, 27,
36, 37, 44, 45, 38, 39, 46, 47,
52, 53, 60, 61, 54, 55, 62, 63
};
static const uint32 binkHuffmanCodes[16][16] = {
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F },
{ 0x00, 0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F, 0x13, 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F },
{ 0x00, 0x02, 0x01, 0x09, 0x05, 0x15, 0x0D, 0x1D, 0x03, 0x13, 0x0B, 0x1B, 0x07, 0x17, 0x0F, 0x1F },
{ 0x00, 0x02, 0x06, 0x01, 0x09, 0x05, 0x0D, 0x1D, 0x03, 0x13, 0x0B, 0x1B, 0x07, 0x17, 0x0F, 0x1F },
{ 0x00, 0x04, 0x02, 0x06, 0x01, 0x09, 0x05, 0x0D, 0x03, 0x13, 0x0B, 0x1B, 0x07, 0x17, 0x0F, 0x1F },
{ 0x00, 0x04, 0x02, 0x0A, 0x06, 0x0E, 0x01, 0x09, 0x05, 0x0D, 0x03, 0x0B, 0x07, 0x17, 0x0F, 0x1F },
{ 0x00, 0x02, 0x0A, 0x06, 0x0E, 0x01, 0x09, 0x05, 0x0D, 0x03, 0x0B, 0x1B, 0x07, 0x17, 0x0F, 0x1F },
{ 0x00, 0x01, 0x05, 0x03, 0x13, 0x0B, 0x1B, 0x3B, 0x07, 0x27, 0x17, 0x37, 0x0F, 0x2F, 0x1F, 0x3F },
{ 0x00, 0x01, 0x03, 0x13, 0x0B, 0x2B, 0x1B, 0x3B, 0x07, 0x27, 0x17, 0x37, 0x0F, 0x2F, 0x1F, 0x3F },
{ 0x00, 0x01, 0x05, 0x0D, 0x03, 0x13, 0x0B, 0x1B, 0x07, 0x27, 0x17, 0x37, 0x0F, 0x2F, 0x1F, 0x3F },
{ 0x00, 0x02, 0x01, 0x05, 0x0D, 0x03, 0x13, 0x0B, 0x1B, 0x07, 0x17, 0x37, 0x0F, 0x2F, 0x1F, 0x3F },
{ 0x00, 0x01, 0x09, 0x05, 0x0D, 0x03, 0x13, 0x0B, 0x1B, 0x07, 0x17, 0x37, 0x0F, 0x2F, 0x1F, 0x3F },
{ 0x00, 0x02, 0x01, 0x03, 0x13, 0x0B, 0x1B, 0x3B, 0x07, 0x27, 0x17, 0x37, 0x0F, 0x2F, 0x1F, 0x3F },
{ 0x00, 0x01, 0x05, 0x03, 0x07, 0x27, 0x17, 0x37, 0x0F, 0x4F, 0x2F, 0x6F, 0x1F, 0x5F, 0x3F, 0x7F },
{ 0x00, 0x01, 0x05, 0x03, 0x07, 0x17, 0x37, 0x77, 0x0F, 0x4F, 0x2F, 0x6F, 0x1F, 0x5F, 0x3F, 0x7F },
{ 0x00, 0x02, 0x01, 0x05, 0x03, 0x07, 0x27, 0x17, 0x37, 0x0F, 0x2F, 0x6F, 0x1F, 0x5F, 0x3F, 0x7F }
};
static const uint8 binkHuffmanLengths[16][16] = {
{ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 },
{ 1, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 },
{ 2, 2, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 },
{ 2, 3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 },
{ 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5 },
{ 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5 },
{ 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5 },
{ 1, 3, 3, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 },
{ 1, 2, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 },
{ 1, 3, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6 },
{ 2, 2, 3, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6 },
{ 1, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6 },
{ 2, 2, 2, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 },
{ 1, 3, 3, 3, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7 },
{ 1, 3, 3, 3, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 },
{ 2, 2, 3, 3, 3, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7 }
};
static const uint8 binkPatterns[16][64] = {
{
0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38,
0x39, 0x31, 0x29, 0x21, 0x19, 0x11, 0x09, 0x01,
0x02, 0x0A, 0x12, 0x1A, 0x22, 0x2A, 0x32, 0x3A,
0x3B, 0x33, 0x2B, 0x23, 0x1B, 0x13, 0x0B, 0x03,
0x04, 0x0C, 0x14, 0x1C, 0x24, 0x2C, 0x34, 0x3C,
0x3D, 0x35, 0x2D, 0x25, 0x1D, 0x15, 0x0D, 0x05,
0x06, 0x0E, 0x16, 0x1E, 0x26, 0x2E, 0x36, 0x3E,
0x3F, 0x37, 0x2F, 0x27, 0x1F, 0x17, 0x0F, 0x07
},
{
0x3B, 0x3A, 0x39, 0x38, 0x30, 0x31, 0x32, 0x33,
0x2B, 0x2A, 0x29, 0x28, 0x20, 0x21, 0x22, 0x23,
0x1B, 0x1A, 0x19, 0x18, 0x10, 0x11, 0x12, 0x13,
0x0B, 0x0A, 0x09, 0x08, 0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07, 0x0F, 0x0E, 0x0D, 0x0C,
0x14, 0x15, 0x16, 0x17, 0x1F, 0x1E, 0x1D, 0x1C,
0x24, 0x25, 0x26, 0x27, 0x2F, 0x2E, 0x2D, 0x2C,
0x34, 0x35, 0x36, 0x37, 0x3F, 0x3E, 0x3D, 0x3C
},
{
0x19, 0x11, 0x12, 0x1A, 0x1B, 0x13, 0x0B, 0x03,
0x02, 0x0A, 0x09, 0x01, 0x00, 0x08, 0x10, 0x18,
0x20, 0x28, 0x30, 0x38, 0x39, 0x31, 0x29, 0x2A,
0x32, 0x3A, 0x3B, 0x33, 0x2B, 0x23, 0x22, 0x21,
0x1D, 0x15, 0x16, 0x1E, 0x1F, 0x17, 0x0F, 0x07,
0x06, 0x0E, 0x0D, 0x05, 0x04, 0x0C, 0x14, 0x1C,
0x24, 0x2C, 0x34, 0x3C, 0x3D, 0x35, 0x2D, 0x2E,
0x36, 0x3E, 0x3F, 0x37, 0x2F, 0x27, 0x26, 0x25
},
{
0x03, 0x0B, 0x02, 0x0A, 0x01, 0x09, 0x00, 0x08,
0x10, 0x18, 0x11, 0x19, 0x12, 0x1A, 0x13, 0x1B,
0x23, 0x2B, 0x22, 0x2A, 0x21, 0x29, 0x20, 0x28,
0x30, 0x38, 0x31, 0x39, 0x32, 0x3A, 0x33, 0x3B,
0x3C, 0x34, 0x3D, 0x35, 0x3E, 0x36, 0x3F, 0x37,
0x2F, 0x27, 0x2E, 0x26, 0x2D, 0x25, 0x2C, 0x24,
0x1C, 0x14, 0x1D, 0x15, 0x1E, 0x16, 0x1F, 0x17,
0x0F, 0x07, 0x0E, 0x06, 0x0D, 0x05, 0x0C, 0x04
},
{
0x18, 0x19, 0x10, 0x11, 0x08, 0x09, 0x00, 0x01,
0x02, 0x03, 0x0A, 0x0B, 0x12, 0x13, 0x1A, 0x1B,
0x1C, 0x1D, 0x14, 0x15, 0x0C, 0x0D, 0x04, 0x05,
0x06, 0x07, 0x0E, 0x0F, 0x16, 0x17, 0x1E, 0x1F,
0x27, 0x26, 0x2F, 0x2E, 0x37, 0x36, 0x3F, 0x3E,
0x3D, 0x3C, 0x35, 0x34, 0x2D, 0x2C, 0x25, 0x24,
0x23, 0x22, 0x2B, 0x2A, 0x33, 0x32, 0x3B, 0x3A,
0x39, 0x38, 0x31, 0x30, 0x29, 0x28, 0x21, 0x20
},
{
0x00, 0x01, 0x02, 0x03, 0x08, 0x09, 0x0A, 0x0B,
0x10, 0x11, 0x12, 0x13, 0x18, 0x19, 0x1A, 0x1B,
0x20, 0x21, 0x22, 0x23, 0x28, 0x29, 0x2A, 0x2B,
0x30, 0x31, 0x32, 0x33, 0x38, 0x39, 0x3A, 0x3B,
0x04, 0x05, 0x06, 0x07, 0x0C, 0x0D, 0x0E, 0x0F,
0x14, 0x15, 0x16, 0x17, 0x1C, 0x1D, 0x1E, 0x1F,
0x24, 0x25, 0x26, 0x27, 0x2C, 0x2D, 0x2E, 0x2F,
0x34, 0x35, 0x36, 0x37, 0x3C, 0x3D, 0x3E, 0x3F
},
{
0x06, 0x07, 0x0F, 0x0E, 0x0D, 0x05, 0x0C, 0x04,
0x03, 0x0B, 0x02, 0x0A, 0x09, 0x01, 0x00, 0x08,
0x10, 0x18, 0x11, 0x19, 0x12, 0x1A, 0x13, 0x1B,
0x14, 0x1C, 0x15, 0x1D, 0x16, 0x1E, 0x17, 0x1F,
0x27, 0x2F, 0x26, 0x2E, 0x25, 0x2D, 0x24, 0x2C,
0x23, 0x2B, 0x22, 0x2A, 0x21, 0x29, 0x20, 0x28,
0x31, 0x30, 0x38, 0x39, 0x3A, 0x32, 0x3B, 0x33,
0x3C, 0x34, 0x3D, 0x35, 0x36, 0x37, 0x3F, 0x3E
},
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, 0x28,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38
},
{
0x00, 0x08, 0x09, 0x01, 0x02, 0x03, 0x0B, 0x0A,
0x12, 0x13, 0x1B, 0x1A, 0x19, 0x11, 0x10, 0x18,
0x20, 0x28, 0x29, 0x21, 0x22, 0x23, 0x2B, 0x2A,
0x32, 0x31, 0x30, 0x38, 0x39, 0x3A, 0x3B, 0x33,
0x34, 0x3C, 0x3D, 0x3E, 0x3F, 0x37, 0x36, 0x35,
0x2D, 0x2C, 0x24, 0x25, 0x26, 0x2E, 0x2F, 0x27,
0x1F, 0x17, 0x16, 0x1E, 0x1D, 0x1C, 0x14, 0x15,
0x0D, 0x0C, 0x04, 0x05, 0x06, 0x0E, 0x0F, 0x07
},
{
0x18, 0x19, 0x10, 0x11, 0x08, 0x09, 0x00, 0x01,
0x02, 0x03, 0x0A, 0x0B, 0x12, 0x13, 0x1A, 0x1B,
0x1C, 0x1D, 0x14, 0x15, 0x0C, 0x0D, 0x04, 0x05,
0x06, 0x07, 0x0E, 0x0F, 0x16, 0x17, 0x1E, 0x1F,
0x26, 0x27, 0x2E, 0x2F, 0x36, 0x37, 0x3E, 0x3F,
0x3C, 0x3D, 0x34, 0x35, 0x2C, 0x2D, 0x24, 0x25,
0x22, 0x23, 0x2A, 0x2B, 0x32, 0x33, 0x3A, 0x3B,
0x38, 0x39, 0x30, 0x31, 0x28, 0x29, 0x20, 0x21
},
{
0x00, 0x08, 0x01, 0x09, 0x02, 0x0A, 0x03, 0x0B,
0x13, 0x1B, 0x12, 0x1A, 0x11, 0x19, 0x10, 0x18,
0x20, 0x28, 0x21, 0x29, 0x22, 0x2A, 0x23, 0x2B,
0x33, 0x3B, 0x32, 0x3A, 0x31, 0x39, 0x30, 0x38,
0x3C, 0x34, 0x3D, 0x35, 0x3E, 0x36, 0x3F, 0x37,
0x2F, 0x27, 0x2E, 0x26, 0x2D, 0x25, 0x2C, 0x24,
0x1F, 0x17, 0x1E, 0x16, 0x1D, 0x15, 0x1C, 0x14,
0x0C, 0x04, 0x0D, 0x05, 0x0E, 0x06, 0x0F, 0x07
},
{
0x00, 0x08, 0x10, 0x18, 0x19, 0x1A, 0x1B, 0x13,
0x0B, 0x03, 0x02, 0x01, 0x09, 0x11, 0x12, 0x0A,
0x04, 0x0C, 0x14, 0x1C, 0x1D, 0x1E, 0x1F, 0x17,
0x0F, 0x07, 0x06, 0x05, 0x0D, 0x15, 0x16, 0x0E,
0x24, 0x2C, 0x34, 0x3C, 0x3D, 0x3E, 0x3F, 0x37,
0x2F, 0x27, 0x26, 0x25, 0x2D, 0x35, 0x36, 0x2E,
0x20, 0x28, 0x30, 0x38, 0x39, 0x3A, 0x3B, 0x33,
0x2B, 0x23, 0x22, 0x21, 0x29, 0x31, 0x32, 0x2A
},
{
0x00, 0x08, 0x09, 0x01, 0x02, 0x03, 0x0B, 0x0A,
0x13, 0x1B, 0x1A, 0x12, 0x11, 0x10, 0x18, 0x19,
0x21, 0x20, 0x28, 0x29, 0x2A, 0x22, 0x23, 0x2B,
0x33, 0x3B, 0x3A, 0x32, 0x31, 0x39, 0x38, 0x30,
0x34, 0x3C, 0x3D, 0x35, 0x36, 0x3E, 0x3F, 0x37,
0x2F, 0x27, 0x26, 0x2E, 0x2D, 0x2C, 0x24, 0x25,
0x1D, 0x1C, 0x14, 0x15, 0x16, 0x1E, 0x1F, 0x17,
0x0E, 0x0F, 0x07, 0x06, 0x05, 0x0D, 0x0C, 0x04
},
{
0x18, 0x10, 0x08, 0x00, 0x01, 0x02, 0x03, 0x0B,
0x13, 0x1B, 0x1A, 0x19, 0x11, 0x0A, 0x09, 0x12,
0x1C, 0x14, 0x0C, 0x04, 0x05, 0x06, 0x07, 0x0F,
0x17, 0x1F, 0x1E, 0x1D, 0x15, 0x0E, 0x0D, 0x16,
0x3C, 0x34, 0x2C, 0x24, 0x25, 0x26, 0x27, 0x2F,
0x37, 0x3F, 0x3E, 0x3D, 0x35, 0x2E, 0x2D, 0x36,
0x38, 0x30, 0x28, 0x20, 0x21, 0x22, 0x23, 0x2B,
0x33, 0x3B, 0x3A, 0x39, 0x31, 0x2A, 0x29, 0x32
},
{
0x00, 0x08, 0x09, 0x01, 0x02, 0x0A, 0x12, 0x11,
0x10, 0x18, 0x19, 0x1A, 0x1B, 0x13, 0x0B, 0x03,
0x07, 0x06, 0x0E, 0x0F, 0x17, 0x16, 0x15, 0x0D,
0x05, 0x04, 0x0C, 0x14, 0x1C, 0x1D, 0x1E, 0x1F,
0x3F, 0x3E, 0x36, 0x37, 0x2F, 0x2E, 0x2D, 0x35,
0x3D, 0x3C, 0x34, 0x2C, 0x24, 0x25, 0x26, 0x27,
0x38, 0x30, 0x31, 0x39, 0x3A, 0x32, 0x2A, 0x29,
0x28, 0x20, 0x21, 0x22, 0x23, 0x2B, 0x33, 0x3B
},
{
0x00, 0x01, 0x08, 0x09, 0x10, 0x11, 0x18, 0x19,
0x20, 0x21, 0x28, 0x29, 0x30, 0x31, 0x38, 0x39,
0x3A, 0x3B, 0x32, 0x33, 0x2A, 0x2B, 0x22, 0x23,
0x1A, 0x1B, 0x12, 0x13, 0x0A, 0x0B, 0x02, 0x03,
0x04, 0x05, 0x0C, 0x0D, 0x14, 0x15, 0x1C, 0x1D,
0x24, 0x25, 0x2C, 0x2D, 0x34, 0x35, 0x3C, 0x3D,
0x3E, 0x3F, 0x36, 0x37, 0x2E, 0x2F, 0x26, 0x27,
0x1E, 0x1F, 0x16, 0x17, 0x0E, 0x0F, 0x06, 0x07
}
};
static const int32 binkIntraQuant[16][64] = {
{
0x010000, 0x016315, 0x01E83D, 0x02A535, 0x014E7B, 0x016577, 0x02F1E6, 0x02724C,
0x010000, 0x00EEDA, 0x024102, 0x017F9B, 0x00BE80, 0x00611E, 0x01083C, 0x00A552,
0x021F88, 0x01DC53, 0x027FAD, 0x01F697, 0x014819, 0x00A743, 0x015A31, 0x009688,
0x02346F, 0x030EE5, 0x01FBFA, 0x02C096, 0x01D000, 0x028396, 0x019247, 0x01F9AA,
0x02346F, 0x01FBFA, 0x01DC53, 0x0231B8, 0x012F12, 0x01E06C, 0x00CB10, 0x0119A8,
0x01C48C, 0x019748, 0x014E86, 0x0122AF, 0x02C628, 0x027F20, 0x0297B5, 0x023F32,
0x025000, 0x01AB6B, 0x01D122, 0x0159B3, 0x012669, 0x008D43, 0x00EE1F, 0x0075ED,
0x01490C, 0x010288, 0x00F735, 0x00EF51, 0x00E0F1, 0x0072AD, 0x00A4D8, 0x006517,
},
{
0x015555, 0x01D971, 0x028AFC, 0x0386F1, 0x01BDF9, 0x01DC9F, 0x03ED33, 0x034311,
0x015555, 0x013E78, 0x030158, 0x01FF7A, 0x00FE00, 0x00817D, 0x01604F, 0x00DC6D,
0x02D4B5, 0x027B19, 0x0354E7, 0x029E1F, 0x01B577, 0x00DF04, 0x01CD96, 0x00C8B6,
0x02F095, 0x0413DC, 0x02A54E, 0x03AB73, 0x026AAB, 0x035A1E, 0x02185E, 0x02A238,
0x02F095, 0x02A54E, 0x027B19, 0x02ECF5, 0x019418, 0x028090, 0x010EC0, 0x01778A,
0x025B66, 0x021F0B, 0x01BE09, 0x018394, 0x03B2E0, 0x03542A, 0x0374F1, 0x02FEEE,
0x031555, 0x0239E4, 0x026C2D, 0x01CCEE, 0x01888C, 0x00BC59, 0x013D7E, 0x009D3C,
0x01B6BB, 0x0158B5, 0x01499C, 0x013F17, 0x012BEC, 0x0098E6, 0x00DBCB, 0x0086C9,
},
{
0x01AAAB, 0x024FCE, 0x032DBB, 0x0468AD, 0x022D78, 0x0253C7, 0x04E87F, 0x0413D5,
0x01AAAB, 0x018E16, 0x03C1AE, 0x027F58, 0x013D80, 0x00A1DC, 0x01B863, 0x011388,
0x0389E2, 0x0319DF, 0x042A21, 0x0345A7, 0x0222D4, 0x0116C5, 0x0240FC, 0x00FAE3,
0x03ACBA, 0x0518D3, 0x034EA1, 0x04964F, 0x030555, 0x0430A5, 0x029E76, 0x034AC5,
0x03ACBA, 0x034EA1, 0x0319DF, 0x03A833, 0x01F91E, 0x0320B4, 0x015270, 0x01D56D,
0x02F23F, 0x02A6CE, 0x022D8B, 0x01E479, 0x049F98, 0x042935, 0x04522D, 0x03BEA9,
0x03DAAB, 0x02C85D, 0x030738, 0x02402A, 0x01EAAF, 0x00EB6F, 0x018CDE, 0x00C48A,
0x022469, 0x01AEE2, 0x019C02, 0x018EDD, 0x0176E7, 0x00BF20, 0x0112BE, 0x00A87B,
},
{
0x020000, 0x02C62A, 0x03D07A, 0x054A69, 0x029CF6, 0x02CAEF, 0x05E3CC, 0x04E499,
0x020000, 0x01DDB4, 0x048204, 0x02FF36, 0x017D01, 0x00C23C, 0x021077, 0x014AA3,
0x043F0F, 0x03B8A6, 0x04FF5A, 0x03ED2E, 0x029032, 0x014E86, 0x02B461, 0x012D11,
0x0468DF, 0x061DCA, 0x03F7F5, 0x05812C, 0x03A000, 0x05072C, 0x03248D, 0x03F353,
0x0468DF, 0x03F7F5, 0x03B8A6, 0x046370, 0x025E24, 0x03C0D8, 0x019620, 0x02334F,
0x038919, 0x032E91, 0x029D0D, 0x02455E, 0x058C50, 0x04FE3F, 0x052F69, 0x047E65,
0x04A000, 0x0356D6, 0x03A243, 0x02B365, 0x024CD2, 0x011A85, 0x01DC3E, 0x00EBD9,
0x029218, 0x020510, 0x01EE69, 0x01DEA2, 0x01C1E2, 0x00E559, 0x0149B0, 0x00CA2D,
},
{
0x02AAAB, 0x03B2E3, 0x0515F8, 0x070DE2, 0x037BF2, 0x03B93E, 0x07DA65, 0x068621,
0x02AAAB, 0x027CF0, 0x0602B1, 0x03FEF3, 0x01FC01, 0x0102FA, 0x02C09F, 0x01B8DA,
0x05A96A, 0x04F632, 0x06A9CE, 0x053C3E, 0x036AED, 0x01BE09, 0x039B2D, 0x01916B,
0x05E129, 0x0827B8, 0x054A9C, 0x0756E5, 0x04D555, 0x06B43B, 0x0430BC, 0x05446F,
0x05E129, 0x054A9C, 0x04F632, 0x05D9EB, 0x032830, 0x050121, 0x021D80, 0x02EF14,
0x04B6CC, 0x043E16, 0x037C11, 0x030728, 0x0765C0, 0x06A855, 0x06E9E2, 0x05FDDB,
0x062AAB, 0x0473C8, 0x04D85A, 0x0399DC, 0x031118, 0x0178B2, 0x027AFD, 0x013A77,
0x036D76, 0x02B16A, 0x029337, 0x027E2E, 0x0257D8, 0x0131CC, 0x01B796, 0x010D91,
},
{
0x038000, 0x04DACA, 0x06ACD5, 0x094238, 0x0492AE, 0x04E322, 0x0A4EA5, 0x08900C,
0x038000, 0x0343FB, 0x07E388, 0x053E9F, 0x029AC1, 0x0153E8, 0x039CD0, 0x02429E,
0x076E5B, 0x068322, 0x08BEDE, 0x06DF11, 0x047C57, 0x02496B, 0x04BBAB, 0x020EDD,
0x07B786, 0x0AB421, 0x06F1ED, 0x09A20D, 0x065800, 0x08CC8E, 0x057FF7, 0x06E9D2,
0x07B786, 0x06F1ED, 0x068322, 0x07AE04, 0x0424BF, 0x06917B, 0x02C6B8, 0x03D9CB,
0x062FEB, 0x05917D, 0x0492D7, 0x03F964, 0x09B58C, 0x08BCEF, 0x0912F8, 0x07DD30,
0x081800, 0x05D7F7, 0x065BF6, 0x04B9F1, 0x040670, 0x01EE69, 0x03416C, 0x019CBC,
0x047FAA, 0x0388DC, 0x036138, 0x03459C, 0x03134C, 0x01915C, 0x0240F5, 0x0161CF,
},
{
0x040000, 0x058C54, 0x07A0F4, 0x0A94D3, 0x0539EC, 0x0595DD, 0x0BC798, 0x09C932,
0x040000, 0x03BB68, 0x090409, 0x05FE6D, 0x02FA01, 0x018477, 0x0420EE, 0x029547,
0x087E1F, 0x07714C, 0x09FEB5, 0x07DA5D, 0x052064, 0x029D0D, 0x0568C3, 0x025A21,
0x08D1BE, 0x0C3B94, 0x07EFEA, 0x0B0258, 0x074000, 0x0A0E59, 0x06491A, 0x07E6A7,
0x08D1BE, 0x07EFEA, 0x07714C, 0x08C6E0, 0x04BC48, 0x0781B1, 0x032C3F, 0x04669F,
0x071232, 0x065D22, 0x053A1A, 0x048ABC, 0x0B18A0, 0x09FC7F, 0x0A5ED3, 0x08FCC9,
0x094000, 0x06ADAC, 0x074487, 0x0566CA, 0x0499A5, 0x02350B, 0x03B87B, 0x01D7B3,
0x052430, 0x040A20, 0x03DCD3, 0x03BD45, 0x0383C5, 0x01CAB3, 0x029361, 0x01945A,
},
{
0x050000, 0x06EF69, 0x098931, 0x0D3A07, 0x068867, 0x06FB55, 0x0EB97E, 0x0C3B7E,
0x050000, 0x04AA42, 0x0B450B, 0x077E08, 0x03B881, 0x01E595, 0x05292A, 0x033A99,
0x0A9DA7, 0x094D9F, 0x0C7E62, 0x09D0F4, 0x06687D, 0x034450, 0x06C2F4, 0x02F0AA,
0x0B062D, 0x0F4A78, 0x09EBE4, 0x0DC2EE, 0x091000, 0x0C91EF, 0x07DB61, 0x09E050,
0x0B062D, 0x09EBE4, 0x094D9F, 0x0AF898, 0x05EB59, 0x09621D, 0x03F74F, 0x058046,
0x08D6BE, 0x07F46A, 0x0688A0, 0x05AD6B, 0x0DDEC8, 0x0C7B9F, 0x0CF687, 0x0B3BFB,
0x0B9000, 0x085917, 0x0915A8, 0x06C07D, 0x05C00E, 0x02C24D, 0x04A69A, 0x024D9F,
0x066D3C, 0x050CA7, 0x04D407, 0x04AC96, 0x0464B6, 0x023D5F, 0x033839, 0x01F971,
},
{
0x060000, 0x08527E, 0x0B716E, 0x0FDF3C, 0x07D6E1, 0x0860CC, 0x11AB63, 0x0EADCB,
0x060000, 0x05991C, 0x0D860D, 0x08FDA3, 0x047702, 0x0246B3, 0x063165, 0x03DFEA,
0x0CBD2E, 0x0B29F1, 0x0EFE0F, 0x0BC78B, 0x07B096, 0x03EB93, 0x081D24, 0x038732,
0x0D3A9C, 0x12595D, 0x0BE7DF, 0x108384, 0x0AE000, 0x0F1585, 0x096DA8, 0x0BD9FA,
0x0D3A9C, 0x0BE7DF, 0x0B29F1, 0x0D2A50, 0x071A6B, 0x0B4289, 0x04C25F, 0x0699EE,
0x0A9B4A, 0x098BB2, 0x07D727, 0x06D01A, 0x10A4F0, 0x0EFABE, 0x0F8E3C, 0x0D7B2E,
0x0DE000, 0x0A0482, 0x0AE6CA, 0x081A2F, 0x06E677, 0x034F90, 0x0594B9, 0x02C38C,
0x07B649, 0x060F2F, 0x05CB3C, 0x059BE7, 0x0545A7, 0x02B00C, 0x03DD11, 0x025E87,
},
{
0x080000, 0x0B18A8, 0x0F41E8, 0x1529A5, 0x0A73D7, 0x0B2BBB, 0x178F2F, 0x139264,
0x080000, 0x0776CF, 0x120812, 0x0BFCD9, 0x05F402, 0x0308EF, 0x0841DC, 0x052A8E,
0x10FC3E, 0x0EE297, 0x13FD69, 0x0FB4B9, 0x0A40C8, 0x053A1A, 0x0AD186, 0x04B442,
0x11A37B, 0x187727, 0x0FDFD4, 0x1604B0, 0x0E8000, 0x141CB1, 0x0C9235, 0x0FCD4D,
0x11A37B, 0x0FDFD4, 0x0EE297, 0x118DC0, 0x09788F, 0x0F0362, 0x06587F, 0x08CD3D,
0x0E2463, 0x0CBA43, 0x0A7434, 0x091577, 0x163140, 0x13F8FE, 0x14BDA5, 0x11F992,
0x128000, 0x0D5B58, 0x0E890D, 0x0ACD94, 0x093349, 0x046A15, 0x0770F7, 0x03AF65,
0x0A4861, 0x08143F, 0x07B9A6, 0x077A89, 0x070789, 0x039565, 0x0526C2, 0x0328B4,
},
{
0x0C0000, 0x10A4FD, 0x16E2DB, 0x1FBE78, 0x0FADC3, 0x10C198, 0x2356C7, 0x1D5B96,
0x0C0000, 0x0B3237, 0x1B0C1A, 0x11FB46, 0x08EE03, 0x048D66, 0x0C62CA, 0x07BFD5,
0x197A5D, 0x1653E3, 0x1DFC1E, 0x178F16, 0x0F612C, 0x07D727, 0x103A49, 0x070E64,
0x1A7539, 0x24B2BB, 0x17CFBD, 0x210709, 0x15C000, 0x1E2B0A, 0x12DB4F, 0x17B3F4,
0x1A7539, 0x17CFBD, 0x1653E3, 0x1A54A0, 0x0E34D7, 0x168513, 0x0984BE, 0x0D33DC,
0x153695, 0x131765, 0x0FAE4E, 0x0DA033, 0x2149E1, 0x1DF57D, 0x1F1C78, 0x1AF65B,
0x1BC000, 0x140904, 0x15CD94, 0x10345E, 0x0DCCEE, 0x069F20, 0x0B2972, 0x058718,
0x0F6C91, 0x0C1E5E, 0x0B9678, 0x0B37CE, 0x0A8B4E, 0x056018, 0x07BA22, 0x04BD0E,
},
{
0x110000, 0x179466, 0x206C0C, 0x2CF87F, 0x16362A, 0x17BCED, 0x321044, 0x299714,
0x110000, 0x0FDC79, 0x265125, 0x19794E, 0x0CA685, 0x0672FB, 0x118BF4, 0x0AFA6D,
0x241804, 0x1FA181, 0x2A7A80, 0x21600A, 0x15C9A9, 0x0B1B77, 0x16FD3C, 0x09FF0D,
0x257B66, 0x33FD33, 0x21BBA2, 0x2EC9F7, 0x1ED000, 0x2ABCF9, 0x1AB6B0, 0x219444,
0x257B66, 0x21BBA2, 0x1FA181, 0x254D38, 0x142030, 0x1FE730, 0x0D7C0E, 0x12B423,
0x1E0D52, 0x1B0BCF, 0x1636EE, 0x134D9E, 0x2F28A9, 0x2A711B, 0x2C12FF, 0x263256,
0x275000, 0x1C621B, 0x1EE33C, 0x16F4DB, 0x138CFB, 0x09616E, 0x0FD00C, 0x07D4B7,
0x15D9CE, 0x112B06, 0x106A80, 0x0FE464, 0x0EF004, 0x079D77, 0x0AF25B, 0x06B67F,
},
{
0x160000, 0x1E83CF, 0x29F53D, 0x3A3286, 0x1CBE90, 0x1EB842, 0x40C9C2, 0x35D293,
0x160000, 0x1486BA, 0x319630, 0x20F756, 0x105F06, 0x085891, 0x16B51E, 0x0E3506,
0x2EB5AA, 0x28EF20, 0x36F8E1, 0x2B30FE, 0x1C3225, 0x0E5FC7, 0x1DC030, 0x0CEFB7,
0x308193, 0x4347AC, 0x2BA786, 0x3C8CE5, 0x27E000, 0x374EE7, 0x229212, 0x2B7494,
0x308193, 0x2BA786, 0x28EF20, 0x3045D0, 0x1A0B89, 0x29494D, 0x11735D, 0x183469,
0x26E410, 0x230039, 0x1CBF8F, 0x18FB09, 0x3D0771, 0x36ECBA, 0x390986, 0x316E52,
0x32E000, 0x24BB33, 0x27F8E4, 0x1DB557, 0x194D09, 0x0C23BB, 0x1476A6, 0x0A2256,
0x1C470A, 0x1637AD, 0x153E87, 0x1490FA, 0x1354B9, 0x09DAD6, 0x0E2A94, 0x08AFF0,
},
{
0x1C0000, 0x26D64D, 0x3566AA, 0x4A11C2, 0x249572, 0x27190E, 0x527525, 0x44805E,
0x1C0000, 0x1A1FD6, 0x3F1C3E, 0x29F4F9, 0x14D607, 0x0A9F44, 0x1CE683, 0x1214F0,
0x3B72D9, 0x341911, 0x45F6F0, 0x36F889, 0x23E2BB, 0x124B5B, 0x25DD54, 0x1076E9,
0x3DBC30, 0x55A109, 0x378F64, 0x4D1069, 0x32C000, 0x46646C, 0x2BFFB9, 0x374E8E,
0x3DBC30, 0x378F64, 0x341911, 0x3D7020, 0x2125F5, 0x348BD6, 0x1635BC, 0x1ECE57,
0x317F5B, 0x2C8BEB, 0x2496B6, 0x1FCB22, 0x4DAC61, 0x45E778, 0x4897C2, 0x3EE97F,
0x40C000, 0x2EBFB5, 0x32DFAE, 0x25CF86, 0x203380, 0x0F734B, 0x1A0B5F, 0x0CE5E2,
0x23FD53, 0x1C46DC, 0x1B09C4, 0x1A2CE1, 0x189A60, 0x0C8AE2, 0x1207A5, 0x0B0E77,
},
{
0x220000, 0x2F28CC, 0x40D818, 0x59F0FE, 0x2C6C53, 0x2F79DA, 0x642089, 0x532E29,
0x220000, 0x1FB8F1, 0x4CA24B, 0x32F29C, 0x194D09, 0x0CE5F7, 0x2317E8, 0x15F4DB,
0x483007, 0x3F4303, 0x54F4FF, 0x42C014, 0x2B9351, 0x1636EE, 0x2DFA79, 0x13FE1A,
0x4AF6CC, 0x67FA67, 0x437743, 0x5D93EE, 0x3DA000, 0x5579F1, 0x356D61, 0x432888,
0x4AF6CC, 0x437743, 0x3F4303, 0x4A9A70, 0x284060, 0x3FCE60, 0x1AF81B, 0x256845,
0x3C1AA5, 0x36179D, 0x2C6DDD, 0x269B3C, 0x5E5152, 0x54E237, 0x5825FE, 0x4C64AD,
0x4EA000, 0x38C437, 0x3DC678, 0x2DE9B5, 0x2719F7, 0x12C2DB, 0x1FA018, 0x0FA96E,
0x2BB39B, 0x22560C, 0x20D500, 0x1FC8C8, 0x1DE007, 0x0F3AEE, 0x15E4B7, 0x0D6CFE,
},
{
0x2C0000, 0x3D079E, 0x53EA79, 0x74650C, 0x397D20, 0x3D7083, 0x819383, 0x6BA525,
0x2C0000, 0x290D75, 0x632C61, 0x41EEAC, 0x20BE0C, 0x10B121, 0x2D6A3B, 0x1C6A0C,
0x5D6B54, 0x51DE40, 0x6DF1C2, 0x5661FB, 0x38644B, 0x1CBF8F, 0x3B8060, 0x19DF6D,
0x610326, 0x868F57, 0x574F0B, 0x7919CA, 0x4FC000, 0x6E9DCE, 0x452423, 0x56E928,
0x610326, 0x574F0B, 0x51DE40, 0x608BA0, 0x341713, 0x52929A, 0x22E6BA, 0x3068D2,
0x4DC821, 0x460071, 0x397F1E, 0x31F611, 0x7A0EE2, 0x6DD974, 0x72130C, 0x62DCA3,
0x65C000, 0x497665, 0x4FF1C9, 0x3B6AAE, 0x329A12, 0x184776, 0x28ED4D, 0x1444AC,
0x388E14, 0x2C6F5A, 0x2A7D0F, 0x2921F4, 0x26A973, 0x13B5AD, 0x1C5528, 0x115FDF,
},
};
static const int32 binkInterQuant[16][64] = {
{
0x010000, 0x017946, 0x01A5A9, 0x0248DC, 0x016363, 0x0152A7, 0x0243EC, 0x0209EA,
0x012000, 0x00E248, 0x01BBDA, 0x015CBC, 0x00A486, 0x0053E0, 0x00F036, 0x008095,
0x01B701, 0x016959, 0x01B0B9, 0x0153FD, 0x00F8E7, 0x007EE4, 0x00EA30, 0x007763,
0x01B701, 0x0260EB, 0x019DE9, 0x023E1B, 0x017000, 0x01FE6E, 0x012DB5, 0x01A27B,
0x01E0D1, 0x01B0B9, 0x018A33, 0x01718D, 0x00D87A, 0x014449, 0x007B9A, 0x00AB71,
0x013178, 0x0112EA, 0x00AD08, 0x009BB9, 0x023D97, 0x020437, 0x021CCC, 0x01E6B4,
0x018000, 0x012DB5, 0x0146D9, 0x0100CE, 0x00CFD2, 0x006E5C, 0x00B0E4, 0x005A2D,
0x00E9CC, 0x00B7B1, 0x00846F, 0x006B85, 0x008337, 0x0042E5, 0x004A10, 0x002831,
},
{
0x015555, 0x01F708, 0x023237, 0x030BD0, 0x01D9D9, 0x01C389, 0x03053B, 0x02B7E3,
0x018000, 0x012DB5, 0x024FCE, 0x01D0FA, 0x00DB5D, 0x006FD5, 0x014048, 0x00AB71,
0x024957, 0x01E1CC, 0x0240F7, 0x01C551, 0x014BDE, 0x00A92F, 0x013840, 0x009F2F,
0x024957, 0x032BE4, 0x0227E1, 0x02FD7A, 0x01EAAB, 0x02A893, 0x019247, 0x022DF9,
0x028116, 0x0240F7, 0x020D99, 0x01ECBC, 0x0120A3, 0x01B061, 0x00A4CE, 0x00E497,
0x01974B, 0x016E8E, 0x00E6B5, 0x00CFA2, 0x02FCC9, 0x02B04A, 0x02D110, 0x0288F1,
0x020000, 0x019247, 0x01B3CC, 0x015668, 0x011518, 0x009325, 0x00EBDA, 0x00783D,
0x0137BB, 0x00F4ED, 0x00B093, 0x008F5C, 0x00AEF4, 0x005931, 0x0062BF, 0x003597,
},
{
0x01AAAB, 0x0274CB, 0x02BEC4, 0x03CEC4, 0x02504F, 0x02346C, 0x03C689, 0x0365DC,
0x01E000, 0x017922, 0x02E3C1, 0x024539, 0x011235, 0x008BCA, 0x01905A, 0x00D64D,
0x02DBAD, 0x025A40, 0x02D134, 0x0236A5, 0x019ED6, 0x00D37B, 0x018650, 0x00C6FB,
0x02DBAD, 0x03F6DD, 0x02B1D9, 0x03BCD8, 0x026555, 0x0352B8, 0x01F6D8, 0x02B977,
0x03215C, 0x02D134, 0x029100, 0x0267EB, 0x0168CC, 0x021C7A, 0x00CE01, 0x011DBD,
0x01FD1E, 0x01CA31, 0x012062, 0x01038A, 0x03BBFB, 0x035C5C, 0x038554, 0x032B2D,
0x028000, 0x01F6D8, 0x0220C0, 0x01AC02, 0x015A5E, 0x00B7EF, 0x0126D1, 0x00964C,
0x0185A9, 0x013228, 0x00DCB8, 0x00B333, 0x00DAB2, 0x006F7D, 0x007B6F, 0x0042FC,
},
{
0x020000, 0x02F28D, 0x034B52, 0x0491B8, 0x02C6C5, 0x02A54E, 0x0487D8, 0x0413D5,
0x024000, 0x01C48F, 0x0377B5, 0x02B977, 0x01490C, 0x00A7BF, 0x01E06C, 0x01012A,
0x036E03, 0x02D2B3, 0x036172, 0x02A7FA, 0x01F1CE, 0x00FDC7, 0x01D460, 0x00EEC7,
0x036E03, 0x04C1D6, 0x033BD1, 0x047C37, 0x02E000, 0x03FCDD, 0x025B6A, 0x0344F5,
0x03C1A1, 0x036172, 0x031466, 0x02E31B, 0x01B0F5, 0x028892, 0x00F735, 0x0156E2,
0x0262F1, 0x0225D5, 0x015A10, 0x013772, 0x047B2D, 0x04086E, 0x043998, 0x03CD69,
0x030000, 0x025B6A, 0x028DB3, 0x02019B, 0x019FA3, 0x00DCB8, 0x0161C7, 0x00B45B,
0x01D398, 0x016F63, 0x0108DD, 0x00D70A, 0x01066F, 0x0085C9, 0x00941F, 0x005062,
},
{
0x02AAAB, 0x03EE11, 0x04646D, 0x0617A0, 0x03B3B2, 0x038713, 0x060A75, 0x056FC6,
0x030000, 0x025B6A, 0x049F9B, 0x03A1F4, 0x01B6BB, 0x00DFAA, 0x028090, 0x0156E2,
0x0492AE, 0x03C399, 0x0481ED, 0x038AA2, 0x0297BD, 0x01525F, 0x027080, 0x013E5E,
0x0492AE, 0x0657C8, 0x044FC1, 0x05FAF4, 0x03D555, 0x055126, 0x03248D, 0x045BF2,
0x05022D, 0x0481ED, 0x041B33, 0x03D979, 0x024147, 0x0360C3, 0x01499C, 0x01C92E,
0x032E96, 0x02DD1C, 0x01CD6A, 0x019F43, 0x05F991, 0x056093, 0x05A220, 0x0511E1,
0x040000, 0x03248D, 0x036799, 0x02ACCF, 0x022A2F, 0x01264B, 0x01D7B5, 0x00F079,
0x026F75, 0x01E9D9, 0x016127, 0x011EB8, 0x015DE9, 0x00B262, 0x00C57F, 0x006B2D,
},
{
0x038000, 0x052876, 0x05C3CF, 0x07FF02, 0x04DBD9, 0x04A148, 0x07EDBA, 0x0722B4,
0x03F000, 0x0317FB, 0x06117C, 0x04C491, 0x023FD5, 0x01258F, 0x0348BD, 0x01C209,
0x060085, 0x04F0B9, 0x05EA87, 0x04A5F5, 0x036728, 0x01BC1C, 0x0333A8, 0x01A1DB,
0x060085, 0x085336, 0x05A8AE, 0x07D960, 0x050800, 0x06FA82, 0x041FF9, 0x05B8AE,
0x0692DA, 0x05EA87, 0x0563B2, 0x050D6E, 0x02F5AD, 0x046F00, 0x01B09C, 0x02580C,
0x042D25, 0x03C235, 0x025D9B, 0x022108, 0x07D78F, 0x070EC1, 0x0764CA, 0x06A777,
0x054000, 0x041FF9, 0x0477F9, 0x0382D0, 0x02D75E, 0x018242, 0x026B1D, 0x013B9F,
0x03324A, 0x0282ED, 0x01CF83, 0x017851, 0x01CB42, 0x00EA21, 0x010336, 0x008CAC,
},
{
0x040000, 0x05E519, 0x0696A4, 0x092370, 0x058D8A, 0x054A9C, 0x090FB0, 0x0827AA,
0x048000, 0x03891F, 0x06EF69, 0x0572EE, 0x029218, 0x014F7E, 0x03C0D8, 0x020254,
0x06DC05, 0x05A565, 0x06C2E4, 0x054FF3, 0x03E39B, 0x01FB8E, 0x03A8C0, 0x01DD8D,
0x06DC05, 0x0983AC, 0x0677A2, 0x08F86E, 0x05C000, 0x07F9B9, 0x04B6D4, 0x0689EB,
0x078343, 0x06C2E4, 0x0628CC, 0x05C635, 0x0361EA, 0x051124, 0x01EE69, 0x02ADC5,
0x04C5E1, 0x044BAA, 0x02B41F, 0x026EE5, 0x08F65A, 0x0810DD, 0x087330, 0x079AD1,
0x060000, 0x04B6D4, 0x051B65, 0x040337, 0x033F47, 0x01B970, 0x02C38F, 0x0168B6,
0x03A730, 0x02DEC6, 0x0211BA, 0x01AE14, 0x020CDD, 0x010B93, 0x01283E, 0x00A0C4,
},
{
0x050000, 0x075E60, 0x083C4D, 0x0B6C4C, 0x06F0ED, 0x069D43, 0x0B539C, 0x0A3194,
0x05A000, 0x046B67, 0x08AB44, 0x06CFAA, 0x03369E, 0x01A35E, 0x04B10F, 0x0282E8,
0x089307, 0x070EBF, 0x08739C, 0x06A3F0, 0x04DC82, 0x027A72, 0x0492F0, 0x0254F0,
0x089307, 0x0BE497, 0x08158B, 0x0B3689, 0x073000, 0x09F827, 0x05E489, 0x082C66,
0x096413, 0x08739C, 0x07B2FF, 0x0737C2, 0x043A64, 0x06556D, 0x026A04, 0x035936,
0x05F75A, 0x055E94, 0x036127, 0x030A9E, 0x0B33F1, 0x0A1514, 0x0A8FFC, 0x098186,
0x078000, 0x05E489, 0x06623F, 0x050405, 0x040F19, 0x0227CC, 0x037473, 0x01C2E3,
0x0490FC, 0x039677, 0x029629, 0x021999, 0x029015, 0x014E78, 0x01724E, 0x00C8F5,
},
{
0x060000, 0x08D7A6, 0x09E1F6, 0x0DB528, 0x085450, 0x07EFEA, 0x0D9788, 0x0C3B7E,
0x06C000, 0x054DAE, 0x0A671E, 0x082C66, 0x03DB24, 0x01F73E, 0x05A145, 0x03037D,
0x0A4A08, 0x087818, 0x0A2455, 0x07F7ED, 0x05D569, 0x02F955, 0x057D20, 0x02CC54,
0x0A4A08, 0x0E4582, 0x09B373, 0x0D74A5, 0x08A000, 0x0BF696, 0x07123E, 0x09CEE0,
0x0B44E4, 0x0A2455, 0x093D32, 0x08A950, 0x0512DF, 0x0799B6, 0x02E59E, 0x0404A7,
0x0728D2, 0x06717F, 0x040E2F, 0x03A657, 0x0D7187, 0x0C194B, 0x0CACC8, 0x0B683A,
0x090000, 0x07123E, 0x07A918, 0x0604D2, 0x04DEEA, 0x029629, 0x042556, 0x021D11,
0x057AC8, 0x044E28, 0x031A97, 0x02851E, 0x03134C, 0x01915C, 0x01BC5D, 0x00F126,
},
{
0x080000, 0x0BCA33, 0x0D2D48, 0x1246E0, 0x0B1B15, 0x0A9538, 0x121F5F, 0x104F53,
0x090000, 0x07123E, 0x0DDED2, 0x0AE5DD, 0x052430, 0x029EFD, 0x0781B1, 0x0404A7,
0x0DB80B, 0x0B4ACB, 0x0D85C7, 0x0A9FE7, 0x07C736, 0x03F71D, 0x075180, 0x03BB1A,
0x0DB80B, 0x130757, 0x0CEF44, 0x11F0DC, 0x0B8000, 0x0FF372, 0x096DA8, 0x0D13D6,
0x0F0686, 0x0D85C7, 0x0C5198, 0x0B8C6A, 0x06C3D4, 0x0A2248, 0x03DCD3, 0x055B8A,
0x098BC3, 0x089754, 0x05683E, 0x04DDC9, 0x11ECB4, 0x1021B9, 0x10E661, 0x0F35A3,
0x0C0000, 0x096DA8, 0x0A36CB, 0x08066E, 0x067E8E, 0x0372E1, 0x05871E, 0x02D16B,
0x074E60, 0x05BD8B, 0x042374, 0x035C28, 0x0419BB, 0x021726, 0x02507C, 0x014188,
},
{
0x0C0000, 0x11AF4C, 0x13C3EC, 0x1B6A50, 0x10A89F, 0x0FDFD4, 0x1B2F0F, 0x1876FD,
0x0D8000, 0x0A9B5D, 0x14CE3C, 0x1058CB, 0x07B649, 0x03EE7B, 0x0B4289, 0x0606FB,
0x149410, 0x10F030, 0x1448AB, 0x0FEFDA, 0x0BAAD2, 0x05F2AB, 0x0AFA40, 0x0598A7,
0x149410, 0x1C8B03, 0x1366E6, 0x1AE949, 0x114000, 0x17ED2B, 0x0E247C, 0x139DC1,
0x1689C8, 0x1448AB, 0x127A63, 0x11529F, 0x0A25BE, 0x0F336D, 0x05CB3C, 0x08094E,
0x0E51A4, 0x0CE2FE, 0x081C5D, 0x074CAE, 0x1AE30E, 0x183296, 0x195991, 0x16D074,
0x120000, 0x0E247C, 0x0F5230, 0x0C09A5, 0x09BDD5, 0x052C51, 0x084AAC, 0x043A21,
0x0AF590, 0x089C51, 0x06352E, 0x050A3B, 0x062698, 0x0322B9, 0x0378BA, 0x01E24D,
},
{
0x110000, 0x190DAC, 0x1C0039, 0x26D69C, 0x17998C, 0x167D16, 0x2682AB, 0x22A891,
0x132000, 0x0F06C3, 0x1D797F, 0x172876, 0x0AECE7, 0x0591D9, 0x0FF398, 0x0889E3,
0x1D2717, 0x17FEEF, 0x1CBC47, 0x1693CA, 0x108754, 0x086D1D, 0x0F8D30, 0x07ED98,
0x1D2717, 0x286F9A, 0x1B7C71, 0x261FD3, 0x187000, 0x21E552, 0x140904, 0x1BCA27,
0x1FEDDC, 0x1CBC47, 0x1A2D62, 0x188A62, 0x0E6022, 0x1588DA, 0x083540, 0x0B6284,
0x1448FE, 0x124192, 0x0B7D84, 0x0A574B, 0x2616FF, 0x2247AA, 0x23E98D, 0x2051FA,
0x198000, 0x140904, 0x15B46F, 0x110DAA, 0x0DCCEE, 0x07541E, 0x0BBF1F, 0x05FD04,
0x0F868B, 0x0C32C8, 0x08CB57, 0x0723D4, 0x08B6AD, 0x047130, 0x04EB08, 0x02AB42,
},
{
0x160000, 0x206C0C, 0x243C86, 0x3242E8, 0x1E8A79, 0x1D1A59, 0x31D646, 0x2CDA25,
0x18C000, 0x13722A, 0x2624C3, 0x1DF820, 0x0E2385, 0x073537, 0x14A4A7, 0x0B0CCC,
0x25BA1D, 0x1F0DAE, 0x252FE4, 0x1D37BB, 0x1563D6, 0x0AE78E, 0x142021, 0x0A4288,
0x25BA1D, 0x345430, 0x2391FB, 0x31565C, 0x1FA000, 0x2BDD7A, 0x19ED8D, 0x23F68C,
0x2951EF, 0x252FE4, 0x21E061, 0x1FC224, 0x129A87, 0x1BDE47, 0x0A9F44, 0x0EBBBA,
0x1A4058, 0x17A026, 0x0EDEAB, 0x0D61E9, 0x314AEF, 0x2C5CBE, 0x2E798A, 0x29D380,
0x210000, 0x19ED8D, 0x1C16AE, 0x1611AE, 0x11DC06, 0x097BEA, 0x0F3391, 0x07BFE7,
0x141787, 0x0FC93E, 0x0B617F, 0x093D6D, 0x0B46C1, 0x05BFA8, 0x065D55, 0x037437,
},
{
0x1C0000, 0x2943B2, 0x2E1E7C, 0x3FF810, 0x26DEC9, 0x250A43, 0x3F6DCE, 0x3915A3,
0x1F8000, 0x18BFD8, 0x308BE1, 0x262485, 0x11FEA9, 0x092C75, 0x1A45EB, 0x0E1049,
0x300425, 0x2785C6, 0x2F5439, 0x252FA8, 0x1B393F, 0x0DE0E4, 0x199D41, 0x0D0EDC,
0x300425, 0x4299B2, 0x2D456E, 0x3ECB00, 0x284000, 0x37D40F, 0x20FFCB, 0x2DC56D,
0x3496D3, 0x2F5439, 0x2B1D93, 0x286B74, 0x17AD66, 0x2377FE, 0x0D84E2, 0x12C062,
0x21692A, 0x1E11A5, 0x12ECDA, 0x110840, 0x3EBC76, 0x387608, 0x3B2652, 0x353BBA,
0x2A0000, 0x20FFCB, 0x23BFC6, 0x1C1681, 0x16BAF1, 0x0C1213, 0x1358E8, 0x09DCF8,
0x19924F, 0x141767, 0x0E7C16, 0x0BC28A, 0x0E5A0D, 0x075104, 0x0819B2, 0x04655D,
},
{
0x220000, 0x321B58, 0x380072, 0x4DAD38, 0x2F3318, 0x2CFA2D, 0x4D0556, 0x455122,
0x264000, 0x1E0D86, 0x3AF2FE, 0x2E50EB, 0x15D9CE, 0x0B23B2, 0x1FE730, 0x1113C7,
0x3A4E2D, 0x2FFDDF, 0x39788E, 0x2D2795, 0x210EA8, 0x10DA39, 0x1F1A61, 0x0FDB2F,
0x3A4E2D, 0x50DF33, 0x36F8E1, 0x4C3FA5, 0x30E000, 0x43CAA5, 0x281209, 0x37944D,
0x3FDBB7, 0x39788E, 0x345AC4, 0x3114C3, 0x1CC044, 0x2B11B4, 0x106A80, 0x16C509,
0x2891FC, 0x248324, 0x16FB08, 0x14AE97, 0x4C2DFD, 0x448F54, 0x47D31B, 0x40A3F5,
0x330000, 0x281209, 0x2B68DF, 0x221B53, 0x1B99DB, 0x0EA83B, 0x177E3E, 0x0BFA09,
0x1F0D17, 0x18658F, 0x1196AE, 0x0E47A8, 0x116D5A, 0x08E260, 0x09D60F, 0x055684,
},
{
0x2C0000, 0x40D818, 0x48790C, 0x6485D0, 0x3D14F2, 0x3A34B2, 0x63AC8D, 0x59B44A,
0x318000, 0x26E454, 0x4C4986, 0x3BF03F, 0x1C470A, 0x0E6A6E, 0x29494D, 0x161998,
0x4B743A, 0x3E1B5C, 0x4A5FC7, 0x3A6F75, 0x2AC7AC, 0x15CF1D, 0x284041, 0x148510,
0x4B743A, 0x68A861, 0x4723F6, 0x62ACB8, 0x3F4000, 0x57BAF3, 0x33DB1A, 0x47ED19,
0x52A3DE, 0x4A5FC7, 0x43C0C2, 0x3F8448, 0x25350D, 0x37BC8E, 0x153E87, 0x1D7775,
0x3480B0, 0x2F404C, 0x1DBD56, 0x1AC3D2, 0x6295DE, 0x58B97B, 0x5CF313, 0x53A701,
0x420000, 0x33DB1A, 0x382D5C, 0x2C235D, 0x23B80D, 0x12F7D4, 0x1E6723, 0x0F7FCF,
0x282F0E, 0x1F927D, 0x16C2FF, 0x127AD9, 0x168D83, 0x0B7F50, 0x0CBAAA, 0x06E86E,
},
};
} // End of namespace Video
#endif // GRAPHICS_VIDEO_BINKDATA_H

3101
video/coktel_decoder.cpp Normal file

File diff suppressed because it is too large Load Diff

658
video/coktel_decoder.h Normal file
View File

@@ -0,0 +1,658 @@
/* 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/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, this code is also
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
* full text of the license.
*
*/
// Currently, only GOB and SCI32 games play IMDs and VMDs, so skip compiling if GOB and SCI32 is disabled.
#if !(defined(ENABLE_GOB) || defined(ENABLE_SCI32) || defined(DYNAMIC_MODULES))
// Do not compile the CoktelDecoder code
#else
#ifndef VIDEO_COKTELDECODER_H
#define VIDEO_COKTELDECODER_H
#include "common/list.h"
#include "common/array.h"
#include "common/rational.h"
#include "common/str.h"
#include "graphics/palette.h"
#include "graphics/surface.h"
#include "video/video_decoder.h"
#include "audio/mixer.h"
namespace Common {
struct Rect;
class MemoryReadWriteStream;
class SeekableReadStream;
}
namespace Audio {
class QueuingAudioStream;
}
namespace Graphics {
struct PixelFormat;
}
namespace Image {
class Codec;
}
namespace Video {
/**
* Decoder for Coktel videos.
*
* Video decoder used in engines:
* - gob
* - sci
*/
class CoktelDecoder {
public:
struct State {
/** Set accordingly to what was done. */
uint32 flags;
/** The id of the spoken words. */
uint16 speechId;
State();
};
CoktelDecoder(Audio::Mixer *mixer,
Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType);
virtual ~CoktelDecoder();
/** Replace the current video stream with this identical one. */
virtual bool reloadStream(Common::SeekableReadStream *stream) = 0;
virtual bool seek(int32 frame, int whence = SEEK_SET, bool restart = false) = 0;
/** Draw directly onto the specified video memory. */
void setSurfaceMemory(void *mem, uint16 width, uint16 height, uint8 bpp);
/** Reset the video memory. */
void setSurfaceMemory();
const Graphics::Surface *getSurface() const;
/** Draw the video starting at this position within the video memory. */
virtual void setXY(uint16 x, uint16 y);
/** Draw the video at the default position. */
void setXY();
void setDouble(bool isDouble); // double the size of the video, to accommodate higher resolutions
/** Override the video's frame rate. */
void setFrameRate(Common::Rational frameRate);
/** Get the video's frame rate. */
Common::Rational getFrameRate() const;
/** Get the video's default X position. */
uint16 getDefaultX() const;
/** Get the video's default Y position. */
uint16 getDefaultY() const;
/** Return a list of rectangles that changed in the last frame. */
const Common::List<Common::Rect> &getDirtyRects() const;
bool hasPalette() const;
virtual bool hasVideo() const;
bool hasSound() const;
bool isSoundEnabled() const;
bool isSoundPlaying() const;
void enableSound();
void disableSound();
void finishSound();
virtual void colorModeChanged();
/** Return the coordinates of the specified frame. */
virtual bool getFrameCoords(int16 frame, int16 &x, int16 &y, int16 &width, int16 &height);
/** Return whether that video has any embedded files. */
virtual bool hasEmbeddedFiles() const;
/** Return whether that embedded file exists. */
virtual bool hasEmbeddedFile(const Common::String &fileName) const;
/** Return that embedded file. */
virtual Common::SeekableReadStream *getEmbeddedFile(const Common::String &fileName) const;
/** Return the current subtitle index. */
virtual int32 getSubtitleIndex() const;
/** Is the video paletted or true color? */
virtual bool isPaletted() const;
virtual uint32 getVideoBufferSize() const = 0;
/**
* Get the current frame
* @see VideoDecoder::getCurFrame()
*/
int getCurFrame() const;
int getNbFramesPastEnd() const;
/**
* Decode the next frame
* @see VideoDecoder::decodeNextFrame()
*/
virtual const Graphics::Surface *decodeNextFrame() = 0;
/**
* Load a video from a stream
* @see VideoDecoder::loadStream()
*/
virtual bool loadStream(Common::SeekableReadStream *stream) = 0;
/** Has a video been loaded? */
virtual bool isVideoLoaded() const = 0;
/** Has the end of the video been reached? */
bool endOfVideo() const;
/** Close the video. */
void close();
/** Get the Mixer SoundType audio is being played with. */
Audio::Mixer::SoundType getSoundType() const;
/** Get the AudioStream for the audio. */
Audio::AudioStream *getAudioStream() const;
uint16 getWidth() const;
uint16 getHeight() const;
virtual uint32 getFlags() const = 0;
virtual uint16 getSoundFlags() const = 0;
virtual Graphics::PixelFormat getPixelFormat() const = 0;
uint32 getFrameCount() const;
const byte *getPalette();
bool hasDirtyPalette() const;
uint32 getTimeToNextFrame() const;
uint32 getStaticTimeToNextFrame() const;
int32 getExpectedFrameFromCurrentTime() const;
void pauseVideo(bool pause);
protected:
enum SoundStage {
kSoundNone = 0, ///< No sound.
kSoundLoaded = 1, ///< Sound loaded.
kSoundPlaying = 2, ///< Sound is playing.
kSoundFinished = 3 ///< No more new sound data.
};
enum Features {
kFeaturesNone = 0x0000,
kFeaturesPalette = 0x0008, ///< Has an own palette.
kFeaturesDataSize = 0x0020, ///< Suggests a data size.
kFeaturesSound = 0x0040, ///< Has sound.
kFeaturesFrameCoords = 0x0080, ///< Has specific frame coordinates.
kFeaturesStdCoords = 0x0100, ///< Has general standard coordinates.
kFeaturesFramePos = 0x0200, ///< Has a frame positions table.
kFeaturesVideo = 0x0400 ///< Has video.
};
Audio::Mixer *_mixer;
Audio::Mixer::SoundType _soundType;
uint16 _width;
uint16 _height;
uint16 _x;
uint16 _y;
uint16 _defaultX;
uint16 _defaultY;
uint32 _features;
int32 _curFrame;
int32 _nbFramesPastEnd;
uint32 _frameCount;
uint32 _startTime;
Graphics::Palette _palette;
bool _paletteDirty;
bool _isDouble;
bool _ownSurface;
Graphics::Surface _surface;
Common::List<Common::Rect> _dirtyRects;
Common::Rational _frameRate;
// Current sound state
bool _hasSound;
bool _soundEnabled;
SoundStage _soundStage;
Audio::QueuingAudioStream *_audioStream;
Audio::SoundHandle _audioHandle;
bool evaluateSeekFrame(int32 &frame, int whence) const;
// Surface management
bool hasSurface();
void createSurface();
void freeSurface();
// Decompression
uint32 deLZ77(byte *dest, const byte *src, uint32 srcSize, uint32 destSize);
void deRLE(byte *&destPtr, const byte *&srcPtr, int16 destLen, int16 srcLen);
// Block rendering
void renderBlockWhole (Graphics::Surface &dstSurf, const byte *src, Common::Rect &rect);
void renderBlockWholeDouble (Graphics::Surface &dstSurf, const byte *src, Common::Rect &rect);
void renderBlockWhole4X (Graphics::Surface &dstSurf, const byte *src, Common::Rect &rect);
void renderBlockWhole2Y (Graphics::Surface &dstSurf, const byte *src, Common::Rect &rect);
void renderBlockSparse (Graphics::Surface &dstSurf, const byte *src, Common::Rect &rect);
void renderBlockSparseDouble(Graphics::Surface &dstSurf, const byte *src, Common::Rect &rect);
void renderBlockSparse2Y (Graphics::Surface &dstSurf, const byte *src, Common::Rect &rect);
void renderBlockRLE (Graphics::Surface &dstSurf, const byte *src, Common::Rect &rect);
// Sound helper functions
inline void unsignedToSigned(byte *buffer, int length);
private:
uint32 _pauseStartTime;
bool _isPaused;
};
class PreIMDDecoder : public CoktelDecoder {
public:
PreIMDDecoder(uint16 width, uint16 height, Audio::Mixer *mixer,
Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType);
~PreIMDDecoder();
bool reloadStream(Common::SeekableReadStream *stream);
bool seek(int32 frame, int whence = SEEK_SET, bool restart = false);
bool loadStream(Common::SeekableReadStream *stream);
void close();
bool isVideoLoaded() const;
const Graphics::Surface *decodeNextFrame();
uint32 getFlags() const;
uint16 getSoundFlags() const;
uint32 getVideoBufferSize() const;
Graphics::PixelFormat getPixelFormat() const;
private:
Common::SeekableReadStream *_stream;
// Buffer for processed frame data
byte *_videoBuffer;
uint32 _videoBufferSize;
// Frame decoding
void processFrame();
void renderFrame();
};
class IMDDecoder : public CoktelDecoder {
public:
IMDDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType);
~IMDDecoder();
bool reloadStream(Common::SeekableReadStream *stream);
bool seek(int32 frame, int whence = SEEK_SET, bool restart = false);
void setXY(uint16 x, uint16 y);
bool loadStream(Common::SeekableReadStream *stream);
void close();
bool isVideoLoaded() const;
const Graphics::Surface *decodeNextFrame();
uint32 getFlags() const;
uint16 getSoundFlags() const;
uint32 getVideoBufferSize() const;
Graphics::PixelFormat getPixelFormat() const;
private:
enum Command {
kCommandNextSound = 0xFF00,
kCommandStartSound = 0xFF01,
kCommandBreak = 0xFFF0,
kCommandBreakSkip0 = 0xFFF1,
kCommandBreakSkip16 = 0xFFF2,
kCommandBreakSkip32 = 0xFFF3,
kCommandBreakMask = 0xFFF8,
kCommandPalette = 0xFFF4,
kCommandVideoData = 0xFFFC,
kCommandJump = 0xFFFD
};
struct Coord {
int16 left;
int16 top;
int16 right;
int16 bottom;
};
Common::SeekableReadStream *_stream;
byte _version;
// Standard coordinates gives by the header
int16 _stdX;
int16 _stdY;
int16 _stdWidth;
int16 _stdHeight;
uint32 _flags;
uint32 _firstFramePos; ///< Position of the first frame's data within the stream.
uint32 *_framePos; ///< Positions of all frames.
Coord *_frameCoords; ///< Coordinates of all frames.
uint32 _videoBufferSize; ///< Size of the video buffers.
byte *_videoBuffer[2]; ///< Video buffers.
uint32 _videoBufferLen[2]; ///< Size of the video buffers filled.
// Sound properties
uint16 _soundFlags;
int16 _soundFreq;
int16 _soundSliceSize;
int16 _soundSlicesCount;
// Loading helper functions
bool loadCoordinates();
bool loadFrameTableOffsets(uint32 &framePosPos, uint32 &frameCoordsPos);
bool assessVideoProperties();
bool assessAudioProperties();
bool loadFrameTables(uint32 framePosPos, uint32 frameCoordsPos);
// Frame decoding
void processFrame();
Common::Rect calcFrameCoords(uint32 frame);
// Video
bool renderFrame(Common::Rect &rect);
// Sound
void nextSoundSlice(bool hasNextCmd);
bool initialSoundSlice(bool hasNextCmd);
void emptySoundSlice(bool hasNextCmd);
};
class VMDDecoder : public CoktelDecoder {
friend class AdvancedVMDDecoder;
public:
VMDDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType);
~VMDDecoder();
bool reloadStream(Common::SeekableReadStream *stream);
bool seek(int32 frame, int whence = SEEK_SET, bool restart = false);
void setXY(uint16 x, uint16 y);
void colorModeChanged();
bool getFrameCoords(int16 frame, int16 &x, int16 &y, int16 &width, int16 &height);
bool hasEmbeddedFiles() const;
bool hasEmbeddedFile(const Common::String &fileName) const;
Common::SeekableReadStream *getEmbeddedFile(const Common::String &fileName) const;
int32 getSubtitleIndex() const;
bool hasVideo() const;
bool isPaletted() const;
bool loadStream(Common::SeekableReadStream *stream);
void close();
bool isVideoLoaded() const;
const Graphics::Surface *decodeNextFrame();
uint32 getFlags() const;
uint16 getSoundFlags() const;
uint32 getVideoBufferSize() const;
Graphics::PixelFormat getPixelFormat() const;
protected:
void setAutoStartSound(bool autoStartSound);
private:
enum PartType {
kPartTypeSeparator = 0,
kPartTypeAudio = 1,
kPartTypeVideo = 2,
kPartTypeFile = 3,
kPartType4 = 4,
kPartTypeSubtitle = 5
};
enum AudioFormat {
kAudioFormat8bitRaw = 0,
kAudioFormat16bitDPCM = 1,
kAudioFormat16bitADPCM = 2
};
struct File {
Common::String name;
uint32 offset;
uint32 size;
uint32 realSize;
File();
};
struct Part {
PartType type;
byte field_1;
byte field_E;
uint32 size;
int16 left;
int16 top;
int16 right;
int16 bottom;
uint16 id;
byte flags;
Part();
};
struct Frame {
uint32 offset;
Part *parts;
Frame();
~Frame();
};
Common::SeekableReadStream *_stream;
byte _version;
uint32 _flags;
uint32 _frameInfoOffset;
uint16 _partsPerFrame;
Frame *_frames;
Common::Array<File> _files;
// Sound properties
uint16 _soundFlags;
uint16 _soundFreq;
int16 _soundSliceSize;
int16 _soundSlicesCount;
byte _soundBytesPerSample;
byte _soundStereo; // (0: mono, 1: old-style stereo, 2: new-style stereo)
uint32 _soundHeaderSize;
uint32 _soundDataSize;
uint32 _soundLastFilledFrame;
AudioFormat _audioFormat;
bool _autoStartSound;
/**
* Old stereo format packs a DPCM stream into audio packets without ensuring
* that each packet contains an even amount of samples. In order for the
* stream to play back correctly, all audio data needs to be pushed into a
* single data buffer and read from there.
*
* This buffer is owned by _audioStream and will be disposed when
* _audioStream is disposed.
*/
Common::MemoryReadWriteStream *_oldStereoBuffer;
// Video properties
bool _hasVideo;
uint32 _videoCodec;
byte _blitMode;
byte _bytesPerPixel;
uint32 _firstFramePos; ///< Position of the first frame's data within the stream.
uint32 _videoBufferSize; ///< Size of the video buffers.
byte *_videoBuffer[3]; ///< Video buffers.
uint32 _videoBufferLen[3]; ///< Size of the video buffers filled.
Graphics::Surface _8bppSurface[3]; ///< Fake 8bpp surfaces over the video buffers.
bool _externalCodec;
Image::Codec *_codec;
int32 _subtitle;
bool _isPaletted;
// Loading helper functions
bool assessVideoProperties();
bool assessAudioProperties();
bool openExternalCodec();
bool readFrameTable(int &numFiles);
bool readFiles();
// Frame decoding
void processFrame();
// Video
bool renderFrame(Common::Rect &rect);
bool getRenderRects(const Common::Rect &rect,
Common::Rect &realRect, Common::Rect &fakeRect);
void blit16(const Graphics::Surface &srcSurf, Common::Rect &rect);
void blit24(const Graphics::Surface &srcSurf, Common::Rect &rect);
// Sound
void emptySoundSlice (uint32 size);
void filledSoundSlice (uint32 size);
void filledSoundSlices(uint32 size, uint32 mask);
void createAudioStream();
uint8 evaluateMask(uint32 mask, bool *fillInfo, uint8 &max);
// Generating audio streams
Audio::AudioStream *create8bitRaw (Common::SeekableReadStream *stream);
Audio::AudioStream *create16bitDPCM (Common::SeekableReadStream *stream);
Audio::AudioStream *create16bitADPCM(Common::SeekableReadStream *stream);
bool getPartCoords(int16 frame, PartType type, int16 &x, int16 &y, int16 &width, int16 &height);
};
/**
* A wrapper around the VMD code that implements the VideoDecoder
* API.
*/
class AdvancedVMDDecoder : public VideoDecoder {
public:
AdvancedVMDDecoder(Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType);
~AdvancedVMDDecoder();
bool loadStream(Common::SeekableReadStream *stream);
void close();
void setSurfaceMemory(void *mem, uint16 width, uint16 height, uint8 bpp);
private:
class VMDVideoTrack : public FixedRateVideoTrack {
public:
VMDVideoTrack(VMDDecoder *decoder);
uint16 getWidth() const;
uint16 getHeight() const;
Graphics::PixelFormat getPixelFormat() const;
int getCurFrame() const;
int getFrameCount() const;
const Graphics::Surface *decodeNextFrame();
const byte *getPalette() const;
bool hasDirtyPalette() const;
protected:
Common::Rational getFrameRate() const;
private:
VMDDecoder *_decoder;
};
class VMDAudioTrack : public AudioTrack {
public:
VMDAudioTrack(VMDDecoder *decoder);
protected:
virtual Audio::AudioStream *getAudioStream() const;
private:
VMDDecoder *_decoder;
};
VMDDecoder *_decoder;
VMDVideoTrack *_videoTrack;
VMDAudioTrack *_audioTrack;
};
} // End of namespace Video
#endif // VIDEO_COKTELDECODER_H
#endif // Engine and dynamic plugins guard

549
video/dxa_decoder.cpp Normal file
View File

@@ -0,0 +1,549 @@
/* 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/debug.h"
#include "common/endian.h"
#include "common/system.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "graphics/surface.h"
#include "video/dxa_decoder.h"
#include "audio/decoders/wave.h"
#include "common/compression/deflate.h"
namespace Video {
DXADecoder::DXADecoder() {
}
DXADecoder::~DXADecoder() {
close();
}
bool DXADecoder::loadStream(Common::SeekableReadStream *stream) {
close();
uint32 tag = stream->readUint32BE();
if (tag != MKTAG('D','E','X','A')) {
close();
return false;
}
DXAVideoTrack *track = new DXAVideoTrack(stream);
addTrack(track);
readSoundData(stream);
track->setFrameStartPos();
return true;
}
void DXADecoder::readSoundData(Common::SeekableReadStream *stream) {
uint32 tag = stream->readUint32BE();
if (tag == MKTAG('W','A','V','E')) {
uint32 size = stream->readUint32BE();
addStreamTrack(Audio::makeWAVStream(stream->readStream(size), DisposeAfterUse::YES));
} else if (tag != MKTAG('N','U','L','L')) {
stream->seek(-4, SEEK_CUR);
}
}
DXADecoder::DXAVideoTrack::DXAVideoTrack(Common::SeekableReadStream *stream) : _palette(256) {
_fileStream = stream;
_curFrame = -1;
_frameStartOffset = 0;
_decompBuffer = 0;
_inBuffer = 0;
uint8 flags = _fileStream->readByte();
_frameCount = _fileStream->readUint16BE();
int32 frameRate = _fileStream->readSint32BE();
if (frameRate > 0)
_frameRate = 1000 / frameRate;
else if (frameRate < 0)
_frameRate = 100000 / (-frameRate);
else
_frameRate = 10;
_width = _fileStream->readUint16BE();
_height = _fileStream->readUint16BE();
if (flags & 0x80) {
_scaleMode = S_INTERLACED;
_curHeight = _height / 2;
} else if (flags & 0x40) {
_scaleMode = S_DOUBLE;
_curHeight = _height / 2;
} else {
_scaleMode = S_NONE;
_curHeight = _height;
}
_surface = new Graphics::Surface();
_surface->format = Graphics::PixelFormat::createFormatCLUT8();
debugC(2, kDebugLevelGVideo, "flags 0x0%x framesCount %d width %d height %d rate %d", flags, getFrameCount(), getWidth(), getHeight(), getFrameRate().toInt());
_frameSize = _width * _height;
_decompBufferSize = _frameSize;
_frameBuffer1 = new byte[_frameSize]();
_frameBuffer2 = new byte[_frameSize]();
_scaledBuffer = 0;
if (_scaleMode != S_NONE) {
_scaledBuffer = new byte[_frameSize]();
}
#ifdef DXA_EXPERIMENT_MAXD
// Check for an extended header
if (flags & 1) {
uint32 size;
do {
tag = _fileStream->readUint32BE();
if (tag != 0)
size = _fileStream->readUint32BE();
switch (tag) {
case 0: // No more tags
break;
case MKTAG('M','A','X','D'):
assert(size == 4);
_decompBufferSize = _fileStream->readUint32BE();
break;
default: // Unknown tag - skip it.
while (size > 0) {
byte dummy = _fileStream->readByte();
size--;
}
break;
}
} while (tag != 0);
}
#endif
}
DXADecoder::DXAVideoTrack::~DXAVideoTrack() {
delete _fileStream;
delete _surface;
delete[] _frameBuffer1;
delete[] _frameBuffer2;
delete[] _scaledBuffer;
delete[] _inBuffer;
delete[] _decompBuffer;
}
bool DXADecoder::DXAVideoTrack::rewind() {
_curFrame = -1;
_fileStream->seek(_frameStartOffset);
return true;
}
Graphics::PixelFormat DXADecoder::DXAVideoTrack::getPixelFormat() const {
return _surface->format;
}
void DXADecoder::DXAVideoTrack::setFrameStartPos() {
_frameStartOffset = _fileStream->pos();
}
void DXADecoder::DXAVideoTrack::decodeZlib(byte *data, int size, int totalSize) {
Common::inflateZlib(data, totalSize, _inBuffer, size);
}
#define BLOCKW 4
#define BLOCKH 4
void DXADecoder::DXAVideoTrack::decode12(int size) {
if (!_decompBuffer) {
_decompBuffer = new byte[_decompBufferSize]();
}
/* decompress the input data */
decodeZlib(_decompBuffer, size, _decompBufferSize);
byte *dat = _decompBuffer;
memcpy(_frameBuffer2, _frameBuffer1, _frameSize);
for (uint32 by = 0; by < _height; by += BLOCKH) {
for (uint32 bx = 0; bx < _width; bx += BLOCKW) {
byte type = *dat++;
byte *b2 = _frameBuffer1 + bx + by * _width;
switch (type) {
case 0:
break;
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
case 1: {
unsigned short diffMap;
if (type >= 10 && type <= 15) {
static const struct { uint8 sh1, sh2; } shiftTbl[6] = {
{0, 0}, {8, 0}, {8, 8}, {8, 4}, {4, 0}, {4, 4}
};
diffMap = ((*dat & 0xF0) << shiftTbl[type-10].sh1) |
((*dat & 0x0F) << shiftTbl[type-10].sh2);
dat++;
} else {
diffMap = READ_BE_UINT16(dat);
dat += 2;
}
for (int yc = 0; yc < BLOCKH; yc++) {
for (int xc = 0; xc < BLOCKW; xc++) {
if (diffMap & 0x8000) {
b2[xc] = *dat++;
}
diffMap <<= 1;
}
b2 += _width;
}
break;
}
case 2: {
byte color = *dat++;
for (int yc = 0; yc < BLOCKH; yc++) {
for (int xc = 0; xc < BLOCKW; xc++) {
b2[xc] = color;
}
b2 += _width;
}
break;
}
case 3: {
for (int yc = 0; yc < BLOCKH; yc++) {
for (int xc = 0; xc < BLOCKW; xc++) {
b2[xc] = *dat++;
}
b2 += _width;
}
break;
}
case 4: {
byte mbyte = *dat++;
int mx = (mbyte >> 4) & 0x07;
if (mbyte & 0x80)
mx = -mx;
int my = mbyte & 0x07;
if (mbyte & 0x08)
my = -my;
byte *b1 = _frameBuffer2 + (bx+mx) + (by+my) * _width;
for (int yc = 0; yc < BLOCKH; yc++) {
memcpy(b2, b1, BLOCKW);
b1 += _width;
b2 += _width;
}
break;
}
case 5:
break;
default:
error("decode12: Unknown type %d", type);
}
}
}
}
void DXADecoder::DXAVideoTrack::decode13(int size) {
uint8 *codeBuf, *dataBuf, *motBuf, *maskBuf;
if (!_decompBuffer) {
_decompBuffer = new byte[_decompBufferSize]();
}
/* decompress the input data */
decodeZlib(_decompBuffer, size, _decompBufferSize);
memcpy(_frameBuffer2, _frameBuffer1, _frameSize);
int codeSize = _width * _curHeight / 16;
int dataSize, motSize;
dataSize = READ_BE_UINT32(&_decompBuffer[0]);
motSize = READ_BE_UINT32(&_decompBuffer[4]);
//maskSize = READ_BE_UINT32(&_decompBuffer[8]);
codeBuf = &_decompBuffer[12];
dataBuf = &codeBuf[codeSize];
motBuf = &dataBuf[dataSize];
maskBuf = &motBuf[motSize];
for (uint32 by = 0; by < _curHeight; by += BLOCKH) {
for (uint32 bx = 0; bx < _width; bx += BLOCKW) {
uint8 type = *codeBuf++;
uint8 *b2 = (uint8 *)_frameBuffer1 + bx + by * _width;
switch (type) {
case 0:
break;
case 1: {
uint16 diffMap = READ_BE_UINT16(maskBuf);
maskBuf += 2;
for (int yc = 0; yc < BLOCKH; yc++) {
for (int xc = 0; xc < BLOCKW; xc++) {
if (diffMap & 0x8000) {
b2[xc] = *dataBuf++;
}
diffMap <<= 1;
}
b2 += _width;
}
break;
}
case 2: {
uint8 color = *dataBuf++;
for (int yc = 0; yc < BLOCKH; yc++) {
for (int xc = 0; xc < BLOCKW; xc++) {
b2[xc] = color;
}
b2 += _width;
}
break;
}
case 3: {
for (int yc = 0; yc < BLOCKH; yc++) {
for (int xc = 0; xc < BLOCKW; xc++) {
b2[xc] = *dataBuf++;
}
b2 += _width;
}
break;
}
case 4: {
uint8 mbyte = *motBuf++;
int mx = (mbyte >> 4) & 0x07;
if (mbyte & 0x80)
mx = -mx;
int my = mbyte & 0x07;
if (mbyte & 0x08)
my = -my;
uint8 *b1 = (uint8 *)_frameBuffer2 + (bx+mx) + (by+my) * _width;
for (int yc = 0; yc < BLOCKH; yc++) {
memcpy(b2, b1, BLOCKW);
b1 += _width;
b2 += _width;
}
break;
}
case 8: {
static const int subX[4] = {0, 2, 0, 2};
static const int subY[4] = {0, 0, 2, 2};
uint8 subMask = *maskBuf++;
for (int subBlock = 0; subBlock < 4; subBlock++) {
int sx = bx + subX[subBlock], sy = by + subY[subBlock];
b2 = (uint8 *)_frameBuffer1 + sx + sy * _width;
switch (subMask & 0xC0) {
// 00: skip
case 0x00:
default:
break;
// 01: solid color
case 0x40: {
uint8 subColor = *dataBuf++;
for (int yc = 0; yc < BLOCKH / 2; yc++) {
for (int xc = 0; xc < BLOCKW / 2; xc++) {
b2[xc] = subColor;
}
b2 += _width;
}
break;
}
// 02: motion vector
case 0x80: {
uint8 mbyte = *motBuf++;
int mx = (mbyte >> 4) & 0x07;
if (mbyte & 0x80)
mx = -mx;
int my = mbyte & 0x07;
if (mbyte & 0x08)
my = -my;
uint8 *b1 = (uint8 *)_frameBuffer2 + (sx+mx) + (sy+my) * _width;
for (int yc = 0; yc < BLOCKH / 2; yc++) {
memcpy(b2, b1, BLOCKW / 2);
b1 += _width;
b2 += _width;
}
break;
}
// 03: raw
case 0xC0:
for (int yc = 0; yc < BLOCKH / 2; yc++) {
for (int xc = 0; xc < BLOCKW / 2; xc++) {
b2[xc] = *dataBuf++;
}
b2 += _width;
}
break;
}
subMask <<= 2;
}
break;
}
case 32:
case 33:
case 34: {
int count = type - 30;
uint8 pixels[4];
memcpy(pixels, dataBuf, count);
dataBuf += count;
if (count == 2) {
uint16 code = READ_BE_UINT16(maskBuf);
maskBuf += 2;
for (int yc = 0; yc < BLOCKH; yc++) {
for (int xc = 0; xc < BLOCKW; xc++) {
b2[xc] = pixels[code & 1];
code >>= 1;
}
b2 += _width;
}
} else {
uint32 code = READ_BE_UINT32(maskBuf);
maskBuf += 4;
for (int yc = 0; yc < BLOCKH; yc++) {
for (int xc = 0; xc < BLOCKW; xc++) {
b2[xc] = pixels[code & 3];
code >>= 2;
}
b2 += _width;
}
}
break;
}
default:
error("decode13: Unknown type %d", type);
}
}
}
}
const Graphics::Surface *DXADecoder::DXAVideoTrack::decodeNextFrame() {
uint32 tag = _fileStream->readUint32BE();
if (tag == MKTAG('C','M','A','P')) {
for (int i = 0; i < 256; i++) {
byte r = _fileStream->readByte();
byte g = _fileStream->readByte();
byte b = _fileStream->readByte();
_palette.set(i, r, g, b);
}
_dirtyPalette = true;
}
tag = _fileStream->readUint32BE();
if (tag == MKTAG('F','R','A','M')) {
byte type = _fileStream->readByte();
uint32 size = _fileStream->readUint32BE();
if (!_inBuffer || _inBufferSize < size) {
delete[] _inBuffer;
_inBuffer = new byte[size]();
_inBufferSize = size;
}
_fileStream->read(_inBuffer, size);
switch (type) {
case 2:
decodeZlib(_frameBuffer1, size, _frameSize);
break;
case 3:
decodeZlib(_frameBuffer2, size, _frameSize);
break;
case 12:
decode12(size);
break;
case 13:
decode13(size);
break;
default:
error("decodeFrame: Unknown compression type %d", type);
}
if (type == 3) {
for (uint32 j = 0; j < _curHeight; ++j) {
for (uint32 i = 0; i < _width; ++i) {
const int offs = j * _width + i;
_frameBuffer1[offs] ^= _frameBuffer2[offs];
}
}
}
}
switch (_scaleMode) {
case S_INTERLACED:
for (int cy = 0; cy < _curHeight; cy++) {
memcpy(&_scaledBuffer[2 * cy * _width], &_frameBuffer1[cy * _width], _width);
memset(&_scaledBuffer[((2 * cy) + 1) * _width], 0, _width);
}
_surface->setPixels(_scaledBuffer);
break;
case S_DOUBLE:
for (int cy = 0; cy < _curHeight; cy++) {
memcpy(&_scaledBuffer[2 * cy * _width], &_frameBuffer1[cy * _width], _width);
memcpy(&_scaledBuffer[((2 * cy) + 1) * _width], &_frameBuffer1[cy * _width], _width);
}
_surface->setPixels(_scaledBuffer);
break;
case S_NONE:
_surface->setPixels(_frameBuffer1);
break;
default:
break;
}
// Copy in the relevant info to the Surface
_surface->w = getWidth();
_surface->h = getHeight();
_surface->pitch = getWidth();
_curFrame++;
return _surface;
}
} // End of namespace Video

116
video/dxa_decoder.h Normal file
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/>.
*
*/
#ifndef VIDEO_DXA_DECODER_H
#define VIDEO_DXA_DECODER_H
#include "common/rational.h"
#include "graphics/palette.h"
#include "graphics/pixelformat.h"
#include "video/video_decoder.h"
namespace Common {
class SeekableReadStream;
}
namespace Video {
/**
* Decoder for DXA videos.
*
* Video decoder used in engines:
* - agos
* - sword1
* - sword2
*/
class DXADecoder : public VideoDecoder {
public:
DXADecoder();
virtual ~DXADecoder();
bool loadStream(Common::SeekableReadStream *stream);
protected:
/**
* Read the sound data out of the given DXA stream
*/
virtual void readSoundData(Common::SeekableReadStream *stream);
private:
class DXAVideoTrack : public FixedRateVideoTrack {
public:
DXAVideoTrack(Common::SeekableReadStream *stream);
~DXAVideoTrack();
bool isRewindable() const { return true; }
bool rewind();
uint16 getWidth() const { return _width; }
uint16 getHeight() const { return _height; }
Graphics::PixelFormat getPixelFormat() const;
int getCurFrame() const { return _curFrame; }
int getFrameCount() const { return _frameCount; }
const Graphics::Surface *decodeNextFrame();
const byte *getPalette() const { _dirtyPalette = false; return _palette.data(); }
bool hasDirtyPalette() const { return _dirtyPalette; }
void setFrameStartPos();
protected:
Common::Rational getFrameRate() const { return _frameRate; }
private:
void decodeZlib(byte *data, int size, int totalSize);
void decode12(int size);
void decode13(int size);
enum ScaleMode {
S_NONE,
S_INTERLACED,
S_DOUBLE
};
Common::SeekableReadStream *_fileStream;
Graphics::Surface *_surface;
byte *_frameBuffer1;
byte *_frameBuffer2;
byte *_scaledBuffer;
byte *_inBuffer;
uint32 _inBufferSize;
byte *_decompBuffer;
uint32 _decompBufferSize;
uint16 _curHeight;
uint32 _frameSize;
ScaleMode _scaleMode;
uint16 _width, _height;
uint32 _frameRate;
uint32 _frameCount;
Graphics::Palette _palette;
mutable bool _dirtyPalette;
int _curFrame;
uint32 _frameStartOffset;
};
};
} // End of namespace Video
#endif

389
video/flic_decoder.cpp Normal file
View File

@@ -0,0 +1,389 @@
/* 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 "video/flic_decoder.h"
#include "common/endian.h"
#include "common/rect.h"
#include "common/stream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "graphics/surface.h"
namespace Video {
FlicDecoder::FlicDecoder() {
}
FlicDecoder::~FlicDecoder() {
close();
}
bool FlicDecoder::loadStream(Common::SeekableReadStream *stream) {
close();
/* uint32 frameSize = */ stream->readUint32LE();
uint16 frameType = stream->readUint16LE();
// Check FLC magic number
if (frameType != 0xAF12) {
warning("FlicDecoder::loadStream(): attempted to load non-FLC data (type = 0x%04X)", frameType);
return false;
}
uint16 frameCount = stream->readUint16LE();
uint16 width = stream->readUint16LE();
uint16 height = stream->readUint16LE();
uint16 colorDepth = stream->readUint16LE();
if (colorDepth != 8) {
warning("FlicDecoder::loadStream(): attempted to load an FLC with a palette of color depth %d. Only 8-bit color palettes are supported", colorDepth);
return false;
}
addTrack(new FlicVideoTrack(stream, frameCount, width, height));
return true;
}
const Common::List<Common::Rect> *FlicDecoder::getDirtyRects() const {
const Track *track = getTrack(0);
if (track)
return ((const FlicVideoTrack *)track)->getDirtyRects();
return 0;
}
void FlicDecoder::clearDirtyRects() {
Track *track = getTrack(0);
if (track)
((FlicVideoTrack *)track)->clearDirtyRects();
}
void FlicDecoder::copyDirtyRectsToBuffer(uint8 *dst, uint pitch) {
Track *track = getTrack(0);
if (track)
((FlicVideoTrack *)track)->copyDirtyRectsToBuffer(dst, pitch);
}
FlicDecoder::FlicVideoTrack::FlicVideoTrack(Common::SeekableReadStream *stream, uint16 frameCount, uint16 width, uint16 height, bool skipHeader) : _palette(256) {
_fileStream = stream;
_frameCount = frameCount;
_surface = new Graphics::Surface();
_surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
_dirtyPalette = false;
_curFrame = -1;
_nextFrameStartTime = 0;
_atRingFrame = false;
if (!skipHeader)
readHeader();
}
FlicDecoder::FlicVideoTrack::~FlicVideoTrack() {
delete _fileStream;
_surface->free();
delete _surface;
}
void FlicDecoder::FlicVideoTrack::readHeader() {
_fileStream->readUint16LE(); // flags
// Note: The normal delay is a 32-bit integer (dword), whereas the overridden delay is a 16-bit integer (word)
// the frame delay is the FLIC "speed", in milliseconds.
_frameDelay = _startFrameDelay = _fileStream->readUint32LE();
_fileStream->seek(80);
_offsetFrame1 = _fileStream->readUint32LE();
_offsetFrame2 = _fileStream->readUint32LE();
if (_offsetFrame1 == 0)
_offsetFrame1 = 0x80; //length of FLIC header
// Seek to the first frame
_fileStream->seek(_offsetFrame1);
}
bool FlicDecoder::FlicVideoTrack::endOfTrack() const {
return getCurFrame() >= getFrameCount() - 1;
}
bool FlicDecoder::FlicVideoTrack::rewind() {
if (endOfTrack() && _fileStream->pos() < _fileStream->size() && _frameCount != 1)
_atRingFrame = true;
else
_fileStream->seek(_offsetFrame1);
_curFrame = -1;
_nextFrameStartTime = 0;
_frameDelay = _startFrameDelay;
return true;
}
uint16 FlicDecoder::FlicVideoTrack::getWidth() const {
return _surface->w;
}
uint16 FlicDecoder::FlicVideoTrack::getHeight() const {
return _surface->h;
}
Graphics::PixelFormat FlicDecoder::FlicVideoTrack::getPixelFormat() const {
return _surface->format;
}
#define FLI_SETPAL 4
#define FLI_SS2 7
#define FLI_BLACK 13
#define FLI_BRUN 15
#define FLI_COPY 16
#define PSTAMP 18
#define FRAME_TYPE 0xF1FA
#define FLC_FILE_HEADER 0xAF12
#define FLC_FILE_HEADER_SIZE 0x80
const Graphics::Surface *FlicDecoder::FlicVideoTrack::decodeNextFrame() {
// Read chunk
/*uint32 frameSize = */ _fileStream->readUint32LE();
uint16 frameType = _fileStream->readUint16LE();
switch (frameType) {
case FRAME_TYPE:
handleFrame();
break;
case FLC_FILE_HEADER:
// Skip 0x80 bytes of file header subtracting 6 bytes of header
_fileStream->skip(FLC_FILE_HEADER_SIZE - 6);
break;
default:
error("FlicDecoder::decodeFrame(): unknown main chunk type (type = 0x%02X)", frameType);
break;
}
_curFrame++;
_nextFrameStartTime += _frameDelay;
if (_atRingFrame) {
// If we decoded the ring frame, seek to the second frame
_atRingFrame = false;
_fileStream->seek(_offsetFrame2);
}
return _surface;
}
void FlicDecoder::FlicVideoTrack::handleFrame() {
uint16 chunkCount = _fileStream->readUint16LE();
// Note: The overridden delay is a 16-bit integer (word), whereas the normal delay is a 32-bit integer (dword)
// the frame delay is the FLIC "speed", in milliseconds.
uint16 newFrameDelay = _fileStream->readUint16LE(); // "speed", in milliseconds
if (newFrameDelay > 0)
_frameDelay = newFrameDelay;
_fileStream->readUint16LE(); // reserved, always 0
uint16 newWidth = _fileStream->readUint16LE();
uint16 newHeight = _fileStream->readUint16LE();
if ((newWidth != 0) || (newHeight != 0)) {
if (newWidth == 0)
newWidth = _surface->w;
if (newHeight == 0)
newHeight = _surface->h;
_surface->free();
delete _surface;
_surface = new Graphics::Surface();
_surface->create(newWidth, newHeight, Graphics::PixelFormat::createFormatCLUT8());
}
// Read subchunks
for (uint32 i = 0; i < chunkCount; ++i) {
uint32 frameSize = _fileStream->readUint32LE();
uint16 frameType = _fileStream->readUint16LE();
uint8 *data = new uint8[frameSize - 6];
_fileStream->read(data, frameSize - 6);
switch (frameType) {
case FLI_SETPAL:
unpackPalette(data);
_dirtyPalette = true;
break;
case FLI_SS2:
decodeDeltaFLC(data);
break;
case FLI_BLACK:
_surface->fillRect(Common::Rect(0, 0, getWidth(), getHeight()), 0);
_dirtyRects.clear();
_dirtyRects.push_back(Common::Rect(0, 0, getWidth(), getHeight()));
break;
case FLI_BRUN:
decodeByteRun(data);
break;
case FLI_COPY:
copyFrame(data);
break;
case PSTAMP:
/* PSTAMP - skip for now */
break;
default:
error("FlicDecoder::decodeNextFrame(): unknown subchunk type (type = 0x%02X)", frameType);
break;
}
delete[] data;
}
}
void FlicDecoder::FlicVideoTrack::copyDirtyRectsToBuffer(uint8 *dst, uint pitch) {
for (const auto &dirtyRect : _dirtyRects) {
for (int y = dirtyRect.top; y < dirtyRect.bottom; ++y) {
const int x = dirtyRect.left;
memcpy(dst + y * pitch + x, (byte *)_surface->getBasePtr(x, y), dirtyRect.right - x);
}
}
clearDirtyRects();
}
void FlicDecoder::FlicVideoTrack::copyFrame(uint8 *data) {
memcpy((byte *)_surface->getPixels(), data, getWidth() * getHeight());
// Redraw
_dirtyRects.clear();
_dirtyRects.push_back(Common::Rect(0, 0, getWidth(), getHeight()));
}
void FlicDecoder::FlicVideoTrack::decodeByteRun(uint8 *data) {
byte *ptr = (byte *)_surface->getPixels();
for (int i = 0; i < getHeight(); ++i) {
data++;
for (int j = 0; j < getWidth();) {
int count = (int8)*data++;
if (count > 0) {
memset(ptr, *data++, count);
} else {
count = -count;
memcpy(ptr, data, count);
data += count;
}
ptr += count;
j += count;
}
}
// Redraw
_dirtyRects.clear();
_dirtyRects.push_back(Common::Rect(0, 0, getWidth(), getHeight()));
}
#define OP_PACKETCOUNT 0
#define OP_UNDEFINED 1
#define OP_LASTPIXEL 2
#define OP_LINESKIPCOUNT 3
void FlicDecoder::FlicVideoTrack::decodeDeltaFLC(uint8 *data) {
uint16 linesInChunk = READ_LE_UINT16(data); data += 2;
uint16 currentLine = 0;
uint16 packetCount = 0;
while (linesInChunk--) {
uint16 opcode;
// First process all the opcodes.
do {
opcode = READ_LE_UINT16(data); data += 2;
switch ((opcode >> 14) & 3) {
case OP_PACKETCOUNT:
packetCount = opcode;
break;
case OP_UNDEFINED:
default:
break;
case OP_LASTPIXEL:
*((byte *)_surface->getBasePtr(getWidth() - 1, currentLine)) = (opcode & 0xFF);
_dirtyRects.push_back(Common::Rect(getWidth() - 1, currentLine, getWidth(), currentLine + 1));
break;
case OP_LINESKIPCOUNT:
currentLine += -(int16)opcode;
break;
}
} while (((opcode >> 14) & 3) != OP_PACKETCOUNT);
uint16 column = 0;
// Now interpret the RLE data
while (packetCount--) {
column += *data++;
int rleCount = (int8)*data++;
if (rleCount > 0) {
memcpy((byte *)_surface->getBasePtr(column, currentLine), data, rleCount * 2);
data += rleCount * 2;
_dirtyRects.push_back(Common::Rect(column, currentLine, column + rleCount * 2, currentLine + 1));
} else if (rleCount < 0) {
rleCount = -rleCount;
uint16 dataWord = READ_UINT16(data); data += 2;
for (int i = 0; i < rleCount; ++i) {
WRITE_UINT16((byte *)_surface->getBasePtr(column + i * 2, currentLine), dataWord);
}
_dirtyRects.push_back(Common::Rect(column, currentLine, column + rleCount * 2, currentLine + 1));
}
column += rleCount * 2;
}
currentLine++;
}
}
void FlicDecoder::FlicVideoTrack::unpackPalette(uint8 *data) {
uint16 numPackets = READ_LE_UINT16(data); data += 2;
if (0 == READ_LE_UINT16(data)) { //special case
data += 2;
for (int i = 0; i < 256; ++i) {
byte r = data[i * 3];
byte g = data[i * 3 + 1];
byte b = data[i * 3 + 2];
_palette.set(i, r, g, b);
}
} else {
uint8 palPos = 0;
while (numPackets--) {
palPos += *data++;
uint8 change = *data++;
for (int i = 0; i < change; ++i) {
byte r = data[i * 3];
byte g = data[i * 3 + 1];
byte b = data[i * 3 + 2];
_palette.set(palPos + i, r, g, b);
}
palPos += change;
data += (change * 3);
}
}
}
} // End of namespace Video

116
video/flic_decoder.h Normal file
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/>.
*
*/
#ifndef VIDEO_FLICDECODER_H
#define VIDEO_FLICDECODER_H
#include "video/video_decoder.h"
#include "common/list.h"
#include "common/rect.h"
#include "graphics/palette.h"
namespace Common {
class SeekableReadStream;
}
namespace Graphics {
struct PixelFormat;
struct Surface;
}
namespace Video {
/**
* Decoder for FLIC videos.
*
* Video decoder used in engines:
* - chewy
* - petka
* - prince
* - tucker
*/
class FlicDecoder : public VideoDecoder {
public:
FlicDecoder();
virtual ~FlicDecoder();
virtual bool loadStream(Common::SeekableReadStream *stream);
const Common::List<Common::Rect> *getDirtyRects() const;
void clearDirtyRects();
void copyDirtyRectsToBuffer(uint8 *dst, uint pitch);
protected:
class FlicVideoTrack : public VideoTrack {
public:
FlicVideoTrack(Common::SeekableReadStream *stream, uint16 frameCount, uint16 width, uint16 height, bool skipHeader = false);
~FlicVideoTrack();
virtual void readHeader();
bool endOfTrack() const override;
virtual bool isRewindable() const override{ return true; }
virtual bool rewind() override;
uint16 getWidth() const override;
uint16 getHeight() const override;
Graphics::PixelFormat getPixelFormat() const override;
int getCurFrame() const override { return _curFrame; }
int getCurFrameDelay() const override { return _frameDelay; }
int getFrameCount() const override { return _frameCount; }
uint32 getNextFrameStartTime() const override { return _nextFrameStartTime; }
virtual const Graphics::Surface *decodeNextFrame() override;
virtual void handleFrame();
const byte *getPalette() const override{ _dirtyPalette = false; return _palette.data(); }
bool hasDirtyPalette() const override { return _dirtyPalette; }
const Common::List<Common::Rect> *getDirtyRects() const { return &_dirtyRects; }
void clearDirtyRects() { _dirtyRects.clear(); }
void copyDirtyRectsToBuffer(uint8 *dst, uint pitch);
protected:
Common::SeekableReadStream *_fileStream;
Graphics::Surface *_surface;
int _curFrame;
bool _atRingFrame;
uint32 _offsetFrame1;
uint32 _offsetFrame2;
Graphics::Palette _palette;
mutable bool _dirtyPalette;
uint32 _frameCount;
uint32 _frameDelay, _startFrameDelay;
uint32 _nextFrameStartTime;
Common::List<Common::Rect> _dirtyRects;
void copyFrame(uint8 *data);
void decodeByteRun(uint8 *data);
void decodeDeltaFLC(uint8 *data);
void unpackPalette(uint8 *mem);
};
};
} // End of namespace Video
#endif

1215
video/hnm_decoder.cpp Normal file

File diff suppressed because it is too large Load Diff

240
video/hnm_decoder.h Normal file
View File

@@ -0,0 +1,240 @@
/* 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/scummsys.h" // for USE_HNM
#ifdef USE_HNM
#ifndef VIDEO_HNM_DECODER_H
#define VIDEO_HNM_DECODER_H
#include "audio/audiostream.h"
#include "common/rational.h"
#include "graphics/palette.h"
#include "graphics/surface.h"
#include "video/video_decoder.h"
namespace Audio {
class APCStream;
}
namespace Common {
class SeekableReadStream;
}
namespace Image {
class HNM6Decoder;
}
namespace Video {
/**
* Decoder for HNM videos.
*
* Video decoder used in engines:
* - cryo
* - cryomni3d
*/
class HNMDecoder : public VideoDecoder {
public:
HNMDecoder(const Graphics::PixelFormat &format, bool loop = false, byte *initialPalette = nullptr);
~HNMDecoder() override;
bool loadStream(Common::SeekableReadStream *stream) override;
void readNextPacket() override;
void close() override;
void setRegularFrameDelay(uint32 regularFrameDelay) { _regularFrameDelayMs = regularFrameDelay; }
private:
class HNMVideoTrack : public VideoTrack {
public:
HNMVideoTrack(uint32 frameCount, uint32 regularFrameDelayMs, uint32 audioSampleRate);
// When _frameCount is 0, it means we are looping
bool endOfTrack() const override { return (_frameCount == 0) ? false : VideoTrack::endOfTrack(); }
int getCurFrame() const override { return _curFrame; }
int getFrameCount() const override { return _frameCount; }
uint32 getNextFrameStartTime() const override { return _nextFrameStartTime.msecs(); }
void restart() { _lastFrameDelaySamps = 0; }
virtual void newFrame(uint32 frameDelay) = 0;
virtual void decodeChunk(byte *data, uint32 size,
uint16 chunkType, uint16 flags) = 0;
protected:
uint32 _regularFrameDelayMs;
uint32 _lastFrameDelaySamps;
Audio::Timestamp _nextFrameStartTime;
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.data(); }
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,
const byte *initialPalette = nullptr);
~HNM45VideoTrack() override;
/** Decode a video chunk. */
void decodePalette(byte *data, uint32 size);
Graphics::Surface _surface;
Graphics::Palette _palette;
mutable bool _dirtyPalette;
byte *_frameBufferC;
byte *_frameBufferP;
};
class HNM4VideoTrack : public HNM45VideoTrack {
public:
HNM4VideoTrack(uint32 width, uint32 height, uint32 frameSize, uint32 frameCount,
uint32 regularFrameDelayMs, uint32 audioSampleRate,
const byte *initialPalette = nullptr);
~HNM4VideoTrack() override;
/** Decode a video chunk. */
void decodeChunk(byte *data, uint32 size,
uint16 chunkType, uint16 flags) override;
protected:
/* Really decode */
void decodeInterframe(byte *data, uint32 size);
void decodeInterframeA(byte *data, uint32 size);
void decodeIntraframe(byte *data, uint32 size);
void presentFrame(uint16 flags);
byte *_frameBufferF;
};
class HNM5VideoTrack : public HNM45VideoTrack {
public:
HNM5VideoTrack(uint32 width, uint32 height, uint32 frameSize, uint32 frameCount,
uint32 regularFrameDelayMs, uint32 audioSampleRate,
const byte *initialPalette = nullptr) :
HNM45VideoTrack(width, height, frameSize, frameCount, regularFrameDelayMs, audioSampleRate,
initialPalette) {}
/** Decode a video chunk. */
void decodeChunk(byte *data, uint32 size,
uint16 chunkType, uint16 flags) override;
protected:
/** Really decode */
void decodeFrame(byte *data, uint32 size);
};
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;
bool setOutputPixelFormat(const Graphics::PixelFormat &format) 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(uint16 chunkType, byte *data, uint32 size) override;
protected:
Audio::AudioStream *getAudioStream() const override { return _audioStream; }
private:
Audio::QueuingAudioStream *_audioStream;
bool _gotLUT;
uint16 _lut[256];
uint16 _lastSampleL;
uint16 _lastSampleR;
uint _sampleRate;
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;
HNMAudioTrack *_audioTrack;
Common::SeekableReadStream *_stream;
bool _alignedChunks;
byte *_dataBuffer;
uint32 _dataBufferAlloc;
};
} // End of namespace Video
#endif
#endif

4
video/mkv/AUTHORS.TXT Normal file
View File

@@ -0,0 +1,4 @@
# Names should be added to this file like so:
# Name or Organization <email address>
Google Inc.

30
video/mkv/LICENSE.TXT Normal file
View File

@@ -0,0 +1,30 @@
Copyright (c) 2010, Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Google nor the names of its contributors may
be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

23
video/mkv/PATENTS.TXT Normal file
View File

@@ -0,0 +1,23 @@
Additional IP Rights Grant (Patents)
------------------------------------
"These implementations" means the copyrightable works that implement the WebM
codecs distributed by Google as part of the WebM Project.
Google hereby grants to you a perpetual, worldwide, non-exclusive, no-charge,
royalty-free, irrevocable (except as stated in this section) patent license to
make, have made, use, offer to sell, sell, import, transfer, and otherwise
run, modify and propagate the contents of these implementations of WebM, where
such license applies only to those patent claims, both currently owned by
Google and acquired in the future, licensable by Google that are necessarily
infringed by these implementations of WebM. This grant does not include claims
that would be infringed only as a consequence of further modification of these
implementations. If you or your agent or exclusive licensee institute or order
or agree to the institution of patent litigation or any other patent
enforcement activity against any entity (including a cross-claim or
counterclaim in a lawsuit) alleging that any of these implementations of WebM
or any code incorporated within any of these implementations of WebM
constitute direct or contributory patent infringement, or inducement of
patent infringement, then any patent rights granted to you under this License
for these implementations of WebM shall terminate as of the date such
litigation is filed.

143
video/mkv/README.libwebm Normal file
View File

@@ -0,0 +1,143 @@
Building Libwebm
To build libwebm you must first create project files. To do this run cmake
and pass it the path to your libwebm repo.
Makefile.unix can be used as a fallback on systems that cmake does not
support.
CMake Basics
To generate project/make files for the default toolchain on your system simply
run cmake with the path to the libwebm repo:
$ cmake path/to/libwebm
On Windows the above command will produce Visual Studio project files for the
newest Visual Studio detected on the system. On Mac OS X and Linux systems, the
above command will produce a makefile.
To control what types of projects are generated the -G parameter is added to
the cmake command line. This argument must be followed by the name of a
generator. Running cmake with the --help argument will list the available
generators for your system.
On Mac OS X you would run the following command to generate Xcode projects:
$ cmake path/to/libwebm -G Xcode
On a Windows box you would run the following command to generate Visual Studio
2013 projects:
$ cmake path/to/libwebm -G "Visual Studio 12"
To generate 64-bit Windows Visual Studio 2013 projects:
$ cmake path/to/libwebm "Visual Studio 12 Win64"
CMake Makefiles: Debugging and Optimization
Unlike Visual Studio and Xcode projects, the build configuration for make builds
is controlled when you run cmake. The following examples demonstrate various
build configurations.
Omitting the build type produces makefiles that use build flags containing
neither optimization nor debug flags:
$ cmake path/to/libwebm
A makefile using release (optimized) flags is produced like this:
$ cmake path/to/libwebm -DCMAKE_BUILD_TYPE=release
A release build with debug info can be produced as well:
$ cmake path/to/libwebm -DCMAKE_BUILD_TYPE=relwithdebinfo
And your standard debug build will be produced using:
$ cmake path/to/libwebm -DCMAKE_BUILD_TYPE=debug
Tests
To enable libwebm tests add -DENABLE_TESTS=ON CMake generation command line. For
example:
$ cmake path/to/libwebm -G Xcode -DENABLE_TESTS=ON
Libwebm tests depend on googletest. By default googletest is expected to be a
sibling directory of the Libwebm repository. To change that, update your CMake
command to be similar to the following:
$ cmake path/to/libwebm -G Xcode -DENABLE_TESTS=ON \
-DGTEST_SRC_DIR=/path/to/googletest
The tests rely upon the LIBWEBM_TEST_DATA_PATH environment variable to locate
test input. The following example demonstrates running the muxer tests from the
build directory:
$ LIBWEBM_TEST_DATA_PATH=path/to/libwebm/testing/testdata ./mkvmuxer_tests
Note: Libwebm Googletest integration was built with googletest from
https://github.com/google/googletest.git at git revision
ddb8012eb48bc203aa93dcc2b22c1db516302b29.
CMake Include-what-you-use integration
Include-what-you-use is an analysis tool that helps ensure libwebm includes the
C/C++ header files actually in use. To enable the integration support
ENABLE_IWYU must be turned on at cmake run time:
$ cmake path/to/libwebm -G "Unix Makefiles" -DENABLE_IWYU=ON
This adds the iwyu target to the build. To run include-what-you-use:
$ make iwyu
The following requirements must be met for ENABLE_IWYU to enable the iwyu
target:
1. include-what-you-use and iwyu_tool.py must be in your PATH.
2. A python interpreter must be on the system and available to CMake.
The values of the following variables are used to determine if the requirements
have been met. Values to the right of the equals sign are what a successful run
might look like:
iwyu_path=/path/to/iwyu_tool.py
iwyu_tool_path=/path/to/include-what-you-use
PYTHONINTERP_FOUND=TRUE
An empty PYTHONINTERP_FOUND, or iwyu_path/iwyu_tool_path suffixed with NOTFOUND
are failures.
For Include-what-you-use setup instructions, see:
https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/InstructionsForUsers.md
If, when building the iwyu target, compile errors reporting failures loading
standard include files occur, one solution can be found here:
https://github.com/include-what-you-use/include-what-you-use/issues/100
CMake cross compile
To cross compile libwebm for Windows using mingw-w64 run cmake with the
following arguments:
$ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/libwebm/build/mingw-w64_toolchain.cmake \
path/to/libwebm
Note1: As of this writing googletest will not build via mingw-w64 without
disabling pthreads.
googletest hash: d225acc90bc3a8c420a9bcd1f033033c1ccd7fe0
To build with tests when using mingw-w64 use the following arguments when
running CMake:
$ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/libwebm/build/mingw-w64_toolchain.cmake \
-DENABLE_TESTS=ON -Dgtest_disable_pthreads=ON path/to/libwebm
Note2: i686-w64-mingw32 is the default compiler. This can be controlled using
the MINGW_PREFIX variable:
$ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/libwebm/build/mingw-w64_toolchain.cmake \
-DMINGW_PREFIX=x86_64-w64-mingw32 path/to/libwebm

8062
video/mkv/mkvparser.cpp Normal file

File diff suppressed because it is too large Load Diff

1147
video/mkv/mkvparser.h Normal file

File diff suppressed because it is too large Load Diff

193
video/mkv/webmids.h Normal file
View File

@@ -0,0 +1,193 @@
// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree.
#ifndef COMMON_WEBMIDS_H_
#define COMMON_WEBMIDS_H_
namespace libwebm {
enum MkvId {
kMkvEBML = 0x1A45DFA3,
kMkvEBMLVersion = 0x4286,
kMkvEBMLReadVersion = 0x42F7,
kMkvEBMLMaxIDLength = 0x42F2,
kMkvEBMLMaxSizeLength = 0x42F3,
kMkvDocType = 0x4282,
kMkvDocTypeVersion = 0x4287,
kMkvDocTypeReadVersion = 0x4285,
kMkvVoid = 0xEC,
kMkvSignatureSlot = 0x1B538667,
kMkvSignatureAlgo = 0x7E8A,
kMkvSignatureHash = 0x7E9A,
kMkvSignaturePublicKey = 0x7EA5,
kMkvSignature = 0x7EB5,
kMkvSignatureElements = 0x7E5B,
kMkvSignatureElementList = 0x7E7B,
kMkvSignedElement = 0x6532,
// segment
kMkvSegment = 0x18538067,
// Meta Seek Information
kMkvSeekHead = 0x114D9B74,
kMkvSeek = 0x4DBB,
kMkvSeekID = 0x53AB,
kMkvSeekPosition = 0x53AC,
// Segment Information
kMkvInfo = 0x1549A966,
kMkvTimecodeScale = 0x2AD7B1,
kMkvDuration = 0x4489,
kMkvDateUTC = 0x4461,
kMkvTitle = 0x7BA9,
kMkvMuxingApp = 0x4D80,
kMkvWritingApp = 0x5741,
// Cluster
kMkvCluster = 0x1F43B675,
kMkvTimecode = 0xE7,
kMkvPrevSize = 0xAB,
kMkvBlockGroup = 0xA0,
kMkvBlock = 0xA1,
kMkvBlockDuration = 0x9B,
kMkvReferenceBlock = 0xFB,
kMkvLaceNumber = 0xCC,
kMkvSimpleBlock = 0xA3,
kMkvBlockAdditions = 0x75A1,
kMkvBlockMore = 0xA6,
kMkvBlockAddID = 0xEE,
kMkvBlockAdditional = 0xA5,
kMkvDiscardPadding = 0x75A2,
// Track
kMkvTracks = 0x1654AE6B,
kMkvTrackEntry = 0xAE,
kMkvTrackNumber = 0xD7,
kMkvTrackUID = 0x73C5,
kMkvTrackType = 0x83,
kMkvFlagEnabled = 0xB9,
kMkvFlagDefault = 0x88,
kMkvFlagForced = 0x55AA,
kMkvFlagLacing = 0x9C,
kMkvDefaultDuration = 0x23E383,
kMkvMaxBlockAdditionID = 0x55EE,
kMkvName = 0x536E,
kMkvLanguage = 0x22B59C,
kMkvCodecID = 0x86,
kMkvCodecPrivate = 0x63A2,
kMkvCodecName = 0x258688,
kMkvCodecDelay = 0x56AA,
kMkvSeekPreRoll = 0x56BB,
// video
kMkvVideo = 0xE0,
kMkvFlagInterlaced = 0x9A,
kMkvStereoMode = 0x53B8,
kMkvAlphaMode = 0x53C0,
kMkvPixelWidth = 0xB0,
kMkvPixelHeight = 0xBA,
kMkvPixelCropBottom = 0x54AA,
kMkvPixelCropTop = 0x54BB,
kMkvPixelCropLeft = 0x54CC,
kMkvPixelCropRight = 0x54DD,
kMkvDisplayWidth = 0x54B0,
kMkvDisplayHeight = 0x54BA,
kMkvDisplayUnit = 0x54B2,
kMkvAspectRatioType = 0x54B3,
kMkvColourSpace = 0x2EB524,
kMkvFrameRate = 0x2383E3,
// end video
// colour
kMkvColour = 0x55B0,
kMkvMatrixCoefficients = 0x55B1,
kMkvBitsPerChannel = 0x55B2,
kMkvChromaSubsamplingHorz = 0x55B3,
kMkvChromaSubsamplingVert = 0x55B4,
kMkvCbSubsamplingHorz = 0x55B5,
kMkvCbSubsamplingVert = 0x55B6,
kMkvChromaSitingHorz = 0x55B7,
kMkvChromaSitingVert = 0x55B8,
kMkvRange = 0x55B9,
kMkvTransferCharacteristics = 0x55BA,
kMkvPrimaries = 0x55BB,
kMkvMaxCLL = 0x55BC,
kMkvMaxFALL = 0x55BD,
// mastering metadata
kMkvMasteringMetadata = 0x55D0,
kMkvPrimaryRChromaticityX = 0x55D1,
kMkvPrimaryRChromaticityY = 0x55D2,
kMkvPrimaryGChromaticityX = 0x55D3,
kMkvPrimaryGChromaticityY = 0x55D4,
kMkvPrimaryBChromaticityX = 0x55D5,
kMkvPrimaryBChromaticityY = 0x55D6,
kMkvWhitePointChromaticityX = 0x55D7,
kMkvWhitePointChromaticityY = 0x55D8,
kMkvLuminanceMax = 0x55D9,
kMkvLuminanceMin = 0x55DA,
// end mastering metadata
// end colour
// projection
kMkvProjection = 0x7670,
kMkvProjectionType = 0x7671,
kMkvProjectionPrivate = 0x7672,
kMkvProjectionPoseYaw = 0x7673,
kMkvProjectionPosePitch = 0x7674,
kMkvProjectionPoseRoll = 0x7675,
// end projection
// audio
kMkvAudio = 0xE1,
kMkvSamplingFrequency = 0xB5,
kMkvOutputSamplingFrequency = 0x78B5,
kMkvChannels = 0x9F,
kMkvBitDepth = 0x6264,
// end audio
// ContentEncodings
kMkvContentEncodings = 0x6D80,
kMkvContentEncoding = 0x6240,
kMkvContentEncodingOrder = 0x5031,
kMkvContentEncodingScope = 0x5032,
kMkvContentEncodingType = 0x5033,
kMkvContentCompression = 0x5034,
kMkvContentCompAlgo = 0x4254,
kMkvContentCompSettings = 0x4255,
kMkvContentEncryption = 0x5035,
kMkvContentEncAlgo = 0x47E1,
kMkvContentEncKeyID = 0x47E2,
kMkvContentSignature = 0x47E3,
kMkvContentSigKeyID = 0x47E4,
kMkvContentSigAlgo = 0x47E5,
kMkvContentSigHashAlgo = 0x47E6,
kMkvContentEncAESSettings = 0x47E7,
kMkvAESSettingsCipherMode = 0x47E8,
kMkvAESSettingsCipherInitData = 0x47E9,
// end ContentEncodings
// Cueing Data
kMkvCues = 0x1C53BB6B,
kMkvCuePoint = 0xBB,
kMkvCueTime = 0xB3,
kMkvCueTrackPositions = 0xB7,
kMkvCueTrack = 0xF7,
kMkvCueClusterPosition = 0xF1,
kMkvCueBlockNumber = 0x5378,
// Chapters
kMkvChapters = 0x1043A770,
kMkvEditionEntry = 0x45B9,
kMkvChapterAtom = 0xB6,
kMkvChapterUID = 0x73C4,
kMkvChapterStringUID = 0x5654,
kMkvChapterTimeStart = 0x91,
kMkvChapterTimeEnd = 0x92,
kMkvChapterDisplay = 0x80,
kMkvChapString = 0x85,
kMkvChapLanguage = 0x437C,
kMkvChapCountry = 0x437E,
// Tags
kMkvTags = 0x1254C367,
kMkvTag = 0x7373,
kMkvSimpleTag = 0x67C8,
kMkvTagName = 0x45A3,
kMkvTagString = 0x4487
};
} // namespace libwebm
#endif // COMMON_WEBMIDS_H_

547
video/mkv_decoder.cpp Normal file
View File

@@ -0,0 +1,547 @@
/* 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 "video/mkv_decoder.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
#include "common/debug.h"
#include "common/stream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "graphics/pixelformat.h"
#include "graphics/yuv_to_rgb.h"
#include "image/codecs/codec.h"
#include "video/mkv/mkvparser.h"
namespace mkvparser {
class MkvReader : public mkvparser::IMkvReader {
public:
MkvReader() { _stream = nullptr; }
MkvReader(Common::SeekableReadStream *stream);
virtual ~MkvReader() { Close(); }
int Open(Common::SeekableReadStream *stream);
void Close();
virtual int Read(long long position, long length, unsigned char *buffer);
virtual int Length(long long *total, long long *available);
private:
Common::SeekableReadStream *_stream;
};
MkvReader::MkvReader(Common::SeekableReadStream *stream) {
_stream = stream;
}
int MkvReader::Open(Common::SeekableReadStream *stream) {
_stream = stream;
return 0;
}
void MkvReader::Close() {
_stream = nullptr;
}
int MkvReader::Read(long long position, long length, unsigned char *buffer) {
if (!_stream)
return -1;
if (position > _stream->size() || position < 0)
return -1;
if (length <= 0)
return -1;
_stream->seek(position);
if (_stream->read(buffer, length) < (uint32)length)
return -1;
return 0;
}
int MkvReader::Length(long long *total, long long *available) {
if (!_stream)
return -1;
if (total)
*total = _stream->size();
if (available)
*available = _stream->size();
return 0;
}
} // end of namespace mkvparser
namespace Video {
MKVDecoder::MKVDecoder() {
_fileStream = 0;
_videoTrack = 0;
_audioTrack = 0;
_hasVideo = _hasAudio = false;
_reader = nullptr;
}
MKVDecoder::~MKVDecoder() {
close();
}
static uint64 xiph_lace_value(byte **np) {
uint64 lace;
uint64 value;
byte *p = *np;
lace = *p++;
value = lace;
while (lace == 255) {
lace = *p++;
value += lace;
}
*np = p;
return value;
}
bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
close();
warning("MKVDecoder::loadStream()");
_reader = new mkvparser::MkvReader(stream);
long long pos = 0;
mkvparser::EBMLHeader ebmlHeader;
ebmlHeader.Parse(_reader, pos);
long long ret = mkvparser::Segment::CreateInstance(_reader, pos, _pSegment);
if (ret) {
error("MKVDecoder::loadStream(): Segment::CreateInstance() failed (%lld).", ret);
}
ret = _pSegment->Load();
if (ret) {
error("MKVDecoder::loadStream(): Segment::Load() failed (%lld).", ret);
}
_pTracks = _pSegment->GetTracks();
uint32 i = 0;
const unsigned long j = _pTracks->GetTracksCount();
debugC(1, kDebugLevelGVideo, "Number of tracks: %lu", j);
enum {VIDEO_TRACK = 1, AUDIO_TRACK = 2};
_vTrack = -1;
_aTrack = -1;
while (i != j) {
const mkvparser::Track *const pTrack = _pTracks->GetTrackByIndex(i++);
if (pTrack == NULL)
continue;
const long long trackType = pTrack->GetType();
if (trackType == mkvparser::Track::kVideo && _vTrack < 0) {
_vTrack = pTrack->GetNumber();
_videoTrack = new VPXVideoTrack(pTrack);
addTrack(_videoTrack);
}
if (trackType == mkvparser::Track::kAudio && _aTrack < 0) {
_aTrack = pTrack->GetNumber();
const mkvparser::AudioTrack *const pAudioTrack = static_cast<const mkvparser::AudioTrack *>(pTrack);
size_t audioHeaderSize;
byte *audioHeader = const_cast<byte *>(pAudioTrack->GetCodecPrivate(audioHeaderSize));
if (audioHeaderSize < 1) {
warning("Strange audio track in movie.");
_aTrack = -1;
continue;
}
byte *p = audioHeader;
uint count = *p++ + 1;
if (count != 3) {
warning("Strange audio track in movie.");
_aTrack = -1;
continue;
}
_audioTrack = new VorbisAudioTrack(pTrack);
addTrack(_audioTrack);
}
}
if (_vTrack < 0)
error("Movie error: No video in movie file.");
if (_aTrack < 0)
error("Movie error: No sound found.");
const unsigned long long clusterCount = _pSegment->GetCount();
if (clusterCount == 0) {
error("Movie error: Segment has no clusters.\n");
}
_frame = new byte[256 * 1024];
if (!_frame)
return false;
_cluster = _pSegment->GetFirst();
if (_cluster->GetFirst(_pBlockEntry))
error("_cluster::GetFirst() failed");
if ((_pBlockEntry == NULL) || _pBlockEntry->EOS()) {
_cluster = _pSegment->GetNext(_cluster);
if ((_cluster == NULL) || _cluster->EOS()) {
error("Error: No movie found in the movie file.");
}
if (_cluster->GetFirst(_pBlockEntry))
error("_cluster::GetFirst() failed");
}
_pBlock = _pBlockEntry->GetBlock();
_trackNum = _pBlock->GetTrackNumber();
_frameCount = _pBlock->GetFrameCount();
_fileStream = stream;
return true;
}
void MKVDecoder::close() {
VideoDecoder::close();
delete _fileStream;
_fileStream = nullptr;
delete _reader;
_reader = nullptr;
}
void MKVDecoder::readNextPacket() {
// First, let's get our frame
if (_cluster == nullptr || _cluster->EOS()) {
_videoTrack->setEndOfVideo();
if (!_audioTrack->hasAudio())
_audioTrack->setEndOfAudio();
return;
}
// ensure we have enough buffers in the stream
while (_audioTrack->needsAudio()) {
if (_frameCounter >= _frameCount) {
_cluster->GetNext(_pBlockEntry, _pBlockEntry);
if ((_pBlockEntry == NULL) || _pBlockEntry->EOS()) {
_cluster = _pSegment->GetNext(_cluster);
if ((_cluster == NULL) || _cluster->EOS()) {
_videoTrack->setEndOfVideo();
_audioTrack->setEndOfAudio();
return;
}
int ret = _cluster->GetFirst(_pBlockEntry);
if (ret < 0)
error("MKVDecoder::readNextPacket(): GetFirst() failed");
}
_pBlock = _pBlockEntry->GetBlock();
_trackNum = _pBlock->GetTrackNumber();
_frameCount = _pBlock->GetFrameCount();
_frameCounter = 0;
}
const mkvparser::Block::Frame &theFrame = _pBlock->GetFrame(_frameCounter);
const uint32 size = theFrame.len;
if (size > sizeof(_frame)) {
if (_frame)
delete[] _frame;
_frame = new unsigned char[size];
if (!_frame)
return;
}
if (_trackNum == _vTrack) {
theFrame.Read(_reader, _frame);
_videoTrack->decodeFrame(_frame, size);
} else if (_trackNum == _aTrack) {
if (size > 0) {
theFrame.Read(_reader, _frame);
queueAudio(size);
}
} else {
warning("Unprocessed track %lld", _trackNum);
}
++_frameCounter;
}
}
MKVDecoder::VPXVideoTrack::VPXVideoTrack(const mkvparser::Track *const pTrack) {
const mkvparser::VideoTrack *const pVideoTrack = static_cast<const mkvparser::VideoTrack *>(pTrack);
_width = pVideoTrack->GetWidth();
_height = pVideoTrack->GetHeight();
_pixelFormat = Image::Codec::getDefaultYUVFormat();
debugC(1, kDebugLevelGVideo, "VideoTrack: %d x %d", _width, _height);
_endOfVideo = false;
_nextFrameStartTime = 0.0;
_curFrame = -1;
_codec = new vpx_codec_ctx_t;
/* Initialize video codec */
if (vpx_codec_dec_init(_codec, &vpx_codec_vp8_dx_algo, NULL, 0))
error("Failed to initialize decoder for movie.");
}
MKVDecoder::VPXVideoTrack::~VPXVideoTrack() {
// The last frame is not freed in decodeNextFrame(), clear it hear instead.
_surface.free();
delete _codec;
}
bool MKVDecoder::VPXVideoTrack::endOfTrack() const {
if (_endOfVideo && _displayQueue.size())
return false;
return _endOfVideo;
}
const Graphics::Surface *MKVDecoder::VPXVideoTrack::decodeNextFrame() {
if (_displayQueue.size()) {
if (_surface.getPixels())
_surface.free();
_surface = _displayQueue.pop();
}
return &_surface;
}
bool MKVDecoder::VPXVideoTrack::decodeFrame(byte *frame, long size) {
//warning("In within decodeFrame");
/* Decode the frame */
if (vpx_codec_decode(_codec, frame, size, NULL, 0))
error("Failed to decode frame");
// Let's decode an image frame!
vpx_codec_iter_t iter = NULL;
vpx_image_t *img;
Graphics::Surface tmp;
tmp.create(getWidth(), getHeight(), getPixelFormat());
/* Get frame data */
while ((img = vpx_codec_get_frame(_codec, &iter))) {
if (img->fmt != VPX_IMG_FMT_I420)
error("Movie error. The movie is not in I420 colour format, which is the only one I can hanlde at the moment.");
YUVToRGBMan.convert420(&tmp, Graphics::YUVToRGBManager::kScaleITU, img->planes[0], img->planes[1], img->planes[2], img->d_w, img->d_h, img->stride[0], img->stride[1]);
_displayQueue.push(tmp);
}
return false;
}
MKVDecoder::VorbisAudioTrack::VorbisAudioTrack(const mkvparser::Track *const pTrack) :
AudioTrack(Audio::Mixer::kPlainSoundType) {
vorbis_comment vorbisComment;
const mkvparser::AudioTrack *const pAudioTrack = static_cast<const mkvparser::AudioTrack *>(pTrack);
const long long audioChannels = pAudioTrack->GetChannels();
const long long audioBitDepth = pAudioTrack->GetBitDepth();
const double audioSampleRate = pAudioTrack->GetSamplingRate();
debugC(1, kDebugLevelGVideo, "audioChannels %lld audioBitDepth %lld audioSamplerate %f", audioChannels, audioBitDepth, audioSampleRate);
size_t audioHeaderSize;
byte *audioHeader = const_cast<byte *>(pAudioTrack->GetCodecPrivate(audioHeaderSize));
byte *p = audioHeader;
uint count = *p++ + 1;
uint64 sizes[3], total;
int l = 0;
total = 0;
while (--count) {
sizes[l] = xiph_lace_value(&p);
total += sizes[l];
l += 1;
}
sizes[l] = audioHeaderSize - total - (p - audioHeader);
// initialize vorbis
vorbis_info_init(&_vorbisInfo);
vorbis_comment_init(&vorbisComment);
memset(&_vorbisDSP, 0, sizeof(_vorbisDSP));
memset(&_vorbisBlock, 0, sizeof(_vorbisBlock));
oggPacket.e_o_s = false;
oggPacket.granulepos = 0;
oggPacket.packetno = 0;
int r;
for (int s = 0; s < 3; s++) {
oggPacket.packet = p;
oggPacket.bytes = sizes[s];
oggPacket.b_o_s = oggPacket.packetno == 0;
r = vorbis_synthesis_headerin(&_vorbisInfo, &vorbisComment, &oggPacket);
if (r)
warning("vorbis_synthesis_headerin failed, error: %d", r);
oggPacket.packetno++;
p += sizes[s];
}
r = vorbis_synthesis_init(&_vorbisDSP, &_vorbisInfo);
if(r)
error("vorbis_synthesis_init, error: %d", r);
r = vorbis_block_init(&_vorbisDSP, &_vorbisBlock);
if(r)
error("vorbis_block_init, error: %d", r);
_audStream = Audio::makeQueuingAudioStream(_vorbisInfo.rate, _vorbisInfo.channels != 1);
_endOfAudio = false;
}
MKVDecoder::VorbisAudioTrack::~VorbisAudioTrack() {
vorbis_dsp_clear(&_vorbisDSP);
vorbis_block_clear(&_vorbisBlock);
delete _audStream;
}
Audio::AudioStream *MKVDecoder::VorbisAudioTrack::getAudioStream() const {
return _audStream;
}
#ifndef USE_TREMOR
static double rint(double v) {
return floor(v + 0.5);
}
#endif
bool MKVDecoder::VorbisAudioTrack::decodeSamples(byte *frame, long size) {
#ifdef USE_TREMOR
ogg_int32_t **pcm;
#else
float **pcm;
#endif
int32 numSamples = vorbis_synthesis_pcmout(&_vorbisDSP, &pcm);
if (numSamples > 0) {
int32 channels = _vorbisInfo.channels;
long bytespersample = _vorbisInfo.channels * 2;
char *buffer = (char*)malloc(bytespersample * numSamples * sizeof(char));
if (!buffer)
error("MKVDecoder::readNextPacket(): buffer allocation failed");
for (int32 i = 0; i < channels; i++) { /* It's faster in this order */
#ifdef USE_TREMOR
ogg_int32_t *src = pcm[i];
#else
float *src = pcm[i];
#endif
short *dest = ((short *)buffer) + i;
for (int32 j = 0; j < numSamples; j++) {
#ifdef USE_TREMOR
int val = (int)(src[j] >> 9);
#else
int val = rint(src[j] * 32768.f);
#endif
val = CLIP(val, -32768, 32767);
*dest = (short)val;
dest += channels;
}
}
byte flags = Audio::FLAG_16BITS;
if (_audStream->isStereo())
flags |= Audio::FLAG_STEREO;
#ifdef SCUMM_LITTLE_ENDIAN
flags |= Audio::FLAG_LITTLE_ENDIAN;
#endif
int64 audioBufferLen = bytespersample * numSamples;
_audStream->queueBuffer((byte *)buffer, audioBufferLen, DisposeAfterUse::YES, flags);
vorbis_synthesis_read(&_vorbisDSP, numSamples);
return true;
}
return false;
}
bool MKVDecoder::VorbisAudioTrack::hasAudio() const {
return _audStream->numQueuedStreams() > 0;
}
bool MKVDecoder::VorbisAudioTrack::needsAudio() const {
// TODO: 10 is very arbitrary. We probably should do something like QuickTime does.
return _audStream->numQueuedStreams() < 10;
}
bool MKVDecoder::VorbisAudioTrack::synthesizePacket(byte *frame, long size) {
bool res = true;
oggPacket.packet = frame;
oggPacket.bytes = size;
oggPacket.b_o_s = false;
oggPacket.packetno++;
oggPacket.granulepos = -1;
if (vorbis_synthesis(&_vorbisBlock, &oggPacket) == 0) { // test for success
if(vorbis_synthesis_blockin(&_vorbisDSP, &_vorbisBlock)) {
res = false;
warning("vorbis_synthesis_blockin failed");
}
}
else {
res = false;
warning("Vorbis synthesis failed");
}
return res;
}
bool MKVDecoder::queueAudio(long size) {
bool queuedAudio = false;
if (_audioTrack->synthesizePacket(_frame, size) && _audioTrack->decodeSamples(_frame, size))
queuedAudio = true;
return queuedAudio;
}
} // End of namespace Video

183
video/mkv_decoder.h Normal file
View File

@@ -0,0 +1,183 @@
/* 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/scummsys.h" // for USE_VPX
#ifdef USE_VPX
#ifndef VIDEO_MKV_DECODER_H
#define VIDEO_MKV_DECODER_H
#include "common/queue.h"
#include "video/video_decoder.h"
#include "audio/mixer.h"
#include "graphics/surface.h"
#ifdef USE_TREMOR
#include <tremor/ivorbiscodec.h>
#else
#include <vorbis/codec.h>
#endif
#include <vpx/vpx_decoder.h>
#include <vpx/vp8dx.h>
namespace Common {
class SeekableReadStream;
}
namespace Audio {
class AudioStream;
class QueuingAudioStream;
}
namespace mkvparser {
class MkvReader;
class Cluster;
class Track;
class Tracks;
class Block;
class BlockEntry;
class Segment;
}
namespace Video {
class MkvReader;
/**
*
* Decoder for MKV/Webm videos.
* Video decoder used in engines:
* - sludge
*/
class MKVDecoder : public VideoDecoder {
public:
MKVDecoder();
virtual ~MKVDecoder();
/**
* Load a video file
* @param stream the stream to load
*/
bool loadStream(Common::SeekableReadStream *stream);
void close();
protected:
void readNextPacket();
private:
class VPXVideoTrack : public VideoTrack {
public:
VPXVideoTrack(const mkvparser::Track *const pTrack);
~VPXVideoTrack();
bool endOfTrack() const;
uint16 getWidth() const { return _width; }
uint16 getHeight() const { return _height; }
Graphics::PixelFormat getPixelFormat() const { return _pixelFormat; }
bool setOutputPixelFormat(const Graphics::PixelFormat &format) {
if (format.bytesPerPixel != 2 && format.bytesPerPixel != 4)
return false;
_pixelFormat = format;
return true;
}
int getCurFrame() const { return _curFrame; }
uint32 getNextFrameStartTime() const { return (uint32)(_nextFrameStartTime * 1000); }
const Graphics::Surface *decodeNextFrame();
bool decodeFrame(byte *frame, long size);
void setEndOfVideo() { _endOfVideo = true; }
private:
int _curFrame;
bool _endOfVideo;
double _nextFrameStartTime;
uint16 _width;
uint16 _height;
Graphics::PixelFormat _pixelFormat;
Graphics::Surface _surface;
Common::Queue<Graphics::Surface> _displayQueue;
vpx_codec_ctx_t *_codec = nullptr;
};
class VorbisAudioTrack : public AudioTrack {
public:
VorbisAudioTrack(const mkvparser::Track *const pTrack);
~VorbisAudioTrack();
bool decodeSamples(byte *frame, long size);
bool hasAudio() const;
bool needsAudio() const;
bool synthesizePacket(byte *frame, long size);
void setEndOfAudio() { _endOfAudio = true; }
protected:
Audio::AudioStream *getAudioStream() const;
private:
Audio::QueuingAudioStream *_audStream;
vorbis_block _vorbisBlock;
vorbis_dsp_state _vorbisDSP;
vorbis_info _vorbisInfo;
bool _endOfAudio;
ogg_packet oggPacket;
};
bool queueAudio(long size);
Common::SeekableReadStream *_fileStream;
bool _hasVideo, _hasAudio;
VPXVideoTrack *_videoTrack = nullptr;
VorbisAudioTrack *_audioTrack = nullptr;
mkvparser::MkvReader *_reader = nullptr;
const mkvparser::Cluster *_cluster = nullptr;
const mkvparser::Tracks *_pTracks = nullptr;
const mkvparser::BlockEntry *_pBlockEntry = nullptr;
mkvparser::Segment *_pSegment = nullptr;
byte *_frame = nullptr;
int _frameCounter = 0;
int _vTrack = -1;
int _aTrack = -1;
const mkvparser::Block *_pBlock;
long long _trackNum;
int _frameCount;
};
} // End of namespace Video
#endif
#endif

41
video/module.mk Normal file
View File

@@ -0,0 +1,41 @@
MODULE := video
MODULE_OBJS := \
3do_decoder.o \
avi_decoder.o \
coktel_decoder.o \
dxa_decoder.o \
flic_decoder.o \
mpegps_decoder.o \
mve_decoder.o \
paco_decoder.o \
psx_decoder.o \
qt_decoder.o \
qtvr_decoder.o \
smk_decoder.o \
subtitles.o \
video_decoder.o
ifdef USE_BINK
MODULE_OBJS += \
bink_decoder.o
endif
ifdef USE_HNM
MODULE_OBJS += \
hnm_decoder.o
endif
ifdef USE_THEORADEC
MODULE_OBJS += \
theora_decoder.o
endif
ifdef USE_VPX
MODULE_OBJS += \
mkv_decoder.o \
mkv/mkvparser.o
endif
# Include common rules
include $(srcdir)/rules.mk

915
video/mpegps_decoder.cpp Normal file
View File

@@ -0,0 +1,915 @@
/* 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 "audio/audiostream.h"
#include "audio/decoders/raw.h"
#include "audio/decoders/ac3.h"
#include "audio/decoders/mp3.h"
#include "common/debug.h"
#include "common/endian.h"
#include "common/stream.h"
#include "common/memstream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "video/mpegps_decoder.h"
#include "image/codecs/mpeg.h"
// The demuxing code is based on libav's demuxing code
namespace Video {
// --------------------------------------------------------------------------
// Decoder - This is the part that takes a packet and figures out what to do
// with it.
// --------------------------------------------------------------------------
enum {
kStartCodeSequenceHeader = 0x1B3,
kStartCodePack = 0x1BA,
kStartCodeSystemHeader = 0x1BB,
kStartCodeProgramStreamMap = 0x1BC,
kStartCodePrivateStream1 = 0x1BD,
kStartCodePaddingStream = 0x1BE,
kStartCodePrivateStream2 = 0x1BF
};
MPEGPSDecoder::MPEGPSDecoder(double decibel) {
_decibel = decibel;
_demuxer = new MPEGPSDemuxer();
}
MPEGPSDecoder::~MPEGPSDecoder() {
close();
delete _demuxer;
}
bool MPEGPSDecoder::loadStream(Common::SeekableReadStream *stream) {
close();
if (!_demuxer->loadStream(stream)) {
close();
return false;
}
if (!addFirstVideoTrack()) {
close();
return false;
}
return true;
}
void MPEGPSDecoder::setPrebufferedPackets(int packets) {
_demuxer->setPrebufferedPackets(packets);
}
void MPEGPSDecoder::close() {
VideoDecoder::close();
_demuxer->close();
_streamMap.clear();
}
MPEGPSDecoder::MPEGStream *MPEGPSDecoder::getStream(uint32 startCode, Common::SeekableReadStream *packet) {
MPEGStream *stream = 0;
if (_streamMap.contains(startCode)) {
// We already found the stream
stream = _streamMap[startCode];
} else {
// We haven't seen this before
if (startCode == kStartCodePrivateStream1) {
PrivateStreamType streamType = detectPrivateStreamType(packet);
packet->seek(0);
// TODO: Handling of these types (as needed)
bool handled = false;
const char *typeName;
switch (streamType) {
case kPrivateStreamAC3: {
typeName = "AC-3";
#ifdef USE_A52
handled = true;
AC3AudioTrack *ac3Track = new AC3AudioTrack(*packet, _decibel, getSoundType());
stream = ac3Track;
_streamMap[startCode] = ac3Track;
addTrack(ac3Track);
#endif
break;
}
case kPrivateStreamDTS:
typeName = "DTS";
break;
case kPrivateStreamDVDPCM:
typeName = "DVD PCM";
break;
case kPrivateStreamPS2Audio: {
typeName = "PS2 Audio";
handled = true;
PS2AudioTrack *audioTrack = new PS2AudioTrack(packet, getSoundType());
stream = audioTrack;
_streamMap[startCode] = audioTrack;
addTrack(audioTrack);
break;
}
default:
typeName = "Unknown";
break;
}
if (!handled) {
warning("Unhandled DVD private stream: %s", typeName);
// Make it 0 so we don't get the warning twice
_streamMap[startCode] = 0;
}
} else if (startCode >= 0x1E0 && startCode <= 0x1EF) {
// Video stream
// TODO: Multiple video streams
warning("Found extra video stream 0x%04X", startCode);
_streamMap[startCode] = 0;
} else if (startCode >= 0x1C0 && startCode <= 0x1DF) {
#ifdef USE_MAD
// MPEG Audio stream
MPEGAudioTrack *audioTrack = new MPEGAudioTrack(*packet, getSoundType());
stream = audioTrack;
_streamMap[startCode] = audioTrack;
addTrack(audioTrack);
#else
warning("Found audio stream 0x%04X, but no MAD support compiled in", startCode);
_streamMap[startCode] = 0;
#endif
} else {
// Probably not relevant
debug(0, "Found unhandled MPEG-PS stream type 0x%04x", startCode);
_streamMap[startCode] = 0;
}
}
return stream;
}
void MPEGPSDecoder::readNextPacket() {
for (;;) {
int32 startCode;
uint32 pts, dts;
Common::SeekableReadStream *packet = _demuxer->getNextPacket(getTime(), startCode, pts, dts);
if (!packet) {
// End of stream
for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++)
if ((*it)->getTrackType() == Track::kTrackTypeVideo)
((MPEGVideoTrack *)*it)->setEndOfTrack();
return;
}
MPEGStream *stream = getStream(startCode, packet);
if (stream) {
packet->seek(0);
bool done = stream->sendPacket(packet, pts, dts);
if (done && stream->getStreamType() == MPEGStream::kStreamTypeVideo)
return;
} else {
delete packet;
}
}
}
bool MPEGPSDecoder::addFirstVideoTrack() {
int32 startCode;
uint32 pts, dts;
Common::SeekableReadStream *packet = _demuxer->getFirstVideoPacket(startCode, pts, dts);
if (!packet)
return false;
// Video stream
// Can be MPEG-1/2 or MPEG-4/h.264. We'll assume the former and
// I hope we never need the latter.
MPEGVideoTrack *track = new MPEGVideoTrack(packet);
addTrack(track);
_streamMap[startCode] = track;
return true;
}
MPEGPSDecoder::PrivateStreamType MPEGPSDecoder::detectPrivateStreamType(Common::SeekableReadStream *packet) {
uint32 dvdCode = packet->readUint32LE();
if (packet->eos())
return kPrivateStreamUnknown;
uint32 ps2Header = packet->readUint32BE();
if (!packet->eos() && ps2Header == MKTAG('S', 'S', 'h', 'd'))
return kPrivateStreamPS2Audio;
switch (dvdCode & 0xE0) {
case 0x80:
if ((dvdCode & 0xF8) == 0x88)
return kPrivateStreamDTS;
return kPrivateStreamAC3;
case 0xA0:
return kPrivateStreamDVDPCM;
default:
break;
}
return kPrivateStreamUnknown;
}
// --------------------------------------------------------------------------
// Demuxer - This is the part that reads packets from the stream and delivers
// them to the decoder.
//
// It will buffer a number of packets in advance, because otherwise it may
// not encounter any audio packets until it's far too late to decode them.
// Before I added this, there would be 9 or 10 frames of video before the
// first audio packet, even though the timestamp indicated that the audio
// should start slightly before the video.
// --------------------------------------------------------------------------
#define AUDIO_THRESHOLD 100
MPEGPSDecoder::MPEGPSDemuxer::MPEGPSDemuxer() {
_stream = 0;
}
MPEGPSDecoder::MPEGPSDemuxer::~MPEGPSDemuxer() {
close();
}
bool MPEGPSDecoder::MPEGPSDemuxer::loadStream(Common::SeekableReadStream *stream) {
close();
// Check if the videostream being loaded is an elementary stream (ES) or a program stream (PS)
// PS streams start with the Pack Header which has a start code of 0x1ba
// ES streams start with the Sequence Header which has a start code of 0x1b3
uint32 header = stream->readUint32BE();
stream->seek(-4, SEEK_CUR);
// If it is a Sequence Header (ES Stream), pass the stream to a Elementary Stream handler.
// If it is a Pack Header (PS stream), pass the stream to PS demuxer for demuxing into video and audio packets
// Currently not handling other stream types like ES audio stream
// Throwing a warning, so that decoding of further streams isn't affected
// Unknown stream header types are "handled" (ignored) in the readNextPacketHeader function
if (header == kStartCodeSequenceHeader) {
_isESStream = true;
} else if (header == kStartCodePack) {
_isESStream = false;
} else {
warning("Unknown Start Code in the MPEG stream, %d", header);
_isESStream = false;
}
_stream = stream;
int queuedPackets = 0;
while (queueNextPacket() && queuedPackets < _prebufferedPackets) {
queuedPackets++;
}
return true;
}
void MPEGPSDecoder::MPEGPSDemuxer::close() {
delete _stream;
_stream = 0;
_firstAudioPacketPts = 0xFFFFFFFF;
_firstVideoPacketPts = 0xFFFFFFFF;
while (!_audioQueue.empty()) {
Packet packet = _audioQueue.pop();
delete packet._stream;
}
while (!_videoQueue.empty()) {
Packet packet = _videoQueue.pop();
delete packet._stream;
}
}
Common::SeekableReadStream *MPEGPSDecoder::MPEGPSDemuxer::getFirstVideoPacket(int32 &startCode, uint32 &pts, uint32 &dts) {
if (_videoQueue.empty())
return nullptr;
Packet packet = _videoQueue.front();
startCode = packet._startCode;
pts = packet._pts;
dts = packet._dts;
return packet._stream;
}
Common::SeekableReadStream *MPEGPSDecoder::MPEGPSDemuxer::getNextPacket(uint32 currentTime, int32 &startCode, uint32 &pts, uint32 &dts) {
queueNextPacket();
// The idea here is to prioritize the delivery of audio packets,
// because when the decoder wants a frame it will keep asking until it
// gets a frame. There is nothing like that in the decoder to ensure
// speedy delivery of audio.
if (!_audioQueue.empty()) {
Packet packet = _audioQueue.front();
bool usePacket = false;
if (packet._pts == 0xFFFFFFFF) {
// No timestamp? Use it just in case. This could be a
// bad idea, but in my tests all audio packets have a
// time stamp.
usePacket = true;
} else {
if (packet._pts >= _firstAudioPacketPts)
packet._pts -= _firstAudioPacketPts;
uint32 packetTime = packet._pts / 90;
if (packetTime <= currentTime || packetTime - currentTime < AUDIO_THRESHOLD || _videoQueue.empty()) {
// The packet is overdue, or will be soon.
//
// TODO: We should pad or trim the first audio
// packet based on the timestamp to get the
// audio to start at the exact desired time.
// But for some reason it seems to work well
// enough anyway. For now.
usePacket = true;
}
}
if (usePacket) {
_audioQueue.pop();
startCode = packet._startCode;
pts = packet._pts;
dts = packet._dts;
return packet._stream;
}
}
if (!_videoQueue.empty()) {
Packet packet = _videoQueue.pop();
startCode = packet._startCode;
if (packet._pts != 0xFFFFFFFF && packet._pts >= _firstVideoPacketPts) {
packet._pts -= _firstVideoPacketPts;
}
pts = packet._pts;
dts = packet._dts;
return packet._stream;
}
return nullptr;
}
bool MPEGPSDecoder::MPEGPSDemuxer::queueNextPacket() {
if (_stream->eos())
return false;
// Program Streams are nothing but a wrapping around Elementary Stream data, this wrapping or header has
// length, pts, dts and other information embedded in it which we parse in the readNextPacketHeader function
// Elementary stream doesn't have pts and dts, but our sendPacket() function handles that well
// TODO: Only handling video ES streams for now, audio ES streams are bit more complicated
if (_isESStream) {
const uint16 kESPacketSize = 1024; // FFmpeg uses 1024 to packetize ES streams into PES packets
Common::SeekableReadStream *stream = _stream->readStream(kESPacketSize);
int32 startCode = 0x1E0;
uint32 pts = 0xFFFFFFFF, dts = 0xFFFFFFFF;
_videoQueue.push(Packet(stream, startCode, pts, dts));
return true;
}
for (;;) {
int32 startCode;
uint32 pts, dts;
int size = readNextPacketHeader(startCode, pts, dts);
if (size < 0) {
// End of stream
return false;
}
Common::SeekableReadStream *stream = _stream->readStream(size);
if (startCode == kStartCodePrivateStream1 || (startCode >= 0x1C0 && startCode <= 0x1DF)) {
// Audio packet
_audioQueue.push(Packet(stream, startCode, pts, dts));
if (_firstAudioPacketPts == 0xFFFFFFFF)
_firstAudioPacketPts = pts;
return true;
}
if (startCode >= 0x1E0 && startCode <= 0x1EF) {
// Video packet
_videoQueue.push(Packet(stream, startCode, pts, dts));
if (_firstVideoPacketPts == 0xFFFFFFFF)
_firstVideoPacketPts = pts;
return true;
}
delete stream;
}
}
int MPEGPSDecoder::MPEGPSDemuxer::readNextPacketHeader(int32 &startCode, uint32 &pts, uint32 &dts) {
for (;;) {
uint32 size;
startCode = findNextStartCode(size);
if (_stream->eos())
return -1;
if (startCode < 0)
continue;
uint32 lastSync = _stream->pos();
if (startCode == kStartCodePack || startCode == kStartCodeSystemHeader)
continue;
int length = _stream->readUint16BE();
if (startCode == kStartCodePaddingStream || startCode == kStartCodePrivateStream2) {
_stream->skip(length);
continue;
}
if (startCode == kStartCodeProgramStreamMap) {
parseProgramStreamMap(length);
continue;
}
// Find matching stream
if (!((startCode >= 0x1C0 && startCode <= 0x1DF) ||
(startCode >= 0x1E0 && startCode <= 0x1EF) ||
startCode == kStartCodePrivateStream1 || startCode == 0x1FD))
continue;
// Stuffing
byte c;
for (;;) {
if (length < 1) {
_stream->seek(lastSync);
continue;
}
c = _stream->readByte();
length--;
// XXX: for mpeg1, should test only bit 7
if (c != 0xFF)
break;
}
if ((c & 0xC0) == 0x40) {
// Buffer scale and size
_stream->readByte();
c = _stream->readByte();
length -= 2;
}
pts = 0xFFFFFFFF;
dts = 0xFFFFFFFF;
if ((c & 0xE0) == 0x20) {
dts = pts = readPTS(c);
length -= 4;
if (c & 0x10) {
dts = readPTS(-1);
length -= 5;
}
} else if ((c & 0xC0) == 0x80) {
// MPEG-2 PES
byte flags = _stream->readByte();
int headerLength = _stream->readByte();
length -= 2;
if (headerLength > length) {
_stream->seek(lastSync);
continue;
}
length -= headerLength;
if (flags & 0x80) {
dts = pts = readPTS(-1);
headerLength -= 5;
if (flags & 0x40) {
dts = readPTS(-1);
headerLength -= 5;
}
}
if (flags & 0x3F && headerLength == 0) {
flags &= 0xC0;
warning("Further flags set but no bytes left");
}
if (flags & 0x01) { // PES extension
byte pesExt =_stream->readByte();
headerLength--;
// Skip PES private data, program packet sequence
int skip = (pesExt >> 4) & 0xB;
skip += skip & 0x9;
if (pesExt & 0x40 || skip > headerLength) {
warning("pesExt %x is invalid", pesExt);
pesExt = skip = 0;
} else {
_stream->skip(skip);
headerLength -= skip;
}
if (pesExt & 0x01) { // PES extension 2
byte ext2Length = _stream->readByte();
headerLength--;
if ((ext2Length & 0x7F) != 0) {
byte idExt = _stream->readByte();
if ((idExt & 0x80) == 0)
startCode = (startCode & 0xFF) << 8;
headerLength--;
}
}
}
if (headerLength < 0) {
_stream->seek(lastSync);
continue;
}
_stream->skip(headerLength);
} else if (c != 0xF) {
continue;
}
if (length < 0) {
_stream->seek(lastSync);
continue;
}
return length;
}
}
#define MAX_SYNC_SIZE 100000
int MPEGPSDecoder::MPEGPSDemuxer::findNextStartCode(uint32 &size) {
size = MAX_SYNC_SIZE;
int32 state = 0xFF;
while (size > 0) {
byte v = _stream->readByte();
if (_stream->eos())
return -1;
size--;
if (state == 0x1)
return ((state << 8) | v) & 0xFFFFFF;
state = ((state << 8) | v) & 0xFFFFFF;
}
return -1;
}
uint32 MPEGPSDecoder::MPEGPSDemuxer::readPTS(int c) {
byte buf[5];
buf[0] = (c < 0) ? _stream->readByte() : c;
_stream->read(buf + 1, 4);
return ((buf[0] & 0x0E) << 29) | ((READ_BE_UINT16(buf + 1) >> 1) << 15) | (READ_BE_UINT16(buf + 3) >> 1);
}
void MPEGPSDecoder::MPEGPSDemuxer::parseProgramStreamMap(int length) {
_stream->readByte();
_stream->readByte();
// skip program stream info
_stream->skip(_stream->readUint16BE());
int esMapLength = _stream->readUint16BE();
while (esMapLength >= 4) {
_stream->readByte(); // type
_stream->readByte(); // esID
uint16 esInfoLength = _stream->readUint16BE();
// Skip program stream info
_stream->skip(esInfoLength);
esMapLength -= 4 + esInfoLength;
}
_stream->readUint32BE(); // CRC32
}
// --------------------------------------------------------------------------
// Video track
// --------------------------------------------------------------------------
MPEGPSDecoder::MPEGVideoTrack::MPEGVideoTrack(Common::SeekableReadStream *firstPacket) {
_surface = 0;
_endOfTrack = false;
_curFrame = -1;
_framePts = 0xFFFFFFFF;
_nextFrameStartTime = Audio::Timestamp(0, 27000000); // 27 MHz timer
findDimensions(firstPacket);
#ifdef USE_MPEG2
_mpegDecoder = new Image::MPEGDecoder();
#endif
}
MPEGPSDecoder::MPEGVideoTrack::~MPEGVideoTrack() {
#ifdef USE_MPEG2
delete _mpegDecoder;
#endif
if (_surface) {
_surface->free();
delete _surface;
}
}
uint16 MPEGPSDecoder::MPEGVideoTrack::getWidth() const {
return _width;
}
uint16 MPEGPSDecoder::MPEGVideoTrack::getHeight() const {
return _height;
}
Graphics::PixelFormat MPEGPSDecoder::MPEGVideoTrack::getPixelFormat() const {
return _pixelFormat;
}
bool MPEGPSDecoder::MPEGVideoTrack::setOutputPixelFormat(const Graphics::PixelFormat &format) {
if (format.bytesPerPixel != 2 && format.bytesPerPixel != 4)
return false;
_pixelFormat = format;
return true;
}
const Graphics::Surface *MPEGPSDecoder::MPEGVideoTrack::decodeNextFrame() {
return _surface;
}
bool MPEGPSDecoder::MPEGVideoTrack::sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts) {
#ifdef USE_MPEG2
if (!_surface) {
_surface = new Graphics::Surface();
_surface->create(_width, _height, _pixelFormat);
}
if (pts != 0xFFFFFFFF) {
_framePts = pts;
}
uint32 framePeriod;
bool foundFrame = _mpegDecoder->decodePacket(*packet, framePeriod, _surface);
if (foundFrame) {
_curFrame++;
// If there has been a timestamp since the previous frame, use that for
// syncing. Usually it will be the timestamp from the current packet,
// but it might not be.
if (_framePts != 0xFFFFFFFF) {
_nextFrameStartTime = Audio::Timestamp(_framePts / 90, 27000000);
} else {
_nextFrameStartTime = _nextFrameStartTime.addFrames(framePeriod);
}
_framePts = 0xFFFFFFFF;
}
#endif
delete packet;
#ifdef USE_MPEG2
return foundFrame;
#else
return true;
#endif
}
void MPEGPSDecoder::MPEGVideoTrack::findDimensions(Common::SeekableReadStream *firstPacket) {
// First, check for the picture start code
if (firstPacket->readUint32BE() != 0x1B3)
error("Failed to detect MPEG sequence start");
// This is part of the bitstream, but there's really no purpose
// to use Common::BitStream just for this: 12 bits width, 12 bits
// height
_width = firstPacket->readByte() << 4;
_height = firstPacket->readByte();
_width |= (_height & 0xF0) >> 4;
_height = ((_height & 0x0F) << 8) | firstPacket->readByte();
_pixelFormat = Image::Codec::getDefaultYUVFormat();
debugC(3, kDebugLevelGVideo, "MPEG dimensions: %dx%d", _width, _height);
firstPacket->seek(0);
}
// --------------------------------------------------------------------------
// Audio track
// --------------------------------------------------------------------------
#ifdef USE_MAD
// The audio code here is almost entirely based on what we do in mp3.cpp
MPEGPSDecoder::MPEGAudioTrack::MPEGAudioTrack(Common::SeekableReadStream &firstPacket, Audio::Mixer::SoundType soundType) :
AudioTrack(soundType) {
_audStream = Audio::makePacketizedMP3Stream(firstPacket);
}
MPEGPSDecoder::MPEGAudioTrack::~MPEGAudioTrack() {
delete _audStream;
}
bool MPEGPSDecoder::MPEGAudioTrack::sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts) {
_audStream->queuePacket(packet);
return true;
}
Audio::AudioStream *MPEGPSDecoder::MPEGAudioTrack::getAudioStream() const {
return _audStream;
}
#endif
#ifdef USE_A52
MPEGPSDecoder::AC3AudioTrack::AC3AudioTrack(Common::SeekableReadStream &firstPacket, double decibel, Audio::Mixer::SoundType soundType) :
AudioTrack(soundType) {
_audStream = Audio::makeAC3Stream(firstPacket, decibel);
if (!_audStream)
error("Could not create AC-3 stream");
}
MPEGPSDecoder::AC3AudioTrack::~AC3AudioTrack() {
delete _audStream;
}
bool MPEGPSDecoder::AC3AudioTrack::sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts) {
// Skip DVD code
packet->readUint32LE();
if (packet->eos())
return true;
_audStream->queuePacket(packet);
return true;
}
Audio::AudioStream *MPEGPSDecoder::AC3AudioTrack::getAudioStream() const {
return _audStream;
}
#endif
MPEGPSDecoder::PS2AudioTrack::PS2AudioTrack(Common::SeekableReadStream *firstPacket, Audio::Mixer::SoundType soundType) :
AudioTrack(soundType) {
firstPacket->seek(12); // unknown data (4), 'SShd', header size (4)
_soundType = firstPacket->readUint32LE();
if (_soundType == PS2_ADPCM)
error("Unhandled PS2 ADPCM sound in MPEG-PS video");
else if (_soundType != PS2_PCM)
error("Unknown PS2 sound type %x", _soundType);
uint32 sampleRate = firstPacket->readUint32LE();
_channels = firstPacket->readUint32LE();
_interleave = firstPacket->readUint32LE();
byte flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
if (_channels == 2)
flags |= Audio::FLAG_STEREO;
_blockBuffer = new byte[_interleave * _channels];
_blockPos = _blockUsed = 0;
_audStream = Audio::makePacketizedRawStream(sampleRate, flags);
_isFirstPacket = true;
firstPacket->seek(0);
}
MPEGPSDecoder::PS2AudioTrack::~PS2AudioTrack() {
delete[] _blockBuffer;
delete _audStream;
}
bool MPEGPSDecoder::PS2AudioTrack::sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts) {
packet->skip(4);
if (_isFirstPacket) {
// Skip over the header which we already parsed
packet->skip(4);
packet->skip(packet->readUint32LE());
if (packet->readUint32BE() != MKTAG('S', 'S', 'b', 'd'))
error("Failed to find 'SSbd' tag");
packet->readUint32LE(); // body size
_isFirstPacket = false;
}
uint32 size = packet->size() - packet->pos();
uint32 bytesPerChunk = _interleave * _channels;
uint32 sampleCount = calculateSampleCount(size);
byte *buffer = (byte *)malloc(sampleCount * 2);
int16 *ptr = (int16 *)buffer;
// Handle any full chunks first
while (size >= bytesPerChunk) {
packet->read(_blockBuffer + _blockPos, bytesPerChunk - _blockPos);
size -= bytesPerChunk - _blockPos;
_blockPos = 0;
for (uint32 i = _blockUsed; i < _interleave / 2; i++)
for (uint32 j = 0; j < _channels; j++)
*ptr++ = READ_UINT16(_blockBuffer + i * 2 + j * _interleave);
_blockUsed = 0;
}
// Then fallback on loading any leftover
if (size > 0) {
packet->read(_blockBuffer, size);
_blockPos = size;
if (size > (_channels - 1) * _interleave) {
_blockUsed = (size - (_channels - 1) * _interleave) / 2;
for (uint32 i = 0; i < _blockUsed; i++)
for (uint32 j = 0; j < _channels; j++)
*ptr++ = READ_UINT16(_blockBuffer + i * 2 + j * _interleave);
}
}
_audStream->queuePacket(new Common::MemoryReadStream(buffer, sampleCount * 2, DisposeAfterUse::YES));
delete packet;
return true;
}
Audio::AudioStream *MPEGPSDecoder::PS2AudioTrack::getAudioStream() const {
return _audStream;
}
uint32 MPEGPSDecoder::PS2AudioTrack::calculateSampleCount(uint32 packetSize) const {
uint32 bytesPerChunk = _interleave * _channels, result = 0;
// If we have a partial block, subtract the remainder from the size. That
// gets put towards reading the partial block
if (_blockPos != 0) {
packetSize -= bytesPerChunk - _blockPos;
result += (_interleave / 2) - _blockUsed;
}
// Round the number of whole chunks down and then calculate how many samples that gives us
result += (packetSize / bytesPerChunk) * _interleave / 2;
// Total up anything we can get from the remainder
packetSize %= bytesPerChunk;
if (packetSize > (_channels - 1) * _interleave)
result += (packetSize - (_channels - 1) * _interleave) / 2;
return result * _channels;
}
} // End of namespace Video

256
video/mpegps_decoder.h Normal file
View File

@@ -0,0 +1,256 @@
/* 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 VIDEO_MPEGPS_DECODER_H
#define VIDEO_MPEGPS_DECODER_H
#include "common/hashmap.h"
#include "common/queue.h"
#include "graphics/surface.h"
#include "video/video_decoder.h"
namespace Audio {
class PacketizedAudioStream;
}
namespace Common {
class SeekableReadStream;
}
namespace Graphics {
struct PixelFormat;
}
namespace Image {
class MPEGDecoder;
}
namespace Video {
/**
* Decoder for MPEG Program Stream videos.
* Video decoder used in engines:
* - mtropolis
* - qdengine
* - zvision
*/
class MPEGPSDecoder : public VideoDecoder {
public:
MPEGPSDecoder(double decibel = 0.0);
virtual ~MPEGPSDecoder();
bool loadStream(Common::SeekableReadStream *stream);
void close();
// Set the number of prebuffered packets in demuxer
// Used only by qdEngine
void setPrebufferedPackets(int packets);
protected:
void readNextPacket();
bool useAudioSync() const { return false; }
private:
class MPEGPSDemuxer {
public:
MPEGPSDemuxer();
~MPEGPSDemuxer();
bool loadStream(Common::SeekableReadStream *stream);
void close();
Common::SeekableReadStream *getFirstVideoPacket(int32 &startCode, uint32 &pts, uint32 &dts);
Common::SeekableReadStream *getNextPacket(uint32 currentTime, int32 &startCode, uint32 &pts, uint32 &dts);
void setPrebufferedPackets(int packets) { _prebufferedPackets = packets; }
private:
class Packet {
public:
Packet(Common::SeekableReadStream *stream, int32 startCode, uint32 pts, uint32 dts) : _stream(stream), _startCode(startCode), _pts(pts), _dts(dts) {}
Common::SeekableReadStream *_stream;
int32 _startCode;
uint32 _pts;
uint32 _dts;
};
bool queueNextPacket();
bool fillQueues();
int readNextPacketHeader(int32 &startCode, uint32 &pts, uint32 &dts);
int findNextStartCode(uint32 &size);
uint32 readPTS(int c);
void parseProgramStreamMap(int length);
Common::SeekableReadStream *_stream;
Common::Queue<Packet> _videoQueue;
Common::Queue<Packet> _audioQueue;
// If we come across a non-packetized elementary stream
bool _isESStream;
uint32 _firstAudioPacketPts = 0xFFFFFFFF;
uint32 _firstVideoPacketPts = 0xFFFFFFFF;
int _prebufferedPackets = 150;
};
// Base class for handling MPEG streams
class MPEGStream {
public:
virtual ~MPEGStream() {}
enum StreamType {
kStreamTypeVideo,
kStreamTypeAudio
};
virtual bool sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts) = 0;
virtual StreamType getStreamType() const = 0;
};
// An MPEG 1/2 video track
class MPEGVideoTrack : public VideoTrack, public MPEGStream {
public:
MPEGVideoTrack(Common::SeekableReadStream *firstPacket);
~MPEGVideoTrack();
bool endOfTrack() const { return _endOfTrack; }
uint16 getWidth() const;
uint16 getHeight() const;
Graphics::PixelFormat getPixelFormat() const;
bool setOutputPixelFormat(const Graphics::PixelFormat &format);
int getCurFrame() const { return _curFrame; }
uint32 getNextFrameStartTime() const { return _nextFrameStartTime.msecs(); }
const Graphics::Surface *decodeNextFrame();
bool sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts);
StreamType getStreamType() const { return kStreamTypeVideo; }
void setEndOfTrack() { _endOfTrack = true; }
private:
bool _endOfTrack;
int _curFrame;
uint32 _framePts;
Audio::Timestamp _nextFrameStartTime;
Graphics::Surface *_surface;
uint16 _width;
uint16 _height;
Graphics::PixelFormat _pixelFormat;
void findDimensions(Common::SeekableReadStream *firstPacket);
#ifdef USE_MPEG2
Image::MPEGDecoder *_mpegDecoder;
#endif
};
#ifdef USE_MAD
// An MPEG audio track
class MPEGAudioTrack : public AudioTrack, public MPEGStream {
public:
MPEGAudioTrack(Common::SeekableReadStream &firstPacket, Audio::Mixer::SoundType soundType);
~MPEGAudioTrack();
bool sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts);
StreamType getStreamType() const { return kStreamTypeAudio; }
protected:
Audio::AudioStream *getAudioStream() const;
private:
Audio::PacketizedAudioStream *_audStream;
};
#endif
#ifdef USE_A52
class AC3AudioTrack : public AudioTrack, public MPEGStream {
public:
AC3AudioTrack(Common::SeekableReadStream &firstPacket, double decibel, Audio::Mixer::SoundType soundType);
~AC3AudioTrack();
bool sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts);
StreamType getStreamType() const { return kStreamTypeAudio; }
protected:
Audio::AudioStream *getAudioStream() const;
private:
Audio::PacketizedAudioStream *_audStream;
};
#endif
class PS2AudioTrack : public AudioTrack, public MPEGStream {
public:
PS2AudioTrack(Common::SeekableReadStream *firstPacket, Audio::Mixer::SoundType soundType);
~PS2AudioTrack();
bool sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts);
StreamType getStreamType() const { return kStreamTypeAudio; }
protected:
Audio::AudioStream *getAudioStream() const;
private:
Audio::PacketizedAudioStream *_audStream;
enum {
PS2_PCM = 0x01,
PS2_ADPCM = 0x10
};
uint32 _channels;
uint32 _soundType;
uint32 _interleave;
bool _isFirstPacket;
byte *_blockBuffer;
uint32 _blockPos, _blockUsed;
uint32 calculateSampleCount(uint32 packetSize) const;
};
// The different types of private streams we can detect at the moment
enum PrivateStreamType {
kPrivateStreamUnknown,
kPrivateStreamAC3,
kPrivateStreamDTS,
kPrivateStreamDVDPCM,
kPrivateStreamPS2Audio
};
PrivateStreamType detectPrivateStreamType(Common::SeekableReadStream *packet);
bool addFirstVideoTrack();
MPEGStream *getStream(uint32 startCode, Common::SeekableReadStream *packet);
MPEGPSDemuxer *_demuxer;
// A map from stream types to stream handlers
typedef Common::HashMap<int, MPEGStream *> StreamMap;
StreamMap _streamMap;
double _decibel;
};
} // End of namespace Video
#endif

547
video/mve_decoder.cpp Normal file
View File

@@ -0,0 +1,547 @@
/* 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 "video/mve_decoder.h"
#include "audio/decoders/raw.h"
#include "common/endian.h"
#include "common/rect.h"
#include "common/stream.h"
#include "common/memstream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/events.h"
#include "graphics/surface.h"
#include "graphics/paletteman.h"
namespace Video {
MveDecoder::MveDecoder()
: _done(false),
_s(nullptr),
_dirtyPalette(false),
_palette(256),
_skipMapSize(0),
_skipMap(nullptr),
_decodingMapSize(0),
_decodingMap(nullptr),
_frameNumber(-1),
_frameSize(0),
_frameData(nullptr),
_audioTrack(0),
_audioStream(nullptr)
{
}
MveDecoder::~MveDecoder() {
close();
delete _audioStream;
delete[] _frameData;
delete[] _decodingMap;
delete[] _skipMap;
}
static const char signature[] = "Interplay MVE File\x1A";
bool MveDecoder::loadStream(Common::SeekableReadStream *stream) {
close();
byte signature_buffer[sizeof(signature)];
stream->read(signature_buffer, sizeof(signature_buffer));
if (memcmp(signature_buffer, signature, sizeof(signature))) {
warning("MveDecoder::loadStream(): attempted to load non-MVE data");
return false;
}
_s = stream;
uint16 h1 = _s->readUint16LE();
uint16 h2 = _s->readUint16LE();
uint16 h3 = _s->readUint16LE();
assert(h1 == 0x001a);
assert(h2 == 0x0100);
assert(h3 == 0x1133);
(void)h1;
(void)h2;
(void)h3;
readPacketHeader();
while (!_done && _packetKind < 3) {
readNextPacket();
}
return true;
}
void MveDecoder::setAudioTrack(int track) {
assert(track >= 0 && track < 16);
_audioTrack= track;
}
void MveDecoder::applyPalette(PaletteManager *paletteManager) {
paletteManager->setPalette(_palette.data() + 3 * _palStart, _palStart, _palCount);
}
void MveDecoder::copyBlock_8bit(Graphics::Surface &dst, Common::MemoryReadStream &s, int block) {
int x = (block % _widthInBlocks) * 8;
int y = (block / _widthInBlocks) * 8;
byte *p = (byte*)dst.getBasePtr(x, y);
for (int i = 0; i != 8; ++i) {
s.read(p, 8);
p += dst.pitch;
}
}
void MveDecoder::copyBlock_16bit(Graphics::Surface &dst, Common::MemoryReadStream &s, int block) {
int x = (block % _widthInBlocks) * 8;
int y = (block / _widthInBlocks) * 8;
byte *p = (byte*)dst.getBasePtr(x, y);
for (int i = 0; i != 8; ++i) {
for (int j = 0; j != 8; ++j) {
WRITE_UINT16(p+2*j, s.readUint16LE());
}
p += dst.pitch;
}
}
void MveDecoder::copyBlock(Graphics::Surface &dst, Graphics::Surface &src, int block, int offset) {
int dx = (block % _widthInBlocks) * 8;
int dy = (block / _widthInBlocks) * 8;
int sx = dx + offset % _width;
int sy = dy + offset / _width;
byte *dp = (byte*)dst.getBasePtr(dx, dy);
byte *sp = (byte*)src.getBasePtr(sx, sy);
for (int i = 0; i != 8; ++i) {
memmove(dp, sp, !_trueColor ? 8 : 16);
dp += dst.pitch;
sp += src.pitch;
}
}
void MveDecoder::copyBlock(Graphics::Surface &dst, Graphics::Surface &src, int dx, int dy, int off_x, int off_y) {
int sx = dx + off_x;
int sy = dy + off_y;
byte *dp = (byte*)dst.getBasePtr(dx, dy);
byte *sp = (byte*)src.getBasePtr(sx, sy);
for (int i = 0; i != 8; ++i) {
memmove(dp, sp, !_trueColor ? 8 : 16);
dp += dst.pitch;
sp += src.pitch;
}
}
void MveDecoder::decodeFormat6() {
_decodingMapSize = _widthInBlocks * _heightInBlocks * 2;
_decodingMap = _frameData + 14;
Common::MemoryReadStream opStream = Common::MemoryReadStream(_decodingMap, _decodingMapSize);
Common::MemoryReadStream frameStream = Common::MemoryReadStream(_frameData + _decodingMapSize + 14, _frameSize);
// Pass 1
opStream.seek(0);
for (int b = 0; b != _widthInBlocks * _heightInBlocks; ++b) {
uint16 op = opStream.readUint16LE();
if (op == 0) {
if (!_trueColor) {
copyBlock_8bit(_decodeSurface0, frameStream, b);
} else {
copyBlock_16bit(_decodeSurface0, frameStream, b);
}
}
}
// Pass 2
opStream.seek(0);
for (int b = 0; b != _widthInBlocks * _heightInBlocks; ++b) {
uint16 op = opStream.readUint16LE();
if (op != 0) {
Graphics::Surface &src = (op & 0x8000) ? _decodeSurface1 : _decodeSurface0;
int offset = int(op & 0x7fff) - 0x4000;
copyBlock(_decodeSurface0, src, b, offset);
}
}
// Pass 3
for (int b = 0; b != _widthInBlocks * _heightInBlocks; ++b) {
copyBlock(_frameSurface, _decodeSurface0, b);
}
Graphics::Surface t = _decodeSurface0;
_decodeSurface0 = _decodeSurface1;
_decodeSurface1 = t;
_decodingMap = nullptr;
}
void MveDecoder::decodeFormat10() {
MveSkipStream skipStream = MveSkipStream(_skipMap, _skipMapSize);
Common::MemoryReadStream opStream = Common::MemoryReadStream(_decodingMap, _decodingMapSize);
Common::MemoryReadStream frameStream = Common::MemoryReadStream(_frameData + 14, _frameSize - 14);
// Pass 1
opStream.seek(0);
skipStream.reset();
for (int b = 0; b != _widthInBlocks * _heightInBlocks; ++b) {
if (skipStream.skip()) continue;
uint16 op = opStream.readUint16LE();
if (op == 0) {
if (!_trueColor) {
copyBlock_8bit(_decodeSurface0, frameStream, b);
} else {
copyBlock_16bit(_decodeSurface0, frameStream, b);
}
}
}
// Pass 2
opStream.seek(0);
skipStream.reset();
for (int b = 0; b != _widthInBlocks * _heightInBlocks; ++b) {
if (skipStream.skip()) continue;
uint16 op = opStream.readUint16LE();
if (op != 0) {
Graphics::Surface &src = (op & 0x8000) ? _decodeSurface1 : _decodeSurface0;
int offset = int(op & 0x7fff) - 0x4000;
copyBlock(_decodeSurface0, src, b, offset);
}
}
// Pass 3
skipStream.reset();
for (int b = 0; b != _widthInBlocks * _heightInBlocks; ++b) {
if (skipStream.skip()) continue;
copyBlock(_frameSurface, _decodeSurface0, b);
}
Graphics::Surface t = _decodeSurface0;
_decodeSurface0 = _decodeSurface1;
_decodeSurface1 = t;
}
void MveDecoder::readPacketHeader() {
_packetLen = _s->readUint16LE();
_packetKind = _s->readUint16LE();
/*
switch (_packetKind) {
case 0:
warning("initialize audio");
break;
case 1:
warning("audio");
break;
case 2:
warning("initialize video");
break;
case 3:
warning("video");
break;
case 4:
warning("shutdown");
break;
case 5:
warning("end chunk");
break;
}
*/
}
void MveDecoder::readNextPacket() {
bool frameDone = false;
while (!_done && !frameDone) {
uint16 opLen = _s->readUint16LE();
uint16 opKind = _s->readUint16BE();
switch (opKind) {
case 0x0000:
{
_done = true;
assert(opLen == 0);
break;
}
case 0x0100:
{
assert(opLen == 0);
readPacketHeader();
break;
}
case 0x0200: // create timer
{
assert(opLen == 6);
uint32 rate = _s->readUint32LE();
uint16 subdiv = _s->readUint16LE();
_frameRate = Common::Rational(1000000, rate * subdiv);
break;
}
case 0x0300: // init audio
{
assert(opLen == 8);
/*uint16 unk =*/ _s->readUint16LE();
uint16 flags = _s->readUint16LE();
uint16 sampleRate = _s->readUint16LE();
/*uint16 bufLen =*/ _s->readUint16LE();
/*
warning("\t\tAudio: %dHz %s %s",
sampleRate,
(flags & 1) == 0 ? "mono" : "stereo",
(flags & 2) == 0 ? "8-bit" : "16-bit"
);
*/
assert((flags & 1) == 0);
assert((flags & 2) == 0);
_audioStream = Audio::makeQueuingAudioStream(sampleRate, (flags & 2) != 0);
addTrack(new MveAudioTrack(this));
break;
}
case 0x0400: // send audio
{
assert(opLen == 0);
break;
}
case 0x0502: // init video buffers
{
assert(opLen == 8);
uint16 width = _s->readUint16LE();
uint16 height = _s->readUint16LE();
/*uint16 count =*/ _s->readUint16LE();
uint16 trueColor = _s->readUint16LE();
_widthInBlocks = width;
_heightInBlocks = height;
_width = 8 * width;
_height = 8 * height;
_trueColor = !!trueColor;
if (!_trueColor) {
_pixelFormat = Graphics::PixelFormat::createFormatCLUT8();
} else {
_pixelFormat = Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0);
}
_decodeSurface0.create(_width, _height, _pixelFormat);
_decodeSurface0.fillRect(Common::Rect(_width, _height), 0);
_decodeSurface1.create(_width, _height, _pixelFormat);
_decodeSurface1.fillRect(Common::Rect(_width, _height), 0);
_frameSurface.create(_width, _height, _pixelFormat);
_frameSurface.fillRect(Common::Rect(_width, _height), 0);
addTrack(new MveVideoTrack(this));
break;
}
case 0x0600:
{
delete[] _frameData;
_frameData = new byte[opLen];
_frameSize = opLen;
_s->read(_frameData, _frameSize);
decodeFormat6();
break;
}
case 0x0701: // send video
{
assert(opLen == 6);
uint16 palStart = _s->readUint16LE();
uint16 palCount = _s->readUint16LE();
uint16 unk = _s->readUint16LE();
(void)unk;
if (palStart || palCount) {
_palStart = palStart;
_palCount = palCount;
}
_frameNumber += 1;
frameDone = true;
break;
}
case 0x0800: // audio frame
{
/*uint16 seq =*/ _s->readUint16LE();
uint16 mask = _s->readUint16LE();
uint16 len = _s->readUint16LE();
assert(opLen == len + 6);
assert(_audioStream);
if (mask & (1 << _audioTrack)) {
byte *audioFrame = new byte[len];
_s->read(audioFrame, len);
_audioStream->queueBuffer(audioFrame, len, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
} else {
_s->skip(len);
}
break;
}
case 0x0900: // audio frame (silent)
{
assert(opLen == 6);
/*uint16 seq =*/ _s->readUint16LE();
/*uint16 mask =*/ _s->readUint16LE();
/*uint16 len =*/ _s->readUint16LE();
break;
}
case 0x0a00: // set video mode
{
assert(opLen == 6);
/*uint16 width =*/ _s->readUint16LE();
/*uint16 height =*/ _s->readUint16LE();
/*uint16 flags =*/ _s->readUint16LE();
break;
}
case 0x0c00:
{
uint16 palStart = _s->readUint16LE();
uint16 palCount = _s->readUint16LE();
assert(opLen >= 3 * palCount + 2);
for (int i = palStart; i < palStart + palCount; ++i) {
byte r = _s->readByte();
byte g = _s->readByte();
byte b = _s->readByte();
_palette.set(i, (r << 2) | (r >> 4), (g << 2) | (g >> 4), (b << 2) | (b >> 4));
}
if (palCount & 1) {
_s->skip(1);
}
_dirtyPalette = true;
_palStart = palStart;
_palCount = palCount;
break;
}
case 0x0e00:
{
// TODO: Preallocate or keep existing buffer
delete[] _skipMap;
_skipMap = new byte[opLen];
_skipMapSize = opLen;
_s->read(_skipMap, _skipMapSize);
break;
}
case 0x0f00:
{
// TODO: Preallocate or keep existing buffer
delete[] _decodingMap;
_decodingMap = new byte[opLen];
_decodingMapSize = opLen;
_s->read(_decodingMap, _decodingMapSize);
break;
}
case 0x1000:
{
// TODO: Preallocate or keep existing buffer
delete[] _frameData;
_frameData = new byte[opLen];
_frameSize = opLen;
_s->read(_frameData, _frameSize);
decodeFormat10();
break;
}
default:
_s->skip(opLen);
error("Unknown opcode %04x", opKind);
break;
}
}
}
MveDecoder::MveVideoTrack::MveVideoTrack(MveDecoder *decoder) : _decoder(decoder) {
}
bool MveDecoder::MveVideoTrack::endOfTrack() const {
return _decoder->_done;
}
uint16 MveDecoder::MveVideoTrack::getWidth() const {
return _decoder->_width;
}
uint16 MveDecoder::MveVideoTrack::getHeight() const {
return _decoder->_height;
}
Graphics::PixelFormat MveDecoder::MveVideoTrack::getPixelFormat() const {
return _decoder->_pixelFormat;
}
int MveDecoder::MveVideoTrack::getCurFrame() const {
return _decoder->_frameNumber;
}
const Graphics::Surface *MveDecoder::MveVideoTrack::decodeNextFrame() {
return &_decoder->_frameSurface;
}
const byte *MveDecoder::MveVideoTrack::getPalette() const {
return _decoder->_palette.data();
}
bool MveDecoder::MveVideoTrack::hasDirtyPalette() const {
return _decoder->_dirtyPalette;
}
Common::Rational MveDecoder::MveVideoTrack::getFrameRate() const {
return _decoder->getFrameRate();
}
MveDecoder::MveAudioTrack::MveAudioTrack(MveDecoder *decoder) :
AudioTrack(Audio::Mixer::kPlainSoundType),
_decoder(decoder)
{
}
Audio::AudioStream *MveDecoder::MveAudioTrack::getAudioStream() const {
return _decoder->_audioStream;
}
} // End of namespace Video

173
video/mve_decoder.h Normal file
View File

@@ -0,0 +1,173 @@
/* 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 VIDEO_MVEDECODER_H
#define VIDEO_MVEDECODER_H
#include "audio/audiostream.h"
#include "video/video_decoder.h"
#include "graphics/palette.h"
#include "graphics/surface.h"
#include "common/list.h"
#include "common/rect.h"
#include "common/memstream.h"
namespace Common {
class SeekableReadStream;
}
namespace Graphics {
struct PixelFormat;
}
class PaletteManager;
namespace Video {
/**
* Decoder for Interplay MVE videos.
*
* Video decoder used in engines:
* - kingdom
* - voyeur
*/
class MveDecoder : public VideoDecoder {
bool _done;
Common::SeekableReadStream *_s;
uint16 _packetLen;
uint16 _packetKind;
Graphics::Surface _decodeSurface0;
Graphics::Surface _decodeSurface1;
Graphics::Surface _frameSurface;
uint16 _widthInBlocks;
uint16 _heightInBlocks;
uint16 _width;
uint16 _height;
Common::Rational _frameRate;
bool _trueColor;
Graphics::PixelFormat _pixelFormat;
bool _dirtyPalette;
uint16 _palStart;
uint16 _palCount;
Graphics::Palette _palette;
uint16 _skipMapSize;
byte *_skipMap;
uint16 _decodingMapSize;
byte *_decodingMap;
int _frameNumber;
uint16 _frameSize;
byte *_frameData;
int _audioTrack;
Audio::QueuingAudioStream *_audioStream;
void readPacketHeader();
void copyBlock_8bit(Graphics::Surface &dst, Common::MemoryReadStream &s, int block);
void copyBlock_16bit(Graphics::Surface &dst, Common::MemoryReadStream &s, int block);
void copyBlock(Graphics::Surface &dst, Graphics::Surface &src, int block, int offset = 0);
void copyBlock(Graphics::Surface &dst, Graphics::Surface &src, int dx, int dy, int off_x, int off_y);
void decodeFormat6();
void decodeFormat10();
class MveVideoTrack : public FixedRateVideoTrack {
MveDecoder *_decoder;
public:
MveVideoTrack(MveDecoder *decoder);
bool endOfTrack() const;
uint16 getWidth() const;
uint16 getHeight() const;
Graphics::PixelFormat getPixelFormat() const;
int getCurFrame() const;
// int getFrameCount() const;
const Graphics::Surface *decodeNextFrame();
const byte *getPalette() const;
bool hasDirtyPalette() const;
protected:
Common::Rational getFrameRate() const;
};
class MveAudioTrack : public AudioTrack {
MveDecoder *_decoder;
public:
MveAudioTrack(MveDecoder *decoder);
Audio::AudioStream *getAudioStream() const;
};
class MveSkipStream {
Common::MemoryReadStream s;
uint16 queue;
public:
MveSkipStream(byte *p, size_t sz)
: s(p, sz), queue(0x8000)
{}
void reset() {
s.seek(0);
queue = 0x8000;
}
bool skip() {
if (queue == 0x8000) {
queue = s.readUint16LE();
assert(queue != 0);
}
bool r = (queue & 0x8000) == 0;
queue <<= 1;
return r;
}
};
public:
MveDecoder();
virtual ~MveDecoder();
bool loadStream(Common::SeekableReadStream *stream);
void setAudioTrack(int track);
void applyPalette(PaletteManager *paletteManager);
// const Common::List<Common::Rect> *getDirtyRects() const;
// void clearDirtyRects();
// void copyDirtyRectsToBuffer(uint8 *dst, uint pitch);
Common::Rational getFrameRate() { return _frameRate; }
void readNextPacket();
};
} // End of namespace Video
#endif

615
video/paco_decoder.cpp Normal file
View File

@@ -0,0 +1,615 @@
/* 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/endian.h"
#include "common/rect.h"
#include "common/stream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "graphics/surface.h"
#include "video/qt_data.h"
#include "video/paco_decoder.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
namespace Video {
enum frameTypes {
NOP = 0, // nop
// 1 - old initialisation data?
PALETTE = 2, // - new initialisation data (usually 0x30 0x00 0x00 ... meaning 8-bit with default QuickTime palette)
DELAY = 3, // - delay information
AUDIO = 4, // - audio data (8-bit unsigned PCM)
// 5 - should not be present
// 6 - should not be present
// 7 - unknown
VIDEO = 8, // - video frame
// 9 - unknown
// 10 - dummy?
EOC = 11 // - end of chunk marker
};
PacoDecoder::PacoDecoder()
: _fileStream(nullptr), _videoTrack(nullptr), _audioTrack(nullptr) {
}
PacoDecoder::~PacoDecoder() {
close();
}
void PacoDecoder::PacoDecoder::close() {
Video::VideoDecoder::close();
delete _fileStream;
_fileStream = nullptr;
_videoTrack = nullptr;
_audioTrack = nullptr;
}
bool PacoDecoder::loadStream(Common::SeekableReadStream *stream) {
close();
_curFrame = 0;
stream->readUint16BE(); // 1
stream->readUint16BE(); // 0x26
uint16 width = stream->readUint16BE();
uint16 height = stream->readUint16BE();
int16 frameRate = stream->readUint16BE();
frameRate = ABS(frameRate); // Negative framerate is indicative of audio, but not always
if (frameRate == 0) {
// 0 is equivalent to playing back at the fastest rate
frameRate = 60;
}
uint16 flags = stream->readUint16BE();
bool hasAudio = (flags & 0x100) == 0x100;
stream->readUint32BE(); // maxChunksize
stream->readUint32BE(); // always 0
stream->readUint32BE(); // audio related flags
uint16 frameCount = stream->readUint16BE();
stream->readUint16BE(); // copy of frameCount
stream->readUint16BE(); // always 8?
stream->readUint16BE(); // always 0x600?
stream->readUint32BE(); // flags
stream->readUint16BE(); // 0
for (uint i = 0; i < frameCount; i++) {
_frameSizes[i] = stream->readUint32BE();
}
_fileStream = stream;
_videoTrack = new PacoVideoTrack(frameRate, frameCount, width, height);
addTrack(_videoTrack);
if (hasAudio) {
_audioTrack = new PacoAudioTrack(getAudioSamplingRate());
addTrack(_audioTrack);
}
return true;
}
int PacoDecoder::getAudioSamplingRate() {
/**
* SamplingRate is found inside the audio packets
* Search for the first audio packet and use it.
*/
const Common::Array<int> samplingRates = {5563, 7418, 11127, 22254};
int index = 0;
int64 startPos = _fileStream->pos();
while (_fileStream->pos() < _fileStream->size()) {
int64 currentPos = _fileStream->pos();
int frameType = _fileStream->readByte();
int v = _fileStream->readByte();
uint32 chunkSize = (v << 16 ) | _fileStream->readUint16BE();
if (frameType != AUDIO) {
_fileStream->seek(currentPos + chunkSize);
continue;
}
uint16 header = _fileStream->readUint16BE();
_fileStream->readUint16BE();
index = (header >> 10) & 7;
break;
}
_fileStream->seek(startPos);
return samplingRates[index];
}
const Common::List<Common::Rect> *PacoDecoder::getDirtyRects() const {
const Track *track = getTrack(0);
if (track)
return ((const PacoVideoTrack *)track)->getDirtyRects();
return 0;
}
void PacoDecoder::clearDirtyRects() {
Track *track = getTrack(0);
if (track)
((PacoVideoTrack *)track)->clearDirtyRects();
}
void PacoDecoder::copyDirtyRectsToBuffer(uint8 *dst, uint pitch) {
Track *track = getTrack(0);
if (track)
((PacoVideoTrack *)track)->copyDirtyRectsToBuffer(dst, pitch);
}
const byte* PacoDecoder::getPalette(){
Track *track = getTrack(0);
if (track)
return ((PacoVideoTrack *)track)->getPalette();
return nullptr;
}
const byte* PacoDecoder::PacoVideoTrack::getPalette() const {
_dirtyPalette = false;
return _palette.data();
}
PacoDecoder::PacoVideoTrack::PacoVideoTrack(
uint16 frameRate, uint16 frameCount, uint16 width, uint16 height) : _palette(256) {
_curFrame = 0;
_frameRate = frameRate;
_frameCount = frameCount;
_surface = new Graphics::Surface();
_surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
_palette.set(quickTimeDefaultPalette256, 0, 256);
_dirtyPalette = true;
}
PacoDecoder::PacoVideoTrack::~PacoVideoTrack() {
_surface->free();
delete _surface;
}
bool PacoDecoder::PacoVideoTrack::endOfTrack() const {
return getCurFrame() >= getFrameCount();
}
uint16 PacoDecoder::PacoVideoTrack::getWidth() const {
return _surface->w;
}
uint16 PacoDecoder::PacoVideoTrack::getHeight() const {
return _surface->h;
}
Graphics::PixelFormat PacoDecoder::PacoVideoTrack::getPixelFormat() const {
return _surface->format;
}
void PacoDecoder::readNextPacket() {
if (_curFrame >= _videoTrack->getFrameCount())
return;
uint32 nextFrame = _fileStream->pos() + _frameSizes[_curFrame];
debugC(2, kDebugLevelGVideo, " frame %3d size %d @ %lX", _curFrame, _frameSizes[_curFrame], long(_fileStream->pos()));
_curFrame++;
while (_fileStream->pos() < nextFrame) {
int64 currentPos = _fileStream->pos();
int frameType = _fileStream->readByte();
int v = _fileStream->readByte();
uint32 chunkSize = (v << 16 ) | _fileStream->readUint16BE();
debugC(2, kDebugLevelGVideo, " slot type %d size %d @ %lX", frameType, chunkSize, long(_fileStream->pos() - 4));
switch (frameType) {
case AUDIO:
if (_audioTrack)
_audioTrack->queueSound(_fileStream, chunkSize - 4);
break;
case VIDEO:
if (_videoTrack)
_videoTrack->handleFrame(_fileStream, chunkSize - 4, _curFrame);
break;
case PALETTE:
if (_videoTrack)
_videoTrack->handlePalette(_fileStream);
break;
case EOC:
if (_videoTrack)
_videoTrack->handleEOC();
break;
case NOP:
break;
default:
error("PacoDecoder::decodeFrame(): unknown main chunk type (type = 0x%02X)", frameType);
break;
}
_fileStream->seek(currentPos + chunkSize);
}
}
const Graphics::Surface *PacoDecoder::PacoVideoTrack::decodeNextFrame() {
return _surface;
}
void PacoDecoder::PacoVideoTrack::handlePalette(Common::SeekableReadStream *fileStream) {
uint32 header = fileStream->readUint32BE();
if (header == 0x30000000) { // default quicktime palette
_palette.set(quickTimeDefaultPalette256, 0, 256);
} else {
fileStream->readUint32BE(); // 4 bytes of 00
for (int i = 0; i < 256; i++){
byte r = fileStream->readByte();
byte g = fileStream->readByte();
byte b = fileStream->readByte();
_palette.set(i, r, g, b);
}
}
_dirtyPalette = true;
}
enum {
COPY = 0, // raw copy pixels
RLE, // RLE
PRLE, // pair RLE (read a pair of pixels and repeat it the specified number of times)
QRLE, // quad RLE (read four pixels and repeat it the specified number of times)
SKIP, // skip
ENDCURRENTLINE, // end current line and skip additional len lines
EOFRAME = 15 // not real opcode but 00 F0 00 00 is often seen at the end of frame and can serve as an end marker
};
#define ADJUST_LINE \
if (compr == 1) \
ypos2 = ypos; \
else { \
ypos2 = ypos * 2 - y; \
if (ypos2 >= y + bh) { \
ypos2 -= bh; \
if (!(bh & 1)) \
ypos2++; \
} \
}
#define PUTPIX(pix) \
do { \
*dst++ = pix; \
xpos++; \
} while(0);
#define SKIP() \
do { \
dst++; \
xpos++; \
} while(0);
void PacoDecoder::PacoVideoTrack::handleFrame(Common::SeekableReadStream *fileStream, uint32 chunkSize, int curFrame) {
_curFrame = curFrame;
uint16 w = getWidth();
uint16 x = fileStream->readUint16BE(); // x offset of the updated area
uint16 y = fileStream->readUint16BE(); // y offset of the updated area
uint16 bw = fileStream->readUint16BE(); // updated area width
uint16 bh = fileStream->readUint16BE(); // updated area height
uint compr = fileStream->readByte(); // compression method and flags
fileStream->readByte(); // padding
debugC(5, kDebugLevelGVideo, " +%d,%d - %dx%d compr %X", x, y, bw, bh, compr);
compr = compr & 0xF;
uint8 *fdata = new uint8[1048576]; // 0x100000 copied from original pacodec
fileStream->read(fdata, chunkSize - 10); // remove header length
debugC(5, kDebugLevelGVideo, "pos: %ld", long(fileStream->pos()));
int16 xpos = x, ypos = y, ypos2 = y;
byte *dst = (byte *)_surface->getPixels() + x + y * w;
const uint8 *src = fdata;
int16 i, c, c1, c2, c3, c4;
uint8 clrs[16];
while (ypos < y + bh) {
c = *src++;
debugC(5, kDebugLevelGVideo, "debug info: ypos %d y %d bh %d src: %d", ypos, y, bh, c);
if (c == 0 ){ // long operation
int16 op = src[0] >> 4;
int16 len = ((src[0] & 0xF) << 8) | src[1];
src += 2;
debugC(5, kDebugLevelGVideo, " long operation: opcode: %d", op);
switch (op) {
case COPY:
while (len--)
PUTPIX(*src++);
break;
case RLE:
c1 = *src++;
while (len--)
PUTPIX(c1);
break;
case PRLE:
c1 = *src++;
c2 = *src++;
while (len--){
PUTPIX(c1);
PUTPIX(c2);
}
break;
case QRLE:
c1 = *src++;
c2 = *src++;
c3 = *src++;
c4 = *src++;
while (len--) {
PUTPIX(c1);
PUTPIX(c2);
PUTPIX(c3);
PUTPIX(c4);
}
break;
case SKIP:
while (len--)
SKIP();
break;
case ENDCURRENTLINE:
xpos = x;
ypos += len + 1;
ADJUST_LINE;
dst = (byte *)_surface->getPixels() + xpos + ypos2 * w;
break;
case EOFRAME:
xpos = x;
ypos = y + bh;
break;
default:
PUTPIX(0xFF);
debugC(5, kDebugLevelGVideo, "PacoDecoder::PacoVideoTrack::handleFrame: Long op: 0x0 op %d", op);
}
} else if (c < 128) { // copy the same amount of pixels
debugC(5, kDebugLevelGVideo, " copy pixels: %d", c);
while (c--)
PUTPIX(*src++);
} else if (c < 254) { // repeat the following value 256 - op times
debugC(5, kDebugLevelGVideo, " copy pixels -op: %d", 256 - c);
c1 = *src++;
c = 256 - c;
while (c--)
PUTPIX(c1);
} else if (c < 255) {
// next byte is either the number of pixels to skip (if non-zero) or
// a signal of compact RLE mode
c = *src++;
if (!c) { // compact RLE mode
unsigned mask = (src[0] << 8) | src[1];
src += 2;
debugC(5, kDebugLevelGVideo, "debug info compact RLE: c: %d mask: %d", c, mask);
for (i = 0; i < 16; i++, mask >>= 1) {
if (mask & 1)
clrs[i] = *src++;
}
while (xpos < x + bw) {
int16 op = *src++;
int16 len = op & 0xF;
op >>= 4;
if (op == 0) { // low nibble....
op = len;
len = *src++;
debugC(5, kDebugLevelGVideo, "debug info compact: op: %d", op);
switch (op) {
case COPY:
debugC(5, kDebugLevelGVideo, "debug info COPY: %d", len);
while (len--) {
c = *src++;
PUTPIX(clrs[c >> 4]);
if (!len)
break;
len--;
PUTPIX(clrs[c & 0xF]);
}
break;
case RLE:
debugC(5, kDebugLevelGVideo, "debug info RLE: %d", len);
c = *src++;
while (len--)
PUTPIX(clrs[c & 0xF]);
break;
case PRLE:
debugC(5, kDebugLevelGVideo, "debug info PRLE: %d", len);
c = *src++;
c1 = clrs[c >> 4];
c2 = clrs[c & 0xF];
while (len--) {
PUTPIX(c1);
PUTPIX(c2);
}
break;
case QRLE:
debugC(5, kDebugLevelGVideo, "debug info QRLE: %d", len);
c = *src++;
c1 = clrs[c >> 4];
c2 = clrs[c & 0xF];
c = *src++;
c3 = clrs[c >> 4];
c4 = clrs[c & 0xF];
while (len--) {
PUTPIX(c1);
PUTPIX(c2);
PUTPIX(c3);
PUTPIX(c4);
}
break;
case SKIP:
debugC(5, kDebugLevelGVideo, "debug info SKIP: %d", len);
while (len--)
SKIP();
break;
case ENDCURRENTLINE:
debugC(5, "debug info ENDCURRENTLINE: %d", len);
xpos = x + bw;
ypos += len;
break;
default:
warning("PacoDecoder::PacoVideoTrack::handleFrame: Compact RLE mode: 0x0 op %d", op);
}
} else if (op < 8) { // copy 1-7 colors
debugC(5, kDebugLevelGVideo, "debug info copy 1-7 colors: %d", len);
PUTPIX(clrs[len]);
op--;
while (op--) {
c = *src++;
PUTPIX(clrs[c >> 4]);
if (!op)
break;
op--;
PUTPIX(clrs[c & 0xF]);
}
} else if (op < 14) { // repeat color
debugC(5, kDebugLevelGVideo, "debug info Repeat color: %d", len);
op = 16 - op;
while (op--)
PUTPIX(clrs[len]);
} else if (op < 15) { // skip number of pixels in low nibbel
debugC(5, kDebugLevelGVideo, "debug info Skip number of pixels: %d", len);
while (len--)
SKIP();
} else {
if (len < 8) { // Pair run
debugC(5, kDebugLevelGVideo, "debug info pair run: %d", len);
c = *src++;
c1 = clrs[c >> 4];
c2 = clrs[c & 0xF];
while (len--) {
PUTPIX(c1);
PUTPIX(c2);
}
} else { // Quad run
debugC(5, kDebugLevelGVideo, "debug info quad run: %d", len);
len = 16 - len;
c = *src++;
c1 = clrs[c >> 4];
c2 = clrs[c & 0xF];
c = *src++;
c3 = clrs[c >> 4];
c4 = clrs[c & 0xF];
while (len--) {
PUTPIX(c1);
PUTPIX(c2);
PUTPIX(c3);
PUTPIX(c4);
}
}
}
}
} else {
debugC(5, kDebugLevelGVideo, "debug info SKIP: %d", c);
while (c--)
SKIP();
}
} else {
// pair or quad run. Read the next byte and if it is below 128 then read and
// repeat a pair of pixels len times, otherwise read and repeat four pixels
// (but 256 - len times)
c = *src++;
if (c < 128) { // pair run
debugC(5, kDebugLevelGVideo, "debug info PAIR RUN: %d", c);
c1 = *src++;
c2 = *src++;
while (c--) {
PUTPIX(c1);
PUTPIX(c2);
}
} else { // quad run
debugC(5, kDebugLevelGVideo, "debug info QUAD RUN: %d", c);
c = 256 - c;
c1 = *src++;
c2 = *src++;
c3 = *src++;
c4 = *src++;
while (c--) {
PUTPIX(c1);
PUTPIX(c2);
PUTPIX(c3);
PUTPIX(c4);
}
}
}
if (xpos > x + bw) debugC(5, kDebugLevelGVideo, "!!!");
if (xpos >= x + bw) {
debugC(5, kDebugLevelGVideo, "debug info ADJUST LINE");
xpos = x;
ypos++;
ADJUST_LINE;
dst = (byte *)_surface->getPixels() + x + ypos2 * w;
}
}
_dirtyRects.clear();
_dirtyRects.push_back(Common::Rect(x, y, x + bw, y + bh));
delete[] fdata;
}
void PacoDecoder::PacoVideoTrack::copyDirtyRectsToBuffer(uint8 *dst, uint pitch) {
for (const auto &dirtyRect : _dirtyRects) {
for (int y = dirtyRect.top; y < dirtyRect.bottom; ++y) {
const int x = dirtyRect.left;
memcpy(dst + y * pitch + x, (byte *)_surface->getBasePtr(x, y), dirtyRect.right - x);
}
}
clearDirtyRects();
}
PacoDecoder::PacoAudioTrack::PacoAudioTrack(int samplingRate)
: AudioTrack(Audio::Mixer::kPlainSoundType) {
_samplingRate = samplingRate;
byte audioFlags = Audio::FLAG_UNSIGNED;
_packetStream = Audio::makePacketizedRawStream(samplingRate, audioFlags);
}
PacoDecoder::PacoAudioTrack::~PacoAudioTrack() {
delete _packetStream;
}
void PacoDecoder::PacoAudioTrack::queueSound(Common::SeekableReadStream *fileStream, uint32 chunkSize) {
const Common::Array<int> samplingRates = {5563, 7418, 11127, 22254};
uint16 header = fileStream->readUint16BE();
fileStream->readUint16BE();
int index = (header >> 10) & 7;
int currentRate = samplingRates[index];
if ( currentRate != _samplingRate)
warning("PacoDecoder::PacoAudioTrack: Sampling rate differs from first frame: %i != %i", currentRate, _samplingRate);
_packetStream->queuePacket(fileStream->readStream(chunkSize - 4));
}
} // End of namespace Video

128
video/paco_decoder.h Normal file
View File

@@ -0,0 +1,128 @@
/* 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 VIDEO_PACODECODER_H
#define VIDEO_PACODECODER_H
#include "audio/audiostream.h"
#include "common/list.h"
#include "common/rect.h"
#include "graphics/palette.h"
#include "video/video_decoder.h"
namespace Common {
class SeekableReadStream;
}
namespace Graphics {
struct PixelFormat;
struct Surface;
} // namespace Graphics
namespace Video {
/**
* Decoder for PACo videos.
*
* Video decoder used in engines:
* - director
*/
class PacoDecoder : public VideoDecoder {
public:
PacoDecoder();
virtual ~PacoDecoder();
void close() override;
virtual bool loadStream(Common::SeekableReadStream *stream) override;
const Common::List<Common::Rect> *getDirtyRects() const;
void clearDirtyRects();
void copyDirtyRectsToBuffer(uint8 *dst, uint pitch);
const byte *getPalette();
virtual void readNextPacket() override;
protected:
class PacoVideoTrack : public FixedRateVideoTrack {
public:
PacoVideoTrack(
uint16 frameRate, uint16 frameCount, uint16 width, uint16 height);
~PacoVideoTrack();
bool endOfTrack() const override;
virtual bool isRewindable() const override { return false; }
uint16 getWidth() const override;
uint16 getHeight() const override;
Graphics::PixelFormat getPixelFormat() const override;
int getCurFrame() const override { return _curFrame; }
int getFrameCount() const override { return _frameCount; }
virtual const Graphics::Surface *decodeNextFrame() override;
virtual void handleFrame(Common::SeekableReadStream *fileStream, uint32 chunkSize, int curFrame);
virtual void handleEOC() { _curFrame += 1; };
void handlePalette(Common::SeekableReadStream *fileStream);
const byte *getPalette() const override;
bool hasDirtyPalette() const override { return _dirtyPalette; }
const Common::List<Common::Rect> *getDirtyRects() const { return &_dirtyRects; }
void clearDirtyRects() { _dirtyRects.clear(); }
void copyDirtyRectsToBuffer(uint8 *dst, uint pitch);
Common::Rational getFrameRate() const override { return Common::Rational(_frameRate, 1); }
protected:
Graphics::Surface *_surface;
Graphics::Palette _palette;
mutable bool _dirtyPalette;
int _curFrame;
uint32 _frameCount;
uint16 _frameRate;
Common::List<Common::Rect> _dirtyRects;
};
class PacoAudioTrack : public AudioTrack {
public:
PacoAudioTrack(int samplingRate);
~PacoAudioTrack();
void queueSound(Common::SeekableReadStream *fileStream, uint32 chunkSize);
protected:
Audio::AudioStream *getAudioStream() const { return _packetStream; }
private:
Audio::PacketizedAudioStream *_packetStream;
int _samplingRate;
};
private:
PacoVideoTrack *_videoTrack;
PacoAudioTrack *_audioTrack;
Common::SeekableReadStream *_fileStream;
int _curFrame = 0;
int _frameSizes[65536]; // can be done differently?
int getAudioSamplingRate();
};
} // End of namespace Video
#endif

642
video/psx_decoder.cpp Normal file
View File

@@ -0,0 +1,642 @@
/* 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/>.
*
*/
// PlayStation Stream demuxer based on FFmpeg/libav
// MDEC video emulation based on https://web.archive.org/web/20170411092531/https://kenai.com/downloads/jpsxdec/Old/PlayStation1_STR_format1-00.txt
#include "audio/audiostream.h"
#include "audio/decoders/adpcm.h"
#include "common/bitstream.h"
#include "common/compression/huffman.h"
#include "common/stream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "graphics/yuv_to_rgb.h"
#include "image/codecs/codec.h"
#include "video/psx_decoder.h"
namespace Video {
// Here are the codes/lengths/symbols that are used for decoding
// DC coefficients (version 3 frames only)
#define DC_CODE_COUNT 9
#define DC_HUFF_VAL(b, n, p) (((b) << 16) | ((n) << 8) | (p))
#define GET_DC_BITS(x) ((x) >> 16)
#define GET_DC_NEG(x) ((int)(((x) >> 8) & 0xff))
#define GET_DC_POS(x) ((int)((x) & 0xff))
static const uint32 s_huffmanDCChromaCodes[DC_CODE_COUNT] = {
254, 126, 62, 30, 14, 6, 2, 1, 0
};
static const byte s_huffmanDCChromaLengths[DC_CODE_COUNT] = {
8, 7, 6, 5, 4, 3, 2, 2, 2
};
static const uint32 s_huffmanDCLumaCodes[DC_CODE_COUNT] = {
126, 62, 30, 14, 6, 5, 1, 0, 4
};
static const byte s_huffmanDCLumaLengths[DC_CODE_COUNT] = {
7, 6, 5, 4, 3, 3, 2, 2, 3
};
static const uint32 s_huffmanDCSymbols[DC_CODE_COUNT] = {
DC_HUFF_VAL(8, 255, 128), DC_HUFF_VAL(7, 127, 64), DC_HUFF_VAL(6, 63, 32),
DC_HUFF_VAL(5, 31, 16), DC_HUFF_VAL(4, 15, 8), DC_HUFF_VAL(3, 7, 4),
DC_HUFF_VAL(2, 3, 2), DC_HUFF_VAL(1, 1, 1), DC_HUFF_VAL(0, 0, 0)
};
// Here are the codes/lengths/symbols that are used for decoding
// DC coefficients (version 2 and 3 frames)
#define AC_CODE_COUNT 113
#define AC_HUFF_VAL(z, a) ((z << 8) | a)
#define ESCAPE_CODE ((uint32)-1) // arbitrary, just so we can tell what code it is
#define END_OF_BLOCK ((uint32)-2) // arbitrary, just so we can tell what code it is
#define GET_AC_ZERO_RUN(code) (code >> 8)
#define GET_AC_COEFFICIENT(code) ((int)(code & 0xff))
static const uint32 s_huffmanACCodes[AC_CODE_COUNT] = {
// Regular codes
3, 3, 4, 5, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7,
32, 33, 34, 35, 36, 37, 38, 39, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31,
// Escape code
1,
// End of block code
2
};
static const byte s_huffmanACLengths[AC_CODE_COUNT] = {
// Regular codes
2, 3, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
8, 8, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10,
10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12,
12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13,
13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14,
14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15,
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
16, 16, 16, 16, 16, 16,
// Escape code
6,
// End of block code
2
};
static const uint32 s_huffmanACSymbols[AC_CODE_COUNT] = {
// Regular codes
AC_HUFF_VAL(0, 1), AC_HUFF_VAL(1, 1), AC_HUFF_VAL(0, 2), AC_HUFF_VAL(2, 1), AC_HUFF_VAL(0, 3),
AC_HUFF_VAL(4, 1), AC_HUFF_VAL(3, 1), AC_HUFF_VAL(7, 1), AC_HUFF_VAL(6, 1), AC_HUFF_VAL(1, 2),
AC_HUFF_VAL(5, 1), AC_HUFF_VAL(2, 2), AC_HUFF_VAL(9, 1), AC_HUFF_VAL(0, 4), AC_HUFF_VAL(8, 1),
AC_HUFF_VAL(13, 1), AC_HUFF_VAL(0, 6), AC_HUFF_VAL(12, 1), AC_HUFF_VAL(11, 1), AC_HUFF_VAL(3, 2),
AC_HUFF_VAL(1, 3), AC_HUFF_VAL(0, 5), AC_HUFF_VAL(10, 1), AC_HUFF_VAL(16, 1), AC_HUFF_VAL(5, 2),
AC_HUFF_VAL(0, 7), AC_HUFF_VAL(2, 3), AC_HUFF_VAL(1, 4), AC_HUFF_VAL(15, 1), AC_HUFF_VAL(14, 1),
AC_HUFF_VAL(4, 2), AC_HUFF_VAL(0, 11), AC_HUFF_VAL(8, 2), AC_HUFF_VAL(4, 3), AC_HUFF_VAL(0, 10),
AC_HUFF_VAL(2, 4), AC_HUFF_VAL(7, 2), AC_HUFF_VAL(21, 1), AC_HUFF_VAL(20, 1), AC_HUFF_VAL(0, 9),
AC_HUFF_VAL(19, 1), AC_HUFF_VAL(18, 1), AC_HUFF_VAL(1, 5), AC_HUFF_VAL(3, 3), AC_HUFF_VAL(0, 8),
AC_HUFF_VAL(6, 2), AC_HUFF_VAL(17, 1), AC_HUFF_VAL(10, 2), AC_HUFF_VAL(9, 2), AC_HUFF_VAL(5, 3),
AC_HUFF_VAL(3, 4), AC_HUFF_VAL(2, 5), AC_HUFF_VAL(1, 7), AC_HUFF_VAL(1, 6), AC_HUFF_VAL(0, 15),
AC_HUFF_VAL(0, 14), AC_HUFF_VAL(0, 13), AC_HUFF_VAL(0, 12), AC_HUFF_VAL(26, 1), AC_HUFF_VAL(25, 1),
AC_HUFF_VAL(24, 1), AC_HUFF_VAL(23, 1), AC_HUFF_VAL(22, 1), AC_HUFF_VAL(0, 31), AC_HUFF_VAL(0, 30),
AC_HUFF_VAL(0, 29), AC_HUFF_VAL(0, 28), AC_HUFF_VAL(0, 27), AC_HUFF_VAL(0, 26), AC_HUFF_VAL(0, 25),
AC_HUFF_VAL(0, 24), AC_HUFF_VAL(0, 23), AC_HUFF_VAL(0, 22), AC_HUFF_VAL(0, 21), AC_HUFF_VAL(0, 20),
AC_HUFF_VAL(0, 19), AC_HUFF_VAL(0, 18), AC_HUFF_VAL(0, 17), AC_HUFF_VAL(0, 16), AC_HUFF_VAL(0, 40),
AC_HUFF_VAL(0, 39), AC_HUFF_VAL(0, 38), AC_HUFF_VAL(0, 37), AC_HUFF_VAL(0, 36), AC_HUFF_VAL(0, 35),
AC_HUFF_VAL(0, 34), AC_HUFF_VAL(0, 33), AC_HUFF_VAL(0, 32), AC_HUFF_VAL(1, 14), AC_HUFF_VAL(1, 13),
AC_HUFF_VAL(1, 12), AC_HUFF_VAL(1, 11), AC_HUFF_VAL(1, 10), AC_HUFF_VAL(1, 9), AC_HUFF_VAL(1, 8),
AC_HUFF_VAL(1, 18), AC_HUFF_VAL(1, 17), AC_HUFF_VAL(1, 16), AC_HUFF_VAL(1, 15), AC_HUFF_VAL(6, 3),
AC_HUFF_VAL(16, 2), AC_HUFF_VAL(15, 2), AC_HUFF_VAL(14, 2), AC_HUFF_VAL(13, 2), AC_HUFF_VAL(12, 2),
AC_HUFF_VAL(11, 2), AC_HUFF_VAL(31, 1), AC_HUFF_VAL(30, 1), AC_HUFF_VAL(29, 1), AC_HUFF_VAL(28, 1),
AC_HUFF_VAL(27, 1),
// Escape code
ESCAPE_CODE,
// End of block code
END_OF_BLOCK
};
PSXStreamDecoder::PSXStreamDecoder(CDSpeed speed, uint32 frameCount) : _speed(speed), _frameCount(frameCount) {
_stream = 0;
_videoTrack = 0;
_audioTrack = 0;
}
PSXStreamDecoder::~PSXStreamDecoder() {
close();
}
#define RAW_CD_SECTOR_SIZE 2352
#define CDXA_TYPE_MASK 0x0E
#define CDXA_TYPE_DATA 0x08
#define CDXA_TYPE_AUDIO 0x04
#define CDXA_TYPE_VIDEO 0x02
bool PSXStreamDecoder::loadStream(Common::SeekableReadStream *stream) {
close();
_stream = stream;
readNextPacket();
return true;
}
void PSXStreamDecoder::close() {
VideoDecoder::close();
_audioTrack = 0;
_videoTrack = 0;
_frameCount = 0;
delete _stream;
_stream = 0;
}
#define VIDEO_DATA_CHUNK_SIZE 2016
#define VIDEO_DATA_HEADER_SIZE 56
void PSXStreamDecoder::readNextPacket() {
Common::SeekableReadStream *sector = 0;
byte *partialFrame = 0;
int sectorsRead = 0;
int64 prevPos = _stream->pos();
while (_stream->pos() < _stream->size()) {
sector = readSector();
sectorsRead++;
if (!sector)
error("Corrupt PSX stream sector");
sector->seek(0x11);
byte channel = sector->readByte();
if (channel >= 32) {
warning("Bad PSX stream channel");
return;
}
byte sectorType = sector->readByte() & CDXA_TYPE_MASK;
switch (sectorType) {
case CDXA_TYPE_DATA:
case CDXA_TYPE_VIDEO: {
if (!_videoTrack) {
_videoTrack = new PSXVideoTrack(sector, _speed, _frameCount, channel);
addTrack(_videoTrack);
// If no video track is initialized, we are called
// by loadStream(). Stop here, and start rendering
// the track from the next call.
_stream->seek(prevPos);
return;
}
if (_videoTrack->getChannel() != channel) {
warning("Unhandled multi-channel video");
return;
}
sector->seek(28);
uint16 curSector = sector->readUint16LE();
uint16 sectorCount = sector->readUint16LE();
sector->readUint32LE();
uint16 frameSize = sector->readUint32LE();
if (curSector >= sectorCount)
error("Bad sector");
if (!partialFrame)
partialFrame = (byte *)malloc(sectorCount * VIDEO_DATA_CHUNK_SIZE);
sector->seek(VIDEO_DATA_HEADER_SIZE);
sector->read(partialFrame + curSector * VIDEO_DATA_CHUNK_SIZE, VIDEO_DATA_CHUNK_SIZE);
if (curSector == sectorCount - 1) {
// Done assembling the frame
Common::BitStreamMemoryStream *frame = new Common::BitStreamMemoryStream(partialFrame, frameSize, DisposeAfterUse::YES);
_videoTrack->decodeFrame(frame, sectorsRead);
delete frame;
delete sector;
return;
}
break;
}
case CDXA_TYPE_AUDIO: {
// We only handle one audio channel so far
if (!_audioTrack) {
_audioTrack = new PSXAudioTrack(sector, getSoundType(), channel);
addTrack(_audioTrack);
}
if (_audioTrack->getChannel() != channel) {
warning("Unhandled multi-channel audio");
}
_audioTrack->queueAudioFromSector(sector);
break;
}
default:
// This shows up way too often, but the other sectors
// are safe to ignore
//warning("Unknown PSX sector type 0x%x", sectorType);
break;
}
delete sector;
}
if (_stream->pos() >= _stream->size()) {
if (_videoTrack)
_videoTrack->setEndOfTrack();
if (_audioTrack)
_audioTrack->setEndOfTrack();
}
}
bool PSXStreamDecoder::useAudioSync() const {
// Audio sync is disabled since most audio data comes after video
// data.
return false;
}
static const byte s_syncHeader[12] = { 0x00, 0xff ,0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 };
Common::SeekableReadStream *PSXStreamDecoder::readSector() {
assert(_stream);
Common::SeekableReadStream *stream = _stream->readStream(RAW_CD_SECTOR_SIZE);
byte syncHeader[12];
stream->read(syncHeader, 12);
if (!memcmp(s_syncHeader, syncHeader, 12))
return stream;
return 0;
}
// Ha! It's palindromic!
#define AUDIO_DATA_CHUNK_SIZE 2304
#define AUDIO_DATA_SAMPLE_COUNT 4032
PSXStreamDecoder::PSXAudioTrack::PSXAudioTrack(Common::SeekableReadStream *sector, Audio::Mixer::SoundType soundType, byte channel) :
AudioTrack(soundType), _channel(channel) {
assert(sector);
_endOfTrack = false;
sector->seek(19);
byte format = sector->readByte();
_stereo = (format & (1 << 0)) != 0;
_rate = (format & (1 << 2)) ? 18900 : 37800;
_audStream = Audio::makeQueuingAudioStream(_rate, _stereo);
}
PSXStreamDecoder::PSXAudioTrack::~PSXAudioTrack() {
delete _audStream;
}
bool PSXStreamDecoder::PSXAudioTrack::endOfTrack() const {
return AudioTrack::endOfTrack() && _endOfTrack;
}
void PSXStreamDecoder::PSXAudioTrack::queueAudioFromSector(Common::SeekableReadStream *sector) {
assert(sector);
sector->seek(24);
// Read the specified sector into memory
Common::SeekableReadStream *compressedAudioStream = sector->readStream(AUDIO_DATA_CHUNK_SIZE);
Audio::SeekableAudioStream *audioStream = Audio::makeADPCMStream(compressedAudioStream, DisposeAfterUse::YES, AUDIO_DATA_CHUNK_SIZE, Audio::kADPCMXA, _rate, _stereo ? 2 : 1);
if (audioStream) {
_audStream->queueAudioStream(audioStream, DisposeAfterUse::YES);
} else {
// in case there was an error
delete compressedAudioStream;
}
}
Audio::AudioStream *PSXStreamDecoder::PSXAudioTrack::getAudioStream() const {
return _audStream;
}
PSXStreamDecoder::PSXVideoTrack::PSXVideoTrack(Common::SeekableReadStream *firstSector, CDSpeed speed, int frameCount, byte channel) :
_nextFrameStartTime(0, speed), _frameCount(frameCount), _channel(channel), _surface(nullptr) {
assert(firstSector);
firstSector->seek(40);
_width = firstSector->readUint16LE();
_height = firstSector->readUint16LE();
_pixelFormat = Image::Codec::getDefaultYUVFormat();
_macroBlocksW = (_width + 15) / 16;
_macroBlocksH = (_height + 15) / 16;
_yBuffer = new byte[_macroBlocksW * _macroBlocksH * 16 * 16];
_cbBuffer = new byte[_macroBlocksW * _macroBlocksH * 8 * 8];
_crBuffer = new byte[_macroBlocksW * _macroBlocksH * 8 * 8];
_endOfTrack = false;
_curFrame = -1;
_acHuffman = new HuffmanDecoder(0, AC_CODE_COUNT, s_huffmanACCodes, s_huffmanACLengths, s_huffmanACSymbols);
_dcHuffmanChroma = new HuffmanDecoder(0, DC_CODE_COUNT, s_huffmanDCChromaCodes, s_huffmanDCChromaLengths, s_huffmanDCSymbols);
_dcHuffmanLuma = new HuffmanDecoder(0, DC_CODE_COUNT, s_huffmanDCLumaCodes, s_huffmanDCLumaLengths, s_huffmanDCSymbols);
}
PSXStreamDecoder::PSXVideoTrack::~PSXVideoTrack() {
if (_surface) {
_surface->free();
delete _surface;
}
delete[] _yBuffer;
delete[] _cbBuffer;
delete[] _crBuffer;
delete _acHuffman;
delete _dcHuffmanChroma;
delete _dcHuffmanLuma;
}
uint32 PSXStreamDecoder::PSXVideoTrack::getNextFrameStartTime() const {
return _nextFrameStartTime.msecs();
}
const Graphics::Surface *PSXStreamDecoder::PSXVideoTrack::decodeNextFrame() {
return _surface;
}
void PSXStreamDecoder::PSXVideoTrack::decodeFrame(Common::BitStreamMemoryStream *frame, uint sectorCount) {
if (!_surface) {
_surface = new Graphics::Surface();
_surface->create(_width, _height, _pixelFormat);
}
// A frame is essentially an MPEG-1 intra frame
Common::BitStreamMemory16LEMSB bits(frame);
bits.skip(16); // unknown
bits.skip(16); // 0x3800
uint16 scale = bits.getBits<16>();
uint16 version = bits.getBits<16>();
if (version != 2 && version != 3) {
warning("Unknown PSX stream frame version");
return;
}
// Initalize default v3 DC here
_lastDC[0] = _lastDC[1] = _lastDC[2] = 0;
for (int mbX = 0; mbX < _macroBlocksW; mbX++)
for (int mbY = 0; mbY < _macroBlocksH; mbY++)
decodeMacroBlock(&bits, mbX, mbY, scale, version);
// Output data onto the frame
YUVToRGBMan.convert420(_surface, Graphics::YUVToRGBManager::kScaleFull, _yBuffer, _cbBuffer, _crBuffer, _surface->w, _surface->h, _macroBlocksW * 16, _macroBlocksW * 8);
_curFrame++;
// Increase the time by the amount of sectors we read
// One may notice that this is still not the most precise
// method since a frame takes up the time its sectors took
// up instead of the amount of time it takes the next frame
// to be read from the sectors. The actual frame rate should
// be constant instead of variable, so the slight difference
// in a frame's showing time is negligible (1/150 of a second).
_nextFrameStartTime = _nextFrameStartTime.addFrames(sectorCount);
}
void PSXStreamDecoder::PSXVideoTrack::decodeMacroBlock(Common::BitStreamMemory16LEMSB *bits, int mbX, int mbY, uint16 scale, uint16 version) {
int pitchY = _macroBlocksW * 16;
int pitchC = _macroBlocksW * 8;
// Note the strange order of red before blue
decodeBlock(bits, _crBuffer + (mbY * pitchC + mbX) * 8, pitchC, scale, version, kPlaneV);
decodeBlock(bits, _cbBuffer + (mbY * pitchC + mbX) * 8, pitchC, scale, version, kPlaneU);
decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16, pitchY, scale, version, kPlaneY);
decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16 + 8, pitchY, scale, version, kPlaneY);
decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16 + 8 * pitchY, pitchY, scale, version, kPlaneY);
decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16 + 8 * pitchY + 8, pitchY, scale, version, kPlaneY);
}
// Standard JPEG/MPEG zig zag table
static const byte s_zigZagTable[8 * 8] = {
0, 1, 5, 6, 14, 15, 27, 28,
2, 4, 7, 13, 16, 26, 29, 42,
3, 8, 12, 17, 25, 30, 41, 43,
9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54,
20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63
};
// One byte different from the standard MPEG-1 table
static const byte s_quantizationTable[8 * 8] = {
2, 16, 19, 22, 26, 27, 29, 34,
16, 16, 22, 24, 27, 29, 34, 37,
19, 22, 26, 27, 29, 34, 34, 38,
22, 22, 26, 27, 29, 34, 37, 40,
22, 26, 27, 29, 32, 35, 40, 48,
26, 27, 29, 32, 35, 40, 48, 58,
26, 27, 29, 34, 38, 46, 56, 69,
27, 29, 35, 38, 46, 56, 69, 83
};
void PSXStreamDecoder::PSXVideoTrack::dequantizeBlock(int *coefficients, float *block, uint16 scale) {
// Dequantize the data, un-zig-zagging as we go along
for (int i = 0; i < 8 * 8; i++) {
if (i == 0) // Special case for the DC coefficient
block[i] = coefficients[i] * s_quantizationTable[i];
else
block[i] = (float)coefficients[s_zigZagTable[i]] * s_quantizationTable[i] * scale / 8;
}
}
int PSXStreamDecoder::PSXVideoTrack::readDC(Common::BitStreamMemory16LEMSB *bits, uint16 version, PlaneType plane) {
// Version 2 just has its coefficient as 10-bits
if (version == 2)
return readSignedCoefficient(bits);
// Version 3 has it stored as huffman codes as a difference from the previous DC value
HuffmanDecoder *huffman = (plane == kPlaneY) ? _dcHuffmanLuma : _dcHuffmanChroma;
uint32 symbol = huffman->getSymbol(*bits);
int dc = 0;
if (GET_DC_BITS(symbol) != 0) {
bool negative = (bits->getBit() == 0);
dc = bits->getBits(GET_DC_BITS(symbol) - 1);
if (negative)
dc -= GET_DC_NEG(symbol);
else
dc += GET_DC_POS(symbol);
}
_lastDC[plane] += dc * 4; // convert from 8-bit to 10-bit
return _lastDC[plane];
}
#define BLOCK_OVERFLOW_CHECK() \
if (count > 63) \
error("PSXStreamDecoder::readAC(): Too many coefficients")
void PSXStreamDecoder::PSXVideoTrack::readAC(Common::BitStreamMemory16LEMSB *bits, int *block) {
// Clear the block first
for (int i = 0; i < 63; i++)
block[i] = 0;
int count = 0;
while (!bits->eos()) {
uint32 symbol = _acHuffman->getSymbol(*bits);
if (symbol == ESCAPE_CODE) {
// The escape code!
int zeroes = bits->getBits<6>();
count += zeroes + 1;
BLOCK_OVERFLOW_CHECK();
block += zeroes;
*block++ = readSignedCoefficient(bits);
} else if (symbol == END_OF_BLOCK) {
// We're done
break;
} else {
// Normal huffman code
int zeroes = GET_AC_ZERO_RUN(symbol);
count += zeroes + 1;
BLOCK_OVERFLOW_CHECK();
block += zeroes;
if (bits->getBit())
*block++ = -GET_AC_COEFFICIENT(symbol);
else
*block++ = GET_AC_COEFFICIENT(symbol);
}
}
}
int PSXStreamDecoder::PSXVideoTrack::readSignedCoefficient(Common::BitStreamMemory16LEMSB *bits) {
uint val = bits->getBits<10>();
// extend the sign
uint shift = 8 * sizeof(int) - 10;
return (int)(val << shift) >> shift;
}
// IDCT table built with :
// _idct8x8[x][y] = cos(((2 * x + 1) * y) * (M_PI / 16.0)) * 0.5;
// _idct8x8[x][y] /= sqrt(2.0) if y == 0
static const double s_idct8x8[8][8] = {
{ 0.353553390593274, 0.490392640201615, 0.461939766255643, 0.415734806151273, 0.353553390593274, 0.277785116509801, 0.191341716182545, 0.097545161008064 },
{ 0.353553390593274, 0.415734806151273, 0.191341716182545, -0.097545161008064, -0.353553390593274, -0.490392640201615, -0.461939766255643, -0.277785116509801 },
{ 0.353553390593274, 0.277785116509801, -0.191341716182545, -0.490392640201615, -0.353553390593274, 0.097545161008064, 0.461939766255643, 0.415734806151273 },
{ 0.353553390593274, 0.097545161008064, -0.461939766255643, -0.277785116509801, 0.353553390593274, 0.415734806151273, -0.191341716182545, -0.490392640201615 },
{ 0.353553390593274, -0.097545161008064, -0.461939766255643, 0.277785116509801, 0.353553390593274, -0.415734806151273, -0.191341716182545, 0.490392640201615 },
{ 0.353553390593274, -0.277785116509801, -0.191341716182545, 0.490392640201615, -0.353553390593273, -0.097545161008064, 0.461939766255643, -0.415734806151273 },
{ 0.353553390593274, -0.415734806151273, 0.191341716182545, 0.097545161008064, -0.353553390593274, 0.490392640201615, -0.461939766255643, 0.277785116509801 },
{ 0.353553390593274, -0.490392640201615, 0.461939766255643, -0.415734806151273, 0.353553390593273, -0.277785116509801, 0.191341716182545, -0.097545161008064 }
};
void PSXStreamDecoder::PSXVideoTrack::idct(float *dequantData, float *result) {
// IDCT code based on JPEG's IDCT code
// TODO: Switch to the integer-based one mentioned in the docs
// This is by far the costliest operation here
float tmp[8 * 8];
// Apply 1D IDCT to rows
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
tmp[y + x * 8] = dequantData[0] * s_idct8x8[x][0]
+ dequantData[1] * s_idct8x8[x][1]
+ dequantData[2] * s_idct8x8[x][2]
+ dequantData[3] * s_idct8x8[x][3]
+ dequantData[4] * s_idct8x8[x][4]
+ dequantData[5] * s_idct8x8[x][5]
+ dequantData[6] * s_idct8x8[x][6]
+ dequantData[7] * s_idct8x8[x][7];
}
dequantData += 8;
}
// Apply 1D IDCT to columns
for (int x = 0; x < 8; x++) {
const float *u = tmp + x * 8;
for (int y = 0; y < 8; y++) {
result[y * 8 + x] = u[0] * s_idct8x8[y][0]
+ u[1] * s_idct8x8[y][1]
+ u[2] * s_idct8x8[y][2]
+ u[3] * s_idct8x8[y][3]
+ u[4] * s_idct8x8[y][4]
+ u[5] * s_idct8x8[y][5]
+ u[6] * s_idct8x8[y][6]
+ u[7] * s_idct8x8[y][7];
}
}
}
void PSXStreamDecoder::PSXVideoTrack::decodeBlock(Common::BitStreamMemory16LEMSB *bits, byte *block, int pitch, uint16 scale, uint16 version, PlaneType plane) {
// Version 2 just has signed 10 bits for DC
// Version 3 has them huffman coded
int coefficients[8 * 8];
coefficients[0] = readDC(bits, version, plane);
readAC(bits, &coefficients[1]); // Read in the AC
// Dequantize
float dequantData[8 * 8];
dequantizeBlock(coefficients, dequantData, scale);
// Perform IDCT
float idctData[8 * 8];
idct(dequantData, idctData);
// Now output the data
for (int y = 0; y < 8; y++) {
byte *dst = block + pitch * y;
// Convert the result to be in the range [0, 255]
for (int x = 0; x < 8; x++)
*dst++ = (int)CLIP<float>(idctData[y * 8 + x], -128.0f, 127.0f) + 128;
}
}
} // End of namespace Video

171
video/psx_decoder.h Normal file
View File

@@ -0,0 +1,171 @@
/* 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 VIDEO_PSX_DECODER_H
#define VIDEO_PSX_DECODER_H
#include "common/bitstream.h"
#include "common/endian.h"
#include "common/rational.h"
#include "common/rect.h"
#include "common/str.h"
#include "graphics/surface.h"
#include "video/video_decoder.h"
namespace Audio {
class QueuingAudioStream;
}
namespace Common {
template <class BITSTREAM>
class Huffman;
}
namespace Graphics {
struct PixelFormat;
}
namespace Video {
/**
* Decoder for PSX stream videos.
* This currently implements the most basic PSX stream format that is
* used by most games on the system. Special variants are not supported
* at this time.
*
* Video decoder used in engines:
* - sword1 (psx)
* - sword2 (psx)
*/
class PSXStreamDecoder : public VideoDecoder {
public:
// CD speed in sectors/second
// Calling code should use these enum values instead of the constants
enum CDSpeed {
kCD1x = 75,
kCD2x = 150
};
PSXStreamDecoder(CDSpeed speed, uint32 frameCount = 0);
virtual ~PSXStreamDecoder() override;
bool loadStream(Common::SeekableReadStream *stream) override;
void close() override;
protected:
void readNextPacket() override;
bool useAudioSync() const override;
private:
class PSXVideoTrack : public VideoTrack {
public:
PSXVideoTrack(Common::SeekableReadStream *firstSector, CDSpeed speed, int frameCount, byte channel);
~PSXVideoTrack() override;
byte getChannel() const { return _channel; }
uint16 getWidth() const override { return _width; }
uint16 getHeight() const override { return _height; }
Graphics::PixelFormat getPixelFormat() const override { return _pixelFormat; }
bool setOutputPixelFormat(const Graphics::PixelFormat &format) override {
if (format.bytesPerPixel != 2 && format.bytesPerPixel != 4)
return false;
_pixelFormat = format;
return true;
}
bool endOfTrack() const override { return _endOfTrack; }
int getCurFrame() const override { return _curFrame; }
int getFrameCount() const override { return _frameCount; }
uint32 getNextFrameStartTime() const override;
const Graphics::Surface *decodeNextFrame() override;
void setEndOfTrack() { _endOfTrack = true; }
void decodeFrame(Common::BitStreamMemoryStream *frame, uint sectorCount);
private:
Graphics::Surface *_surface;
Graphics::PixelFormat _pixelFormat;
uint16 _width;
uint16 _height;
uint32 _frameCount;
Audio::Timestamp _nextFrameStartTime;
byte _channel;
bool _endOfTrack;
int _curFrame;
enum PlaneType {
kPlaneY = 0,
kPlaneU = 1,
kPlaneV = 2
};
typedef Common::Huffman<Common::BitStreamMemory16LEMSB> HuffmanDecoder;
uint16 _macroBlocksW, _macroBlocksH;
byte *_yBuffer, *_cbBuffer, *_crBuffer;
void decodeMacroBlock(Common::BitStreamMemory16LEMSB *bits, int mbX, int mbY, uint16 scale, uint16 version);
void decodeBlock(Common::BitStreamMemory16LEMSB *bits, byte *block, int pitch, uint16 scale, uint16 version, PlaneType plane);
void readAC(Common::BitStreamMemory16LEMSB *bits, int *block);
HuffmanDecoder *_acHuffman;
int readDC(Common::BitStreamMemory16LEMSB *bits, uint16 version, PlaneType plane);
HuffmanDecoder *_dcHuffmanLuma, *_dcHuffmanChroma;
int _lastDC[3];
void dequantizeBlock(int *coefficients, float *block, uint16 scale);
void idct(float *dequantData, float *result);
int readSignedCoefficient(Common::BitStreamMemory16LEMSB *bits);
};
class PSXAudioTrack : public AudioTrack {
public:
PSXAudioTrack(Common::SeekableReadStream *sector, Audio::Mixer::SoundType soundType, byte channel);
~PSXAudioTrack() override;
byte getChannel() const { return _channel; }
bool endOfTrack() const override;
void setEndOfTrack() { _endOfTrack = true; }
void queueAudioFromSector(Common::SeekableReadStream *sector);
private:
Audio::AudioStream *getAudioStream() const override;
Audio::QueuingAudioStream *_audStream;
byte _channel;
bool _endOfTrack;
bool _stereo;
uint _rate;
};
CDSpeed _speed;
uint32 _frameCount;
Common::SeekableReadStream *_stream;
PSXVideoTrack *_videoTrack;
PSXAudioTrack *_audioTrack;
Common::SeekableReadStream *readSector();
};
} // End of namespace Video
#endif

323
video/qt_data.h Normal file
View File

@@ -0,0 +1,323 @@
/* 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/>.
*
*/
/*
* QuickTime palettes from FFmpeg
* Copyright (c) 2001 Fabrice Bellard
* Copyright (c) 2009 Baptiste Coudurier <baptiste dot coudurier at gmail dot com>
* Copyright (c) 2015 Mats Peterson
*/
#ifndef VIDEO_QT_DATA_H
#define VIDEO_QT_DATA_H
namespace Video {
/* From a screenshot of the "Monitors & Sound" control panel in Mac OS 7.5.5 */
static const byte quickTimeDefaultPalette4[4 * 3] = {
0xFF, 0xFF, 0xFF,
0xAC, 0xAC, 0xAC,
0x55, 0x55, 0x55,
0x00, 0x00, 0x00
};
/* From a screenshot of the "Monitors & Sound" control panel in Mac OS 7.5.5 */
static const byte quickTimeDefaultPalette16[16 * 3] = {
0xFF, 0xFF, 0xFF,
0xFC, 0xF3, 0x05,
0xFF, 0x64, 0x02,
0xDD, 0x08, 0x06,
0xF2, 0x08, 0x84,
0x46, 0x00, 0xA5,
0x00, 0x00, 0xD4,
0x02, 0xAB, 0xEA,
0x1F, 0xB7, 0x14,
0x00, 0x64, 0x11,
0x56, 0x2C, 0x05,
0x90, 0x71, 0x3A,
0xC0, 0xC0, 0xC0,
0x80, 0x80, 0x80,
0x40, 0x40, 0x40,
0x00, 0x00, 0x00
};
static const byte quickTimeDefaultPalette256[256 * 3] = {
/* 0, 0x00 */ 0xFF, 0xFF, 0xFF,
/* 1, 0x01 */ 0xFF, 0xFF, 0xCC,
/* 2, 0x02 */ 0xFF, 0xFF, 0x99,
/* 3, 0x03 */ 0xFF, 0xFF, 0x66,
/* 4, 0x04 */ 0xFF, 0xFF, 0x33,
/* 5, 0x05 */ 0xFF, 0xFF, 0x00,
/* 6, 0x06 */ 0xFF, 0xCC, 0xFF,
/* 7, 0x07 */ 0xFF, 0xCC, 0xCC,
/* 8, 0x08 */ 0xFF, 0xCC, 0x99,
/* 9, 0x09 */ 0xFF, 0xCC, 0x66,
/* 10, 0x0A */ 0xFF, 0xCC, 0x33,
/* 11, 0x0B */ 0xFF, 0xCC, 0x00,
/* 12, 0x0C */ 0xFF, 0x99, 0xFF,
/* 13, 0x0D */ 0xFF, 0x99, 0xCC,
/* 14, 0x0E */ 0xFF, 0x99, 0x99,
/* 15, 0x0F */ 0xFF, 0x99, 0x66,
/* 16, 0x10 */ 0xFF, 0x99, 0x33,
/* 17, 0x11 */ 0xFF, 0x99, 0x00,
/* 18, 0x12 */ 0xFF, 0x66, 0xFF,
/* 19, 0x13 */ 0xFF, 0x66, 0xCC,
/* 20, 0x14 */ 0xFF, 0x66, 0x99,
/* 21, 0x15 */ 0xFF, 0x66, 0x66,
/* 22, 0x16 */ 0xFF, 0x66, 0x33,
/* 23, 0x17 */ 0xFF, 0x66, 0x00,
/* 24, 0x18 */ 0xFF, 0x33, 0xFF,
/* 25, 0x19 */ 0xFF, 0x33, 0xCC,
/* 26, 0x1A */ 0xFF, 0x33, 0x99,
/* 27, 0x1B */ 0xFF, 0x33, 0x66,
/* 28, 0x1C */ 0xFF, 0x33, 0x33,
/* 29, 0x1D */ 0xFF, 0x33, 0x00,
/* 30, 0x1E */ 0xFF, 0x00, 0xFF,
/* 31, 0x1F */ 0xFF, 0x00, 0xCC,
/* 32, 0x20 */ 0xFF, 0x00, 0x99,
/* 33, 0x21 */ 0xFF, 0x00, 0x66,
/* 34, 0x22 */ 0xFF, 0x00, 0x33,
/* 35, 0x23 */ 0xFF, 0x00, 0x00,
/* 36, 0x24 */ 0xCC, 0xFF, 0xFF,
/* 37, 0x25 */ 0xCC, 0xFF, 0xCC,
/* 38, 0x26 */ 0xCC, 0xFF, 0x99,
/* 39, 0x27 */ 0xCC, 0xFF, 0x66,
/* 40, 0x28 */ 0xCC, 0xFF, 0x33,
/* 41, 0x29 */ 0xCC, 0xFF, 0x00,
/* 42, 0x2A */ 0xCC, 0xCC, 0xFF,
/* 43, 0x2B */ 0xCC, 0xCC, 0xCC,
/* 44, 0x2C */ 0xCC, 0xCC, 0x99,
/* 45, 0x2D */ 0xCC, 0xCC, 0x66,
/* 46, 0x2E */ 0xCC, 0xCC, 0x33,
/* 47, 0x2F */ 0xCC, 0xCC, 0x00,
/* 48, 0x30 */ 0xCC, 0x99, 0xFF,
/* 49, 0x31 */ 0xCC, 0x99, 0xCC,
/* 50, 0x32 */ 0xCC, 0x99, 0x99,
/* 51, 0x33 */ 0xCC, 0x99, 0x66,
/* 52, 0x34 */ 0xCC, 0x99, 0x33,
/* 53, 0x35 */ 0xCC, 0x99, 0x00,
/* 54, 0x36 */ 0xCC, 0x66, 0xFF,
/* 55, 0x37 */ 0xCC, 0x66, 0xCC,
/* 56, 0x38 */ 0xCC, 0x66, 0x99,
/* 57, 0x39 */ 0xCC, 0x66, 0x66,
/* 58, 0x3A */ 0xCC, 0x66, 0x33,
/* 59, 0x3B */ 0xCC, 0x66, 0x00,
/* 60, 0x3C */ 0xCC, 0x33, 0xFF,
/* 61, 0x3D */ 0xCC, 0x33, 0xCC,
/* 62, 0x3E */ 0xCC, 0x33, 0x99,
/* 63, 0x3F */ 0xCC, 0x33, 0x66,
/* 64, 0x40 */ 0xCC, 0x33, 0x33,
/* 65, 0x41 */ 0xCC, 0x33, 0x00,
/* 66, 0x42 */ 0xCC, 0x00, 0xFF,
/* 67, 0x43 */ 0xCC, 0x00, 0xCC,
/* 68, 0x44 */ 0xCC, 0x00, 0x99,
/* 69, 0x45 */ 0xCC, 0x00, 0x66,
/* 70, 0x46 */ 0xCC, 0x00, 0x33,
/* 71, 0x47 */ 0xCC, 0x00, 0x00,
/* 72, 0x48 */ 0x99, 0xFF, 0xFF,
/* 73, 0x49 */ 0x99, 0xFF, 0xCC,
/* 74, 0x4A */ 0x99, 0xFF, 0x99,
/* 75, 0x4B */ 0x99, 0xFF, 0x66,
/* 76, 0x4C */ 0x99, 0xFF, 0x33,
/* 77, 0x4D */ 0x99, 0xFF, 0x00,
/* 78, 0x4E */ 0x99, 0xCC, 0xFF,
/* 79, 0x4F */ 0x99, 0xCC, 0xCC,
/* 80, 0x50 */ 0x99, 0xCC, 0x99,
/* 81, 0x51 */ 0x99, 0xCC, 0x66,
/* 82, 0x52 */ 0x99, 0xCC, 0x33,
/* 83, 0x53 */ 0x99, 0xCC, 0x00,
/* 84, 0x54 */ 0x99, 0x99, 0xFF,
/* 85, 0x55 */ 0x99, 0x99, 0xCC,
/* 86, 0x56 */ 0x99, 0x99, 0x99,
/* 87, 0x57 */ 0x99, 0x99, 0x66,
/* 88, 0x58 */ 0x99, 0x99, 0x33,
/* 89, 0x59 */ 0x99, 0x99, 0x00,
/* 90, 0x5A */ 0x99, 0x66, 0xFF,
/* 91, 0x5B */ 0x99, 0x66, 0xCC,
/* 92, 0x5C */ 0x99, 0x66, 0x99,
/* 93, 0x5D */ 0x99, 0x66, 0x66,
/* 94, 0x5E */ 0x99, 0x66, 0x33,
/* 95, 0x5F */ 0x99, 0x66, 0x00,
/* 96, 0x60 */ 0x99, 0x33, 0xFF,
/* 97, 0x61 */ 0x99, 0x33, 0xCC,
/* 98, 0x62 */ 0x99, 0x33, 0x99,
/* 99, 0x63 */ 0x99, 0x33, 0x66,
/* 100, 0x64 */ 0x99, 0x33, 0x33,
/* 101, 0x65 */ 0x99, 0x33, 0x00,
/* 102, 0x66 */ 0x99, 0x00, 0xFF,
/* 103, 0x67 */ 0x99, 0x00, 0xCC,
/* 104, 0x68 */ 0x99, 0x00, 0x99,
/* 105, 0x69 */ 0x99, 0x00, 0x66,
/* 106, 0x6A */ 0x99, 0x00, 0x33,
/* 107, 0x6B */ 0x99, 0x00, 0x00,
/* 108, 0x6C */ 0x66, 0xFF, 0xFF,
/* 109, 0x6D */ 0x66, 0xFF, 0xCC,
/* 110, 0x6E */ 0x66, 0xFF, 0x99,
/* 111, 0x6F */ 0x66, 0xFF, 0x66,
/* 112, 0x70 */ 0x66, 0xFF, 0x33,
/* 113, 0x71 */ 0x66, 0xFF, 0x00,
/* 114, 0x72 */ 0x66, 0xCC, 0xFF,
/* 115, 0x73 */ 0x66, 0xCC, 0xCC,
/* 116, 0x74 */ 0x66, 0xCC, 0x99,
/* 117, 0x75 */ 0x66, 0xCC, 0x66,
/* 118, 0x76 */ 0x66, 0xCC, 0x33,
/* 119, 0x77 */ 0x66, 0xCC, 0x00,
/* 120, 0x78 */ 0x66, 0x99, 0xFF,
/* 121, 0x79 */ 0x66, 0x99, 0xCC,
/* 122, 0x7A */ 0x66, 0x99, 0x99,
/* 123, 0x7B */ 0x66, 0x99, 0x66,
/* 124, 0x7C */ 0x66, 0x99, 0x33,
/* 125, 0x7D */ 0x66, 0x99, 0x00,
/* 126, 0x7E */ 0x66, 0x66, 0xFF,
/* 127, 0x7F */ 0x66, 0x66, 0xCC,
/* 128, 0x80 */ 0x66, 0x66, 0x99,
/* 129, 0x81 */ 0x66, 0x66, 0x66,
/* 130, 0x82 */ 0x66, 0x66, 0x33,
/* 131, 0x83 */ 0x66, 0x66, 0x00,
/* 132, 0x84 */ 0x66, 0x33, 0xFF,
/* 133, 0x85 */ 0x66, 0x33, 0xCC,
/* 134, 0x86 */ 0x66, 0x33, 0x99,
/* 135, 0x87 */ 0x66, 0x33, 0x66,
/* 136, 0x88 */ 0x66, 0x33, 0x33,
/* 137, 0x89 */ 0x66, 0x33, 0x00,
/* 138, 0x8A */ 0x66, 0x00, 0xFF,
/* 139, 0x8B */ 0x66, 0x00, 0xCC,
/* 140, 0x8C */ 0x66, 0x00, 0x99,
/* 141, 0x8D */ 0x66, 0x00, 0x66,
/* 142, 0x8E */ 0x66, 0x00, 0x33,
/* 143, 0x8F */ 0x66, 0x00, 0x00,
/* 144, 0x90 */ 0x33, 0xFF, 0xFF,
/* 145, 0x91 */ 0x33, 0xFF, 0xCC,
/* 146, 0x92 */ 0x33, 0xFF, 0x99,
/* 147, 0x93 */ 0x33, 0xFF, 0x66,
/* 148, 0x94 */ 0x33, 0xFF, 0x33,
/* 149, 0x95 */ 0x33, 0xFF, 0x00,
/* 150, 0x96 */ 0x33, 0xCC, 0xFF,
/* 151, 0x97 */ 0x33, 0xCC, 0xCC,
/* 152, 0x98 */ 0x33, 0xCC, 0x99,
/* 153, 0x99 */ 0x33, 0xCC, 0x66,
/* 154, 0x9A */ 0x33, 0xCC, 0x33,
/* 155, 0x9B */ 0x33, 0xCC, 0x00,
/* 156, 0x9C */ 0x33, 0x99, 0xFF,
/* 157, 0x9D */ 0x33, 0x99, 0xCC,
/* 158, 0x9E */ 0x33, 0x99, 0x99,
/* 159, 0x9F */ 0x33, 0x99, 0x66,
/* 160, 0xA0 */ 0x33, 0x99, 0x33,
/* 161, 0xA1 */ 0x33, 0x99, 0x00,
/* 162, 0xA2 */ 0x33, 0x66, 0xFF,
/* 163, 0xA3 */ 0x33, 0x66, 0xCC,
/* 164, 0xA4 */ 0x33, 0x66, 0x99,
/* 165, 0xA5 */ 0x33, 0x66, 0x66,
/* 166, 0xA6 */ 0x33, 0x66, 0x33,
/* 167, 0xA7 */ 0x33, 0x66, 0x00,
/* 168, 0xA8 */ 0x33, 0x33, 0xFF,
/* 169, 0xA9 */ 0x33, 0x33, 0xCC,
/* 170, 0xAA */ 0x33, 0x33, 0x99,
/* 171, 0xAB */ 0x33, 0x33, 0x66,
/* 172, 0xAC */ 0x33, 0x33, 0x33,
/* 173, 0xAD */ 0x33, 0x33, 0x00,
/* 174, 0xAE */ 0x33, 0x00, 0xFF,
/* 175, 0xAF */ 0x33, 0x00, 0xCC,
/* 176, 0xB0 */ 0x33, 0x00, 0x99,
/* 177, 0xB1 */ 0x33, 0x00, 0x66,
/* 178, 0xB2 */ 0x33, 0x00, 0x33,
/* 179, 0xB3 */ 0x33, 0x00, 0x00,
/* 180, 0xB4 */ 0x00, 0xFF, 0xFF,
/* 181, 0xB5 */ 0x00, 0xFF, 0xCC,
/* 182, 0xB6 */ 0x00, 0xFF, 0x99,
/* 183, 0xB7 */ 0x00, 0xFF, 0x66,
/* 184, 0xB8 */ 0x00, 0xFF, 0x33,
/* 185, 0xB9 */ 0x00, 0xFF, 0x00,
/* 186, 0xBA */ 0x00, 0xCC, 0xFF,
/* 187, 0xBB */ 0x00, 0xCC, 0xCC,
/* 188, 0xBC */ 0x00, 0xCC, 0x99,
/* 189, 0xBD */ 0x00, 0xCC, 0x66,
/* 190, 0xBE */ 0x00, 0xCC, 0x33,
/* 191, 0xBF */ 0x00, 0xCC, 0x00,
/* 192, 0xC0 */ 0x00, 0x99, 0xFF,
/* 193, 0xC1 */ 0x00, 0x99, 0xCC,
/* 194, 0xC2 */ 0x00, 0x99, 0x99,
/* 195, 0xC3 */ 0x00, 0x99, 0x66,
/* 196, 0xC4 */ 0x00, 0x99, 0x33,
/* 197, 0xC5 */ 0x00, 0x99, 0x00,
/* 198, 0xC6 */ 0x00, 0x66, 0xFF,
/* 199, 0xC7 */ 0x00, 0x66, 0xCC,
/* 200, 0xC8 */ 0x00, 0x66, 0x99,
/* 201, 0xC9 */ 0x00, 0x66, 0x66,
/* 202, 0xCA */ 0x00, 0x66, 0x33,
/* 203, 0xCB */ 0x00, 0x66, 0x00,
/* 204, 0xCC */ 0x00, 0x33, 0xFF,
/* 205, 0xCD */ 0x00, 0x33, 0xCC,
/* 206, 0xCE */ 0x00, 0x33, 0x99,
/* 207, 0xCF */ 0x00, 0x33, 0x66,
/* 208, 0xD0 */ 0x00, 0x33, 0x33,
/* 209, 0xD1 */ 0x00, 0x33, 0x00,
/* 210, 0xD2 */ 0x00, 0x00, 0xFF,
/* 211, 0xD3 */ 0x00, 0x00, 0xCC,
/* 212, 0xD4 */ 0x00, 0x00, 0x99,
/* 213, 0xD5 */ 0x00, 0x00, 0x66,
/* 214, 0xD6 */ 0x00, 0x00, 0x33,
/* 215, 0xD7 */ 0xEE, 0x00, 0x00,
/* 216, 0xD8 */ 0xDD, 0x00, 0x00,
/* 217, 0xD9 */ 0xBB, 0x00, 0x00,
/* 218, 0xDA */ 0xAA, 0x00, 0x00,
/* 219, 0xDB */ 0x88, 0x00, 0x00,
/* 220, 0xDC */ 0x77, 0x00, 0x00,
/* 221, 0xDD */ 0x55, 0x00, 0x00,
/* 222, 0xDE */ 0x44, 0x00, 0x00,
/* 223, 0xDF */ 0x22, 0x00, 0x00,
/* 224, 0xE0 */ 0x11, 0x00, 0x00,
/* 225, 0xE1 */ 0x00, 0xEE, 0x00,
/* 226, 0xE2 */ 0x00, 0xDD, 0x00,
/* 227, 0xE3 */ 0x00, 0xBB, 0x00,
/* 228, 0xE4 */ 0x00, 0xAA, 0x00,
/* 229, 0xE5 */ 0x00, 0x88, 0x00,
/* 230, 0xE6 */ 0x00, 0x77, 0x00,
/* 231, 0xE7 */ 0x00, 0x55, 0x00,
/* 232, 0xE8 */ 0x00, 0x44, 0x00,
/* 233, 0xE9 */ 0x00, 0x22, 0x00,
/* 234, 0xEA */ 0x00, 0x11, 0x00,
/* 235, 0xEB */ 0x00, 0x00, 0xEE,
/* 236, 0xEC */ 0x00, 0x00, 0xDD,
/* 237, 0xED */ 0x00, 0x00, 0xBB,
/* 238, 0xEE */ 0x00, 0x00, 0xAA,
/* 239, 0xEF */ 0x00, 0x00, 0x88,
/* 240, 0xF0 */ 0x00, 0x00, 0x77,
/* 241, 0xF1 */ 0x00, 0x00, 0x55,
/* 242, 0xF2 */ 0x00, 0x00, 0x44,
/* 243, 0xF3 */ 0x00, 0x00, 0x22,
/* 244, 0xF4 */ 0x00, 0x00, 0x11,
/* 245, 0xF5 */ 0xEE, 0xEE, 0xEE,
/* 246, 0xF6 */ 0xDD, 0xDD, 0xDD,
/* 247, 0xF7 */ 0xBB, 0xBB, 0xBB,
/* 248, 0xF8 */ 0xAA, 0xAA, 0xAA,
/* 249, 0xF9 */ 0x88, 0x88, 0x88,
/* 250, 0xFA */ 0x77, 0x77, 0x77,
/* 251, 0xFB */ 0x55, 0x55, 0x55,
/* 252, 0xFC */ 0x44, 0x44, 0x44,
/* 253, 0xFD */ 0x22, 0x22, 0x22,
/* 254, 0xFE */ 0x11, 0x11, 0x11,
/* 255, 0xFF */ 0x00, 0x00, 0x00
};
} // End of namespace Video
#endif

972
video/qt_decoder.cpp Normal file
View File

@@ -0,0 +1,972 @@
/* 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/>.
*
*/
//
// Partially based on ffmpeg code.
//
// Copyright (c) 2001 Fabrice Bellard.
// First version by Francois Revol revol@free.fr
// Seek function by Gael Chardon gael.dev@4now.net
//
#include "video/qt_decoder.h"
#include "video/qt_data.h"
#include "audio/audiostream.h"
#include "common/archive.h"
#include "common/debug.h"
#include "common/memstream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "common/compression/unzip.h"
#include "graphics/cursorman.h"
#include "image/icocur.h"
// Video codecs
#include "image/codecs/codec.h"
#include "image/codecs/dither.h"
namespace Video {
////////////////////////////////////////////
// QuickTimeDecoder
////////////////////////////////////////////
QuickTimeDecoder::QuickTimeDecoder() {
_scaledSurface = nullptr;
_width = _height = 0;
_enableEditListBoundsCheckQuirk = false;
}
QuickTimeDecoder::~QuickTimeDecoder() {
close();
}
bool QuickTimeDecoder::loadFile(const Common::Path &filename) {
debugC(1, kDebugLevelGVideo, "QuickTimeDecoder::loadFile(\"%s\")", filename.toString().c_str());
if (!Common::QuickTimeParser::parseFile(filename))
return false;
init();
return true;
}
bool QuickTimeDecoder::loadStream(Common::SeekableReadStream *stream) {
if (!Common::QuickTimeParser::parseStream(stream))
return false;
init();
return true;
}
void QuickTimeDecoder::close() {
VideoDecoder::close();
Common::QuickTimeParser::close();
if (_scaledSurface) {
_scaledSurface->free();
delete _scaledSurface;
_scaledSurface = 0;
}
closeQTVR();
}
const Graphics::Surface *QuickTimeDecoder::decodeNextFrame() {
const Graphics::Surface *frame = VideoDecoder::decodeNextFrame();
if (isVR())
updateAngles();
// Update audio buffers too
// (needs to be done after we find the next track)
updateAudioBuffer();
// We have to initialize the scaled surface
if (frame && (_scaleFactorX != 1 || _scaleFactorY != 1)) {
if (!_scaledSurface) {
_scaledSurface = new Graphics::Surface();
_scaledSurface->create(_width, _height, getPixelFormat());
}
scaleSurface(frame, _scaledSurface, _scaleFactorX, _scaleFactorY);
return _scaledSurface;
}
return frame;
}
Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format, uint32 descSize) {
if (track->codecType == CODEC_TYPE_VIDEO) {
debugC(0, kDebugLevelGVideo, "Video Codec FourCC: \'%s\'", tag2str(format));
VideoSampleDesc *entry = new VideoSampleDesc(track, format);
_fd->readUint16BE(); // version
_fd->readUint16BE(); // revision level
_fd->readUint32BE(); // vendor
_fd->readUint32BE(); // temporal quality
_fd->readUint32BE(); // spacial quality
uint16 width = _fd->readUint16BE(); // width
uint16 height = _fd->readUint16BE(); // height
// The width is most likely invalid for entries after the first one
// so only set the overall width if it is not zero here.
if (width)
track->width = width;
if (height)
track->height = height;
_fd->readUint32BE(); // horiz resolution
_fd->readUint32BE(); // vert resolution
_fd->readUint32BE(); // data size, always 0
_fd->readUint16BE(); // frames per samples
byte codecName[32];
_fd->read(codecName, 32); // codec name, pascal string (FIXME: true for mp4?)
if (codecName[0] <= 31) {
memcpy(entry->_codecName, &codecName[1], codecName[0]);
entry->_codecName[codecName[0]] = 0;
}
entry->_bitsPerSample = _fd->readUint16BE(); // depth
entry->_colorTableId = _fd->readUint16BE(); // colortable id
// figure out the palette situation
byte colorDepth = entry->_bitsPerSample & 0x1F;
bool colorGreyscale = (entry->_bitsPerSample & 0x20) != 0;
debugC(0, kDebugLevelGVideo, "color depth: %d", colorDepth);
// if the depth is 2, 4, or 8 bpp, file is palettized
if (colorDepth == 2 || colorDepth == 4 || colorDepth == 8) {
// Initialize the palette
entry->_palette.resize(256, false);
if (colorGreyscale) {
debugC(0, kDebugLevelGVideo, "Greyscale palette");
// compute the greyscale palette
uint16 colorCount = 1 << colorDepth;
int16 colorIndex = 255;
byte colorDec = 256 / (colorCount - 1);
for (uint16 j = 0; j < colorCount; j++) {
entry->_palette.set(j, colorIndex, colorIndex, colorIndex);
colorIndex -= colorDec;
if (colorIndex < 0)
colorIndex = 0;
}
} else if (entry->_colorTableId & 0x08) {
// if flag bit 3 is set, use the default palette
//uint16 colorCount = 1 << colorDepth;
debugC(0, kDebugLevelGVideo, "Predefined palette! %dbpp", colorDepth);
if (colorDepth == 2)
entry->_palette.set(quickTimeDefaultPalette4, 0, 4);
else if (colorDepth == 4)
entry->_palette.set(quickTimeDefaultPalette16, 0, 16);
else if (colorDepth == 8)
entry->_palette.set(quickTimeDefaultPalette256, 0, 256);
} else {
debugC(0, kDebugLevelGVideo, "Palette from file");
// load the palette from the file
uint32 colorStart = _fd->readUint32BE();
/* uint16 colorCount = */ _fd->readUint16BE();
uint16 colorEnd = _fd->readUint16BE();
for (uint32 j = colorStart; j <= colorEnd; j++) {
// each R, G, or B component is 16 bits;
// only use the top 8 bits; skip alpha bytes
// up front
_fd->readByte();
_fd->readByte();
byte r = _fd->readByte();
_fd->readByte();
byte g = _fd->readByte();
_fd->readByte();
byte b = _fd->readByte();
_fd->readByte();
entry->_palette.set(j, r, g, b);
}
}
entry->_bitsPerSample &= 0x1f; // clear grayscale bit
}
return entry;
} else if (track->codecType == CODEC_TYPE_PANO) {
return readPanoSampleDesc(track, format, descSize);
}
// Pass it on up
return Audio::QuickTimeAudioDecoder::readSampleDesc(track, format, descSize);
}
void QuickTimeDecoder::init() {
if (_qtvrType == QTVRType::OBJECT || _qtvrType == QTVRType::PANORAMA)
_isVR = true;
Audio::QuickTimeAudioDecoder::init();
// Initialize all the audio tracks
for (uint32 i = 0; i < _audioTracks.size(); i++)
addTrack(new AudioTrackHandler(this, _audioTracks[i]));
// Initialize all the video tracks
const Common::Array<Common::QuickTimeParser::Track *> &tracks = Common::QuickTimeParser::_tracks;
for (uint32 i = 0; i < tracks.size(); i++) {
if (tracks[i]->codecType == CODEC_TYPE_VIDEO) {
for (uint32 j = 0; j < tracks[i]->sampleDescs.size(); j++)
((VideoSampleDesc *)tracks[i]->sampleDescs[j])->initCodec();
addTrack(new VideoTrackHandler(this, tracks[i]));
tracks[i]->targetTrack = getNumTracks() - 1;
}
if (tracks[i]->codecType == CODEC_TYPE_PANO) {
addTrack(new PanoTrackHandler(this, tracks[i]));
tracks[i]->targetTrack = getNumTracks() - 1;
}
}
if (_qtvrType == QTVRType::PANORAMA) {
for (uint32 i = 0; i < Common::QuickTimeParser::_tracks.size(); i++) {
if (Common::QuickTimeParser::_tracks[i]->codecType == CODEC_TYPE_PANO) {
((PanoTrackHandler *)getTrack(Common::QuickTimeParser::_tracks[i]->targetTrack))->initPanorama();
}
}
}
// Prepare the first video track
VideoTrackHandler *nextVideoTrack = (VideoTrackHandler *)findNextVideoTrack();
if (nextVideoTrack) {
if (_scaleFactorX != 1 || _scaleFactorY != 1) {
// We have to take the scale into consideration when setting width/height
_width = (nextVideoTrack->getScaledWidth() / _scaleFactorX).toInt();
_height = (nextVideoTrack->getScaledHeight() / _scaleFactorY).toInt();
} else {
_width = nextVideoTrack->getWidth();
_height = nextVideoTrack->getHeight();
}
}
}
void QuickTimeDecoder::updateAudioBuffer() {
// Updates the audio buffers for all audio tracks
for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++)
if ((*it)->getTrackType() == VideoDecoder::Track::kTrackTypeAudio)
((AudioTrackHandler *)*it)->updateBuffer();
}
void QuickTimeDecoder::scaleSurface(const Graphics::Surface *src, Graphics::Surface *dst, const Common::Rational &scaleFactorX, const Common::Rational &scaleFactorY) {
assert(src && dst);
for (int32 j = 0; j < dst->h; j++)
for (int32 k = 0; k < dst->w; k++)
memcpy(dst->getBasePtr(k, j), src->getBasePtr((k * scaleFactorX).toInt() , (j * scaleFactorY).toInt()), src->format.bytesPerPixel);
}
QuickTimeDecoder::VideoSampleDesc::VideoSampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag) : Common::QuickTimeParser::SampleDesc(parentTrack, codecTag), _palette(0) {
memset(_codecName, 0, 32);
_colorTableId = 0;
_videoCodec = 0;
_bitsPerSample = 0;
}
QuickTimeDecoder::VideoSampleDesc::~VideoSampleDesc() {
delete _videoCodec;
}
void QuickTimeDecoder::VideoSampleDesc::initCodec() {
_videoCodec = Image::createQuickTimeCodec(_codecTag, _parentTrack->width, _parentTrack->height, _bitsPerSample);
}
QuickTimeDecoder::AudioTrackHandler::AudioTrackHandler(QuickTimeDecoder *decoder, QuickTimeAudioTrack *audioTrack) :
SeekableAudioTrack(decoder->getSoundType()),
_decoder(decoder),
_audioTrack(audioTrack) {
}
void QuickTimeDecoder::AudioTrackHandler::updateBuffer() {
if (_decoder->endOfVideoTracks()) // If we have no video left (or no video), there's nothing to base our buffer against
_audioTrack->queueRemainingAudio();
else // Otherwise, queue enough to get us to the next frame plus another half second spare
_audioTrack->queueAudio(Audio::Timestamp(_decoder->getTimeToNextFrame() + 500, 1000));
}
Audio::SeekableAudioStream *QuickTimeDecoder::AudioTrackHandler::getSeekableAudioStream() const {
return _audioTrack;
}
QuickTimeDecoder::VideoTrackHandler::VideoTrackHandler(QuickTimeDecoder *decoder, Common::QuickTimeParser::Track *parent) : _decoder(decoder), _parent(parent) {
if (decoder->_enableEditListBoundsCheckQuirk) {
checkEditListBounds();
}
_curEdit = 0;
_curFrame = -1;
_delayedFrameToBufferTo = -1;
enterNewEditListEntry(true, true); // might set _curFrame
if (decoder->_qtvrType == QTVRType::OBJECT)
_curFrame = getFrameCount() / 2;
_durationOverride = -1;
_scaledSurface = 0;
_curPalette = nullptr;
_dirtyPalette = false;
_reversed = false;
}
// FIXME: This check breaks valid QuickTime movies, such as the KQ6 Mac opening.
// It doesn't take media rate into account and mixes up units that are in movie
// time scale and media time scale, which is easy to do since they're often the
// same value. Other decoder bugs have been fixed since this was written, so it
// would be good to re-evaluate what the problem was with the Riven Spanish video.
// It's now disabled for everything except Riven.
void QuickTimeDecoder::VideoTrackHandler::checkEditListBounds() {
// Check all the edit list entries are within the bounds of the media
// In the Spanish version of Riven, the last edit of the video ogk.mov
// ends one frame after the end of the media.
uint32 offset = 0;
uint32 mediaDuration = _parent->mediaDuration * _decoder->_timeScale / _parent->timeScale;
for (uint i = 0; i < _parent->editList.size(); i++) {
EditListEntry &edit = _parent->editList[i];
if (edit.mediaTime < 0) {
offset += edit.trackDuration;
continue; // Ignore empty edits
}
if ((uint32) edit.mediaTime > mediaDuration) {
// Check if the edit starts after the end of the media
// If so, mark it as empty so it is ignored
edit.mediaTime = -1;
} else if (edit.mediaTime + edit.trackDuration > mediaDuration) {
// Check if the edit ends after the end of the media
// If so, clip it so it fits in the media
edit.trackDuration = mediaDuration - edit.mediaTime;
}
edit.timeOffset = offset;
offset += edit.trackDuration;
}
}
QuickTimeDecoder::VideoTrackHandler::~VideoTrackHandler() {
if (_scaledSurface) {
_scaledSurface->free();
delete _scaledSurface;
}
}
bool QuickTimeDecoder::VideoTrackHandler::endOfTrack() const {
// A track is over when we've finished going through all edits
if (_decoder->_qtvrType != QTVRType::PANORAMA)
return _reversed ? (_curEdit == 0 && _curFrame < 0) : atLastEdit();
else
return true;
}
bool QuickTimeDecoder::VideoTrackHandler::seek(const Audio::Timestamp &requestedTime) {
_delayedFrameToBufferTo = -1; // abort any delayed buffering
_curPalette = nullptr; // invalidate any palette
uint32 convertedFrames = requestedTime.convertToFramerate(_decoder->_timeScale).totalNumberOfFrames();
for (_curEdit = 0; !atLastEdit(); _curEdit++)
if (convertedFrames >= _parent->editList[_curEdit].timeOffset && convertedFrames < _parent->editList[_curEdit].timeOffset + _parent->editList[_curEdit].trackDuration)
break;
// If we did reach the end of the track, break out
if (atLastEdit()) {
// Call setReverse to set the position to the last frame of the last edit
if (_reversed)
setReverse(true);
return true;
}
// If this track is in an empty edit, position us at the next non-empty
// edit. There's nothing else to do after this.
if (_parent->editList[_curEdit].mediaTime == -1) {
while (!atLastEdit() && _parent->editList[_curEdit].mediaTime == -1)
_curEdit++;
if (!atLastEdit())
enterNewEditListEntry(true);
return true;
}
enterNewEditListEntry(false);
// One extra check for the end of a track
if (atLastEdit()) {
// Call setReverse to set the position to the last frame of the last edit
if (_reversed)
setReverse(true);
return true;
}
// Now we're in the edit and need to figure out what frame we need
Audio::Timestamp time = requestedTime.convertToFramerate(_parent->timeScale);
while (getRateAdjustedFrameTime() < (uint32)time.totalNumberOfFrames()) {
_curFrame++;
if (_durationOverride >= 0) {
_nextFrameStartTime += _durationOverride;
_durationOverride = -1;
} else {
_nextFrameStartTime += getCurFrameDuration();
}
}
// Check if we went past, then adjust the frame times
if (getRateAdjustedFrameTime() != (uint32)time.totalNumberOfFrames()) {
_curFrame--;
_durationOverride = getRateAdjustedFrameTime() - time.totalNumberOfFrames();
_nextFrameStartTime = time.totalNumberOfFrames();
}
if (_reversed) {
// Call setReverse again to update
setReverse(true);
} else {
// Handle the keyframe here
int32 destinationFrame = _curFrame + 1;
assert(destinationFrame < (int32)_parent->frameCount);
_curFrame = findKeyFrame(destinationFrame) - 1;
while (_curFrame < destinationFrame - 1)
bufferNextFrame();
}
return true;
}
Audio::Timestamp QuickTimeDecoder::VideoTrackHandler::getDuration() const {
return Audio::Timestamp(0, _parent->duration, _decoder->_timeScale);
}
uint16 QuickTimeDecoder::VideoTrackHandler::getWidth() const {
return getScaledWidth().toInt();
}
uint16 QuickTimeDecoder::VideoTrackHandler::getHeight() const {
return getScaledHeight().toInt();
}
Graphics::PixelFormat QuickTimeDecoder::VideoTrackHandler::getPixelFormat() const {
// TODO: What should happen if there are multiple codecs with different formats?
return ((VideoSampleDesc *)_parent->sampleDescs[0])->_videoCodec->getPixelFormat();
}
bool QuickTimeDecoder::VideoTrackHandler::setOutputPixelFormat(const Graphics::PixelFormat &format) {
bool success = true;
for (uint i = 0; i < _parent->sampleDescs.size(); i++) {
VideoSampleDesc *desc = (VideoSampleDesc *)_parent->sampleDescs[i];
success = success && desc->_videoCodec->setOutputPixelFormat(format);
}
return success;
}
int QuickTimeDecoder::VideoTrackHandler::getFrameCount() const {
return _parent->frameCount;
}
uint32 QuickTimeDecoder::VideoTrackHandler::getNextFrameStartTime() const {
if (endOfTrack() || _decoder->_isVR)
return 0;
Audio::Timestamp frameTime(0, getRateAdjustedFrameTime(), _parent->timeScale);
// Check if the frame goes beyond the end of the edit. In that case, the next frame
// should really be when we cross the edit boundary.
if (_reversed) {
Audio::Timestamp editStartTime(0, _parent->editList[_curEdit].timeOffset, _decoder->_timeScale);
if (frameTime < editStartTime)
return editStartTime.msecs();
} else {
Audio::Timestamp nextEditStartTime(0, _parent->editList[_curEdit].timeOffset + _parent->editList[_curEdit].trackDuration, _decoder->_timeScale);
if (frameTime > nextEditStartTime)
return nextEditStartTime.msecs();
}
// Not past an edit boundary, so the frame time is what should be used
return frameTime.msecs();
}
const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::decodeNextFrame() {
if (endOfTrack())
return 0;
if (_reversed) {
// Subtract one to place us on the frame before the current displayed frame.
_curFrame--;
// We have one "dummy" frame at the end to so the last frame is displayed
// for the right amount of time.
if (_curFrame < 0)
return 0;
// Decode from the last key frame to the frame before the one we need.
// TODO: Probably would be wise to do some caching
int targetFrame = _curFrame;
_curFrame = findKeyFrame(targetFrame) - 1;
while (_curFrame != targetFrame - 1)
bufferNextFrame();
}
// Update the edit list, if applicable
// FIXME: Add support for playing backwards videos with more than one edit
// For now, stay on the first edit for reversed playback
if (endOfCurEdit() && !_reversed) {
_curEdit++;
if (atLastEdit())
return 0;
enterNewEditListEntry(true);
if (isEmptyEdit())
return 0;
}
const Graphics::Surface *frame = bufferNextFrame();
if (_reversed) {
if (_durationOverride >= 0) {
// Use our own duration overridden from a media seek
_nextFrameStartTime -= _durationOverride;
_durationOverride = -1;
} else {
// Just need to subtract the time
_nextFrameStartTime -= getCurFrameDuration();
}
} else {
if (_durationOverride >= 0) {
// Use our own duration overridden from a media seek
_nextFrameStartTime += _durationOverride;
_durationOverride = -1;
} else {
_nextFrameStartTime += getCurFrameDuration();
}
}
if (frame && (_parent->scaleFactorX != 1 || _parent->scaleFactorY != 1)) {
if (!_scaledSurface) {
_scaledSurface = new Graphics::Surface();
_scaledSurface->create(getScaledWidth().toInt(), getScaledHeight().toInt(), getPixelFormat());
}
_decoder->scaleSurface(frame, _scaledSurface, _parent->scaleFactorX, _parent->scaleFactorY);
return _scaledSurface;
}
return frame;
}
Common::String QuickTimeDecoder::getAliasPath() {
const Common::Array<Common::QuickTimeParser::Track *> &tracks = Common::QuickTimeParser::_tracks;
for (uint32 i = 0; i < tracks.size(); i++) {
if (!tracks[i]->path.empty())
return tracks[i]->path;
}
return Common::String();
}
Audio::Timestamp QuickTimeDecoder::VideoTrackHandler::getFrameTime(uint frame) const {
// TODO: This probably doesn't work right with edit lists
int cumulativeDuration = 0;
for (int ttsIndex = 0; ttsIndex < _parent->timeToSampleCount; ttsIndex++) {
const TimeToSampleEntry &tts = _parent->timeToSample[ttsIndex];
if ((int)frame < tts.count)
return Audio::Timestamp(0, _parent->timeScale).addFrames(cumulativeDuration + frame * tts.duration);
else {
frame -= tts.count;
cumulativeDuration += tts.duration * tts.count;
}
}
return Audio::Timestamp().addFrames(-1);
}
const byte *QuickTimeDecoder::VideoTrackHandler::getPalette() const {
_dirtyPalette = false;
return _curPalette;
}
bool QuickTimeDecoder::VideoTrackHandler::setReverse(bool reverse) {
_delayedFrameToBufferTo = -1; // abort any delayed buffering
_curPalette = nullptr; // invalidate any palette
_reversed = reverse;
if (_reversed) {
if (_parent->editList.size() != 1) {
// TODO: Myst's holo.mov needs this :(
warning("Can only set reverse without edits");
return false;
}
if (atLastEdit()) {
// If we're at the end of the video, go to the penultimate edit.
// The current frame is set to one beyond the last frame here;
// one "past" the currently displayed frame.
_curEdit = _parent->editList.size() - 1;
_curFrame = _parent->frameCount;
_nextFrameStartTime = _parent->editList[_curEdit].trackDuration + _parent->editList[_curEdit].timeOffset;
} else if (_durationOverride >= 0) {
// We just had a media seek, so "pivot" around the frame that should
// be displayed.
_curFrame += 2;
_nextFrameStartTime += _durationOverride;
} else {
// We need to put _curFrame to be the one after the one that should be displayed.
// Since we're on the frame that should be displaying right now, add one.
_curFrame++;
}
} else {
// Update the edit list, if applicable
if (!atLastEdit() && endOfCurEdit()) {
_curEdit++;
if (atLastEdit())
return true;
}
if (_durationOverride >= 0) {
// We just had a media seek, so "pivot" around the frame that should
// be displayed.
_curFrame--;
_nextFrameStartTime -= _durationOverride;
}
// We need to put _curFrame to be the one before the one that should be displayed.
// Since we're on the frame that should be displaying right now, subtract one.
// (As long as the current frame isn't -1, of course)
if (_curFrame > 0) {
// We then need to handle the keyframe situation
int targetFrame = _curFrame - 1;
_curFrame = findKeyFrame(targetFrame) - 1;
while (_curFrame < targetFrame)
bufferNextFrame();
} else if (_curFrame == 0) {
// Make us start at the first frame (no keyframe needed)
_curFrame--;
}
}
return true;
}
Common::Rational QuickTimeDecoder::VideoTrackHandler::getScaledWidth() const {
return Common::Rational(_parent->width) / _parent->scaleFactorX;
}
Common::Rational QuickTimeDecoder::VideoTrackHandler::getScaledHeight() const {
return Common::Rational(_parent->height) / _parent->scaleFactorY;
}
Common::SeekableReadStream *QuickTimeDecoder::VideoTrackHandler::getNextFramePacket(uint32 &descId) {
// First, we have to track down which chunk holds the sample and which sample in the chunk contains the frame we are looking for.
int32 totalSampleCount = 0;
int32 sampleInChunk = 0;
int32 actualChunk = -1;
uint32 sampleToChunkIndex = 0;
for (uint32 i = 0; i < _parent->chunkCount; i++) {
if (sampleToChunkIndex < _parent->sampleToChunkCount && i >= _parent->sampleToChunk[sampleToChunkIndex].first)
sampleToChunkIndex++;
totalSampleCount += _parent->sampleToChunk[sampleToChunkIndex - 1].count;
if (totalSampleCount > _curFrame) {
actualChunk = i;
descId = _parent->sampleToChunk[sampleToChunkIndex - 1].id;
sampleInChunk = _parent->sampleToChunk[sampleToChunkIndex - 1].count - totalSampleCount + _curFrame;
break;
}
}
if (actualChunk < 0)
error("Could not find data for frame %d", _curFrame);
// Next seek to that frame
Common::SeekableReadStream *stream = _decoder->_fd;
stream->seek(_parent->chunkOffsets[actualChunk]);
// Then, if the chunk holds more than one frame, seek to where the frame we want is located
for (int32 i = _curFrame - sampleInChunk; i < _curFrame; i++) {
if (_parent->sampleSize != 0)
stream->skip(_parent->sampleSize);
else
stream->skip(_parent->sampleSizes[i]);
}
// Finally, read in the raw data for the frame
//debug("Frame Data[%d]: Offset = %d, Size = %d", _curFrame, stream->pos(), _parent->sampleSizes[_curFrame]);
if (_parent->sampleSize != 0)
return stream->readStream(_parent->sampleSize);
return stream->readStream(_parent->sampleSizes[_curFrame]);
}
uint32 QuickTimeDecoder::VideoTrackHandler::getCurFrameDuration() {
uint32 curFrameIndex = 0;
for (int32 i = 0; i < _parent->timeToSampleCount; i++) {
curFrameIndex += _parent->timeToSample[i].count;
if ((uint32)_curFrame < curFrameIndex) {
// Ok, now we have what duration this frame has.
return _parent->timeToSample[i].duration;
}
}
// This should never occur
error("Cannot find duration for frame %d", _curFrame);
return 0;
}
uint32 QuickTimeDecoder::VideoTrackHandler::findKeyFrame(uint32 frame) const {
for (int i = _parent->keyframeCount - 1; i >= 0; i--)
if (_parent->keyframes[i] <= frame)
return _parent->keyframes[i];
// If none found, we'll assume the requested frame is a key frame
return frame;
}
bool QuickTimeDecoder::VideoTrackHandler::isEmptyEdit() const {
return (_parent->editList[_curEdit].mediaTime == -1);
}
void QuickTimeDecoder::VideoTrackHandler::enterNewEditListEntry(bool bufferFrames, bool initializingTrack) {
if (atLastEdit())
return;
// if this is an empty edit then the only thing to do is set the
// time for the next frame, which is the duration of this edit.
if (isEmptyEdit()) {
_curFrame = -1;
_nextFrameStartTime = getCurEditTimeOffset() + getCurEditTrackDuration();
return;
}
uint32 mediaTime = _parent->editList[_curEdit].mediaTime;
uint32 frameNum = 0;
uint32 totalDuration = 0;
_durationOverride = -1;
// Track down where the mediaTime is in the media
// This is basically time -> frame mapping
// Note that this code uses first frame = 0
for (int32 i = 0; i < _parent->timeToSampleCount; i++) {
uint32 duration = _parent->timeToSample[i].count * _parent->timeToSample[i].duration;
if (totalDuration + duration >= mediaTime) {
uint32 frameInc = (mediaTime - totalDuration) / _parent->timeToSample[i].duration;
frameNum += frameInc;
totalDuration += frameInc * _parent->timeToSample[i].duration;
// If we didn't get to the exact media time, mark an override for
// the time.
if (totalDuration != mediaTime)
_durationOverride = totalDuration + _parent->timeToSample[i].duration - mediaTime;
break;
}
frameNum += _parent->timeToSample[i].count;
totalDuration += duration;
}
if (bufferFrames) {
// Track down the keyframe
// Then decode until the frame before target
_curFrame = findKeyFrame(frameNum) - 1;
if (initializingTrack) {
// We can't decode frames during track initialization,
// so delay buffering until the first decode.
_delayedFrameToBufferTo = (int32)frameNum - 1;
} else {
while (_curFrame < (int32)frameNum - 1) {
bufferNextFrame();
}
}
} else {
// Since frameNum is the frame that needs to be displayed
// we'll set _curFrame to be the "last frame displayed"
_curFrame = frameNum - 1;
}
_nextFrameStartTime = getCurEditTimeOffset();
}
const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::bufferNextFrame() {
// Buffer any frames that were identified during track initialization
// and delayed until decoding.
if (_delayedFrameToBufferTo != -1) {
int32 frameNum = _delayedFrameToBufferTo;
_delayedFrameToBufferTo = -1;
while (_curFrame < frameNum) {
bufferNextFrame();
}
}
if (_decoder->_qtvrType != QTVRType::OBJECT)
_curFrame++;
// Get the next packet
uint32 descId;
Common::SeekableReadStream *frameData = getNextFramePacket(descId);
if (!frameData || !descId || descId > _parent->sampleDescs.size()) {
delete frameData;
return 0;
}
// Find which video description entry we want
VideoSampleDesc *entry = (VideoSampleDesc *)_parent->sampleDescs[descId - 1];
if (!entry->_videoCodec) {
delete frameData;
return 0;
}
// Check if the video description has been updated
const byte *palette = entry->_palette.data();
if (palette != _curPalette) {
_curPalette = palette;
_dirtyPalette = true;
}
// Update the palette used when dithering
Image::DitherCodec *ditherCodec = dynamic_cast<Image::DitherCodec *>(entry->_videoCodec);
if (ditherCodec && _dirtyPalette) {
ditherCodec->setPalette(_curPalette);
_dirtyPalette = false;
}
const Graphics::Surface *frame = entry->_videoCodec->decodeFrame(*frameData);
delete frameData;
// The codec palette takes priority over the container one
if (entry->_videoCodec->containsPalette()) {
_dirtyPalette = entry->_videoCodec->hasDirtyPalette();
_curPalette = entry->_videoCodec->getPalette();
}
return frame;
}
uint32 QuickTimeDecoder::VideoTrackHandler::getRateAdjustedFrameTime() const {
// Figure out what time the next frame is at taking the edit list rate into account,
// unless this is an empty edit, in which case the rate isn't applicable.
Common::Rational offsetFromEdit = Common::Rational(_nextFrameStartTime - getCurEditTimeOffset());
if (!isEmptyEdit()) {
offsetFromEdit /= _parent->editList[_curEdit].mediaRate;
}
uint32 convertedTime = offsetFromEdit.toInt();
if ((offsetFromEdit.getNumerator() % offsetFromEdit.getDenominator()) > (offsetFromEdit.getDenominator() / 2))
convertedTime++;
return convertedTime + getCurEditTimeOffset();
}
uint32 QuickTimeDecoder::VideoTrackHandler::getCurEditTimeOffset() const {
// Need to convert to the track scale
// We have to round the time off to the nearest in the scale, otherwise
// bad things happen. QuickTime docs are pretty silent on all this stuff,
// so this was found from samples. It doesn't help that this is really
// the only open source implementation of QuickTime edits.
uint32 mult = _parent->editList[_curEdit].timeOffset * _parent->timeScale;
uint32 result = mult / _decoder->_timeScale;
if ((mult % _decoder->_timeScale) > (_decoder->_timeScale / 2))
result++;
return result;
}
uint32 QuickTimeDecoder::VideoTrackHandler::getCurEditTrackDuration() const {
// convert from movie time scale to the track's media time scale
return _parent->editList[_curEdit].trackDuration * _parent->timeScale / _decoder->_timeScale;
}
bool QuickTimeDecoder::VideoTrackHandler::atLastEdit() const {
return _curEdit == _parent->editList.size();
}
bool QuickTimeDecoder::VideoTrackHandler::endOfCurEdit() const {
// We're at the end of the edit once the next frame's time would
// bring us past the end of the edit.
if (!_decoder->_isVR)
return getRateAdjustedFrameTime() >= getCurEditTimeOffset() + getCurEditTrackDuration();
else
return false;
}
bool QuickTimeDecoder::VideoTrackHandler::canDither() const {
for (uint i = 0; i < _parent->sampleDescs.size(); i++) {
VideoSampleDesc *desc = (VideoSampleDesc *)_parent->sampleDescs[i];
if (!desc || !desc->_videoCodec)
return false;
}
return true;
}
void QuickTimeDecoder::VideoTrackHandler::setDither(const byte *palette) {
assert(canDither());
for (uint i = 0; i < _parent->sampleDescs.size(); i++) {
VideoSampleDesc *desc = (VideoSampleDesc *)_parent->sampleDescs[i];
if (desc->_videoCodec->canDither(Image::Codec::kDitherTypeQT)) {
// Codec dither
desc->_videoCodec->setDither(Image::Codec::kDitherTypeQT, palette);
} else {
// Forced dither
desc->_videoCodec = new Image::DitherCodec(desc->_videoCodec);
desc->_videoCodec->setDither(Image::Codec::kDitherTypeQT, palette);
}
}
}
} // End of namespace Video

451
video/qt_decoder.h Normal file
View File

@@ -0,0 +1,451 @@
/* 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/>.
*
*/
//
// Partially based on ffmpeg code.
//
// Copyright (c) 2001 Fabrice Bellard.
// First version by Francois Revol revol@free.fr
// Seek function by Gael Chardon gael.dev@4now.net
//
#ifndef VIDEO_QT_DECODER_H
#define VIDEO_QT_DECODER_H
#include "audio/decoders/quicktime_intern.h"
#include "common/keyboard.h"
#include "common/scummsys.h"
#include "graphics/palette.h"
#include "graphics/transform_tools.h"
#include "video/video_decoder.h"
namespace Common {
class Archive;
class Rational;
}
namespace Graphics {
class Cursor;
struct PixelFormat;
}
namespace Image {
class Codec;
}
namespace Video {
/**
* Decoder for QuickTime videos.
*
* Video decoder used in engines:
* - mohawk
* - pegasus
* - sci
*/
class QuickTimeDecoder : public VideoDecoder, public Audio::QuickTimeAudioDecoder {
public:
QuickTimeDecoder();
virtual ~QuickTimeDecoder();
bool loadFile(const Common::Path &filename);
bool loadStream(Common::SeekableReadStream *stream);
void close();
uint16 getWidth() const { return _width; }
uint16 getHeight() const { return _height; }
const Graphics::Surface *decodeNextFrame();
Audio::Timestamp getDuration() const { return Audio::Timestamp(0, _duration, _timeScale); }
void enableEditListBoundsCheckQuirk(bool enable) { _enableEditListBoundsCheckQuirk = enable; }
Common::String getAliasPath();
////////////////
// QTVR stuff
////////////////
void setTargetSize(uint16 w, uint16 h);
void setOrigin(int left, int top) { _origin = Common::Point(left, top); }
void handleMouseMove(int16 x, int16 y);
void handleMouseButton(bool isDown, int16 x = -1, int16 y = -1, bool repeat = false);
void handleKey(Common::KeyState &state, bool down, bool repeat = false);
void handleQuit();
Common::Point getLastClick() { return _mouseDrag; }
float getPanAngle() const { return _panAngle; }
void setPanAngle(float panAngle);
float getTiltAngle() const { return _tiltAngle; }
void setTiltAngle(float tiltAngle);
float getFOV() const { return _fov; }
float getHFOV() const { return _hfov; }
bool setFOV(float fov);
int getCurrentNodeID() { return _currentSample == -1 ? 0 : _panoTrack->panoSamples[_currentSample].hdr.nodeID; }
Common::String getCurrentNodeName();
int getCurrentRow() { return _nextVideoTrack->getCurFrame() / _nav.columns; }
void setCurrentRow(int row);
int getCurrentColumn() { return _nextVideoTrack->getCurFrame() % _nav.columns; }
void setCurrentColumn(int column);
int getZoomState() { return _zoomState; }
const PanoHotSpot *getRolloverHotspot() { return _rolloverHotspot; }
int getRolloverHotspotID() { return _rolloverHotspotID; }
const PanoHotSpot *getClickedHotspot() { return _clickedHotspot; }
int getClickedHotspotID() { return _clickedHotspotID; }
Common::Point getPanLoc(int16 x, int16 y);
Graphics::FloatPoint getPanAngles(int16 x, int16 y);
Common::String getHotSpotName(int id);
void setClickedHotSpot(int id);
const PanoHotSpot *getHotSpotByID(int id);
const PanoNavigation *getHotSpotNavByID(int id);
void nudge(const Common::String &direction);
bool isVR() const { return _isVR; }
QTVRType getQTVRType() const { return _qtvrType; }
int getWarpMode() const { return _warpMode; }
void setWarpMode(int warpMode);
float getQuality() const { return _quality; }
void setQuality(float quality);
Common::String getTransitionMode() const { return _transitionMode == kTransitionModeNormal ? "normal" : "swing"; }
void setTransitionMode(Common::String mode);
float getTransitionSpeed() const { return _transitionSpeed; }
void setTransitionSpeed(float speed);
Common::String getUpdateMode() const;
void setUpdateMode(Common::String mode);
void renderHotspots(bool mode);
struct NodeData {
uint32 nodeID;
float defHPan;
float defVPan;
float defZoom;
float minHPan;
float minVPan;
float maxHPan;
float maxVPan;
float minZoom;
Common::String name;
};
NodeData getNodeData(uint32 nodeID);
void goToNode(uint32 nodeID);
protected:
Common::QuickTimeParser::SampleDesc *readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format, uint32 descSize);
Common::QuickTimeParser::SampleDesc *readPanoSampleDesc(Common::QuickTimeParser::Track *track, uint32 format, uint32 descSize);
private:
void init();
void updateAudioBuffer();
void handleObjectMouseMove(int16 x, int16 y);
void handleObjectMouseButton(bool isDown, int16 x, int16 y, bool repeat);
void handlePanoMouseMove(int16 x, int16 y);
void handlePanoMouseButton(bool isDown, int16 x, int16 y, bool repeat);
void handleObjectKey(Common::KeyState &state, bool down, bool repeat);
void handlePanoKey(Common::KeyState &state, bool down, bool repeat);
void closeQTVR();
void updateAngles();
void lookupHotspot(int16 x, int16 y);
void updateQTVRCursor(int16 x, int16 y);
void setCursor(int curId);
void cleanupCursors();
void computeInteractivityZones();
uint16 _width, _height;
// _origin is the top left corner point of the panorama video being played
// by director engine or whichever engine is using QTVR decoder currently
// decoder handles swing transitions (in QTVR xtra) internally
// Hence, it needs to know where to blit the projected panorama during transition
Common::Point _origin;
public:
int _currentSample = -1;
Common::Point _prevMouse;
bool _isMouseButtonDown = false;
Common::Point _mouseDrag;
bool _isKeyDown = false;
Common::KeyState _lastKey;
enum {
kZoomNone,
kZoomQuestion,
kZoomIn,
kZoomOut,
kZoomLimit,
kTransitionModeNormal,
kTransitionModeSwing,
kUpdateModeNormal,
kUpdateModeUpdateBoth,
kUpdateModeOffscreenOnly,
kUpdateModeFromOffscreen,
kUpdateModeDirectToScreen,
};
private:
Common::Rect _curBbox;
int _currentQTVRCursor = -1;
Common::Archive *_dataBundle = nullptr;
Graphics::Cursor **_cursorCache = nullptr;
int _cursorDirMap[256];
bool _isVR = false;
uint8 _warpMode = 2; // (2 | 1 | 0) for 2-d, 1-d or no warping
float _quality = 0.0f;
int _transitionMode = kTransitionModeNormal;
float _transitionSpeed = 1.0f;
int _updateMode = kUpdateModeNormal;
float _panAngle = 0.0f;
float _tiltAngle = 0.0f;
float _fov = 56.0f;
float _hfov = 56.0f;
int _zoomState = kZoomNone;
const PanoHotSpot *_rolloverHotspot = nullptr;
int _rolloverHotspotID = 0;
const PanoHotSpot *_clickedHotspot = nullptr;
int _clickedHotspotID = 0;
bool _renderHotspots = false;
Graphics::Surface *_scaledSurface;
void scaleSurface(const Graphics::Surface *src, Graphics::Surface *dst,
const Common::Rational &scaleFactorX, const Common::Rational &scaleFactorY);
bool _enableEditListBoundsCheckQuirk;
bool _cursorDirty;
Common::Point _cursorPos;
class VideoSampleDesc : public Common::QuickTimeParser::SampleDesc {
public:
VideoSampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag);
~VideoSampleDesc();
void initCodec();
// TODO: Make private in the long run
uint16 _bitsPerSample;
char _codecName[32];
uint16 _colorTableId;
Graphics::Palette _palette;
Image::Codec *_videoCodec;
};
// The AudioTrackHandler is currently just a wrapper around some
// QuickTimeDecoder functions.
class AudioTrackHandler : public SeekableAudioTrack {
public:
AudioTrackHandler(QuickTimeDecoder *decoder, QuickTimeAudioTrack *audioTrack);
void updateBuffer();
protected:
Audio::SeekableAudioStream *getSeekableAudioStream() const;
private:
QuickTimeDecoder *_decoder;
QuickTimeAudioTrack *_audioTrack;
};
class PanoSampleDesc : public Common::QuickTimeParser::SampleDesc {
public:
PanoSampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag);
~PanoSampleDesc();
int16 _majorVersion; // must be zero, also observed to be 1
int16 _minorVersion; // must be zero, also observed to be 1
int32 _sceneTrackID; // ID of video track that contains panoramic scene
int32 _loResSceneTrackID; // ID of video track that contains low res panoramic scene
byte _reserved3[4 * 6]; // must be zero
int32 _hotSpotTrackID; // ID of video track that contains hotspot image
byte _reserved4[4 * 9]; // must be zero
float _hPanStart; // horizontal pan range start angle (e.g. 0)
float _hPanEnd; // horizontal pan range end angle (e.g. 360)
float _vPanTop; // vertical pan range top angle (e.g. 42.5)
float _vPanBottom; // vertical pan range bottom angle (e.g. -42.5)
float _minimumZoom; // minimum zoom angle (e.g. 5; use 0 for default)
float _maximumZoom; // maximum zoom angle (e.g. 65; use 0 for default)
// info for the highest res version of scene track
uint32 _sceneSizeX; // pixel width of the panorama (e.g. 768)
uint32 _sceneSizeY; // pixel height of the panorama (e.g. 2496)
uint32 _numFrames; // number of diced frames (e.g. 24)
int16 _reserved5; // must be zero
int16 _sceneNumFramesX; // diced frames wide (e.g. 1)
int16 _sceneNumFramesY; // diced frames high (e.g. 24)
int16 _sceneColorDepth; // bit depth of the scene track (e.g. 32)
// info for the highest rest version of hotSpot track
int32 _hotSpotSizeX; // pixel width of the hot spot panorama (e.g. 768)
int32 _hotSpotSizeY; // pixel height of the hot spot panorama (e.g. 2496)
int16 _reserved6; // must be zero
int16 _hotSpotNumFramesX; // diced frames wide (e.g. 1)
int16 _hotSpotNumFramesY; // diced frames high (e.g. 24)
int16 _hotSpotColorDepth; // must be 8
};
// The VideoTrackHandler is the bridge between the time of playback
// and the media for the given track. It calculates when to start
// tracks and at what rate to play the media using the edit list.
class VideoTrackHandler : public VideoTrack {
public:
VideoTrackHandler(QuickTimeDecoder *decoder, Common::QuickTimeParser::Track *parent);
~VideoTrackHandler();
bool endOfTrack() const;
bool isSeekable() const { return true; }
bool seek(const Audio::Timestamp &time);
Audio::Timestamp getDuration() const;
uint16 getWidth() const;
uint16 getHeight() const;
Graphics::PixelFormat getPixelFormat() const;
bool setOutputPixelFormat(const Graphics::PixelFormat &format);
int getCurFrame() const { return _curFrame; }
void setCurFrame(int32 curFrame) { _curFrame = curFrame; }
int getFrameCount() const;
uint32 getNextFrameStartTime() const; // milliseconds
const Graphics::Surface *decodeNextFrame();
Audio::Timestamp getFrameTime(uint frame) const;
const byte *getPalette() const;
bool hasDirtyPalette() const { return _dirtyPalette; }
bool setReverse(bool reverse);
bool isReversed() const { return _reversed; }
bool canDither() const;
void setDither(const byte *palette);
Common::Rational getScaledWidth() const;
Common::Rational getScaledHeight() const;
const Graphics::Surface *bufferNextFrame();
private:
QuickTimeDecoder *_decoder;
Common::QuickTimeParser::Track *_parent;
uint32 _curEdit;
int32 _curFrame;
int32 _delayedFrameToBufferTo;
uint32 _nextFrameStartTime; // media time
Graphics::Surface *_scaledSurface;
int32 _durationOverride; // media time
const byte *_curPalette;
mutable bool _dirtyPalette;
bool _reversed;
Common::SeekableReadStream *getNextFramePacket(uint32 &descId);
uint32 getCurFrameDuration(); // media time
uint32 findKeyFrame(uint32 frame) const;
bool isEmptyEdit() const;
void enterNewEditListEntry(bool bufferFrames, bool intializingTrack = false);
uint32 getRateAdjustedFrameTime() const; // media time
uint32 getCurEditTimeOffset() const; // media time
uint32 getCurEditTrackDuration() const; // media time
bool atLastEdit() const;
bool endOfCurEdit() const;
void checkEditListBounds();
};
class PanoTrackHandler : public VideoTrack {
public:
PanoTrackHandler(QuickTimeDecoder *decoder, Common::QuickTimeParser::Track *parent);
~PanoTrackHandler();
bool endOfTrack() const { return false; }
uint16 getWidth() const;
uint16 getHeight() const;
int getCurFrame() const { return 1; }
uint32 getNextFrameStartTime() const { return 0; }
Graphics::PixelFormat getPixelFormat() const;
const Graphics::Surface *decodeNextFrame();
Common::Rational getScaledWidth() const;
Common::Rational getScaledHeight() const;
void initPanorama();
void constructPanorama();
Graphics::Surface *constructMosaic(VideoTrackHandler *track, uint w, uint h, Common::String fname);
Common::Point projectPoint(int16 x, int16 y);
void setDirty() { _dirty = true; }
private:
QuickTimeDecoder *_decoder;
Common::QuickTimeParser::Track *_parent;
void projectPanorama(uint8 scaleFactor, float fov, float hfov, float panAngle, float tiltAngle);
void swingTransitionHandler();
void boxAverage(Graphics::Surface *sourceSurface, uint8 scaleFactor);
Graphics::Surface* upscalePanorama(Graphics::Surface *sourceSurface, int8 level);
const Graphics::Surface *bufferNextFrame();
public:
Graphics::Surface *_constructedPano;
Graphics::Surface *_upscaledConstructedPano;
Graphics::Surface *_constructedHotspots;
Graphics::Surface *_projectedPano;
Graphics::Surface *_planarProjection;
// Current upscale level (0 or 1 or 2) of _upscaledConstructedPanorama compared to _constructedPano
// level 0 means that constructedPano was just contructed and hasn't been upscaled yet
// level 1 means only upscaled height (2x pixels)
// level 2 means upscaled height and width (4x pixels)
uint8 _upscaleLevel = 0;
// Defining these to make the swing transition happen
// which requires storing the previous point during every change in FOV, Pan Angle and Tilt Angle
// If swing transition is called, this will be the start point of the transition
float _currentFOV = 0;
float _currentHFOV = 0;
float _currentPanAngle = 0;
float _currentTiltAngle = 0;
private:
bool _isPanoConstructed;
bool _dirty;
};
};
} // End of namespace Video
#endif

2036
video/qtvr_decoder.cpp Normal file

File diff suppressed because it is too large Load Diff

1043
video/smk_decoder.cpp Normal file

File diff suppressed because it is too large Load Diff

229
video/smk_decoder.h Normal file
View File

@@ -0,0 +1,229 @@
/* 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/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, MojoTouch has
* non-exclusively licensed this code on March 23th, 2024, to be used in
* closed-source products.
* Therefore, any contributions (commits) to it will also be dual-licensed.
*
*/
#ifndef VIDEO_SMK_PLAYER_H
#define VIDEO_SMK_PLAYER_H
#include "common/bitarray.h"
#include "common/bitstream.h"
#include "common/rational.h"
#include "common/rect.h"
#include "graphics/palette.h"
#include "graphics/pixelformat.h"
#include "graphics/surface.h"
#include "video/video_decoder.h"
#include "audio/mixer.h"
namespace Audio {
class QueuingAudioStream;
}
namespace Common {
class SeekableReadStream;
}
namespace Video {
class BigHuffmanTree;
// Because the maximum number of bits read from a bitstream is 16, and the data is 8-bit, the container only
// needs to hold up to 23 bits at any given time. As such, we use a bitstream with a 32-bit container to
// avoid the overhead of 64-bit maths on systems that don't support it natively.
typedef Common::BitStreamImpl<Common::BitStreamMemoryStream, uint32, 8, false, false> SmackerBitStream;
/**
* Decoder for Smacker v2/v4 videos.
*
* Based on https://wiki.multimedia.cx/index.php?title=Smacker
* and the FFmpeg Smacker decoder (libavcodec/smacker.c), revision 16143
* https://git.ffmpeg.org/gitweb/ffmpeg.git/commit/40a19c443430de520d86bbd644033c8e2ca87e9b
*
* Video decoder used in engines:
* - agos
* - bagel
* - saga
* - scumm (he)
* - sword1
* - sword2
* - toon
* - trecision
* - twine
*/
class SmackerDecoder : public VideoDecoder {
public:
SmackerDecoder();
virtual ~SmackerDecoder();
virtual bool loadStream(Common::SeekableReadStream *stream);
void close();
const Graphics::Surface *forceSeekToFrame(uint frame);
bool rewind();
Common::Rational getFrameRate() const;
virtual const Common::Rect *getNextDirtyRect();
protected:
void readNextPacket();
bool supportsAudioTrackSwitching() const { return true; }
AudioTrack *getAudioTrack(int index);
virtual void handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize);
virtual uint32 getSignatureVersion(uint32 signature) const;
class SmackerVideoTrack : public FixedRateVideoTrack {
public:
SmackerVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 version);
~SmackerVideoTrack();
bool isRewindable() const { return true; }
bool rewind() { _curFrame = -1; return true; }
uint16 getWidth() const;
uint16 getHeight() const;
Graphics::PixelFormat getPixelFormat() const;
int getCurFrame() const { return _curFrame; }
int getFrameCount() const { return _frameCount; }
const Graphics::Surface *decodeNextFrame() { return _surface; }
const byte *getPalette() const { _dirtyPalette = false; return _palette.data(); }
bool hasDirtyPalette() const { return _dirtyPalette; }
void readTrees(SmackerBitStream &bs, uint32 mMapSize, uint32 mClrSize, uint32 fullSize, uint32 typeSize);
void increaseCurFrame() { _curFrame++; }
void decodeFrame(SmackerBitStream &bs);
void unpackPalette(Common::SeekableReadStream *stream);
Common::Rational getFrameRate() const { return _frameRate; }
const Common::Rect *getNextDirtyRect();
protected:
Graphics::Surface *_surface;
private:
Common::Rational _frameRate;
uint32 _flags, _version;
Graphics::Palette _palette;
mutable bool _dirtyPalette;
int _curFrame;
uint32 _frameCount;
BigHuffmanTree *_MMapTree;
BigHuffmanTree *_MClrTree;
BigHuffmanTree *_FullTree;
BigHuffmanTree *_TypeTree;
Common::BitArray _dirtyBlocks;
Common::Rect _lastDirtyRect;
// Possible runs of blocks
static uint getBlockRun(int index) { return (index <= 58) ? index + 1 : 128 << (index - 59); }
};
virtual SmackerVideoTrack *createVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 version) const;
Common::SeekableReadStream *_fileStream;
enum AudioCompression {
kCompressionNone,
kCompressionDPCM,
kCompressionRDFT,
kCompressionDCT
};
struct AudioInfo {
AudioCompression compression;
bool hasAudio;
bool is16Bits;
bool isStereo;
uint32 sampleRate;
};
struct {
uint32 signature;
uint32 flags;
uint32 audioSize[7];
uint32 treesSize;
uint32 mMapSize;
uint32 mClrSize;
uint32 fullSize;
uint32 typeSize;
AudioInfo audioInfo[7];
uint32 dummy;
} _header;
uint32 *_frameSizes;
private:
class SmackerAudioTrack : public AudioTrack {
public:
SmackerAudioTrack(const AudioInfo &audioInfo, Audio::Mixer::SoundType soundType);
~SmackerAudioTrack();
bool isRewindable() const { return true; }
bool rewind();
void queueCompressedBuffer(byte *buffer, uint32 bufferSize, uint32 unpackedSize);
void queuePCM(byte *buffer, uint32 bufferSize);
protected:
Audio::AudioStream *getAudioStream() const;
private:
Audio::QueuingAudioStream *_audioStream;
AudioInfo _audioInfo;
};
class SmackerEmptyTrack : public Track {
VideoDecoder::Track::TrackType getTrackType() const { return VideoDecoder::Track::kTrackTypeNone; }
bool endOfTrack() const { return true; }
bool isSeekable() const { return true; }
bool seek(const Audio::Timestamp &time) { return true; }
};
protected:
// The FrameTypes section of a Smacker file contains an array of bytes, where
// the 8 bits of each byte describe the contents of the corresponding frame.
// The highest 7 bits correspond to audio frames (bit 7 is track 6, bit 6 track 5
// and so on), so there can be up to 7 different audio tracks. When the lowest bit
// (bit 0) is set, it denotes a frame that contains a palette record
byte *_frameTypes;
private:
uint32 _firstFrameStart;
};
} // End of namespace Video
#endif

636
video/subtitles.cpp Normal file
View File

@@ -0,0 +1,636 @@
/* 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/debug.h"
#include "common/file.h"
#include "common/system.h"
#include "common/ustr.h"
#include "common/unicode-bidi.h"
#include "graphics/fonts/ttf.h"
#include "graphics/font.h"
#include "graphics/fontman.h"
#include "graphics/surface.h"
#include "video/subtitles.h"
#include "common/config-manager.h"
namespace Video {
SRTParser::SRTParser() {
}
SRTParser::~SRTParser() {
cleanup();
}
void SRTParser::cleanup() {
for (const auto *item : _entries)
delete item;
_entries.clear();
}
void SRTParser::parseTextAndTags(const Common::String &text, Common::Array<SubtitlePart> &parts) const {
Common::String currentText = text;
while (true) {
Common::String::size_type pos_i_start = currentText.find("<i>");
Common::String::size_type pos_sfx_start = currentText.find("<sfx>");
Common::String::size_type first_tag_start = Common::String::npos;
Common::String start_tag, end_tag;
Common::String tag;
if (pos_i_start != Common::String::npos && (pos_sfx_start == Common::String::npos || pos_i_start < pos_sfx_start)) {
first_tag_start = pos_i_start;
start_tag = "<i>";
end_tag = "</i>";
tag = "i";
} else if (pos_sfx_start != Common::String::npos) {
first_tag_start = pos_sfx_start;
start_tag = "<sfx>";
end_tag = "</sfx>";
tag = "sfx";
}
if (first_tag_start == Common::String::npos) {
if (!currentText.empty()) {
parts.push_back(SubtitlePart(currentText, ""));
}
break;
}
if (first_tag_start > 0) {
parts.push_back(SubtitlePart(currentText.substr(0, first_tag_start), ""));
}
Common::String::size_type end_tag_pos = currentText.find(end_tag, first_tag_start);
if (end_tag_pos == Common::String::npos) {
parts.push_back(SubtitlePart(currentText.substr(first_tag_start + start_tag.size()), tag));
break;
}
parts.push_back(SubtitlePart(currentText.substr(first_tag_start + start_tag.size(), end_tag_pos - (first_tag_start + start_tag.size())), tag));
currentText = currentText.substr(end_tag_pos + end_tag.size());
}
}
bool parseTime(const char **pptr, uint32 *res) {
int hours, mins, secs, msecs;
const char *ptr = *pptr;
hours = (*ptr++ - '0') * 10;
hours += *ptr++ - '0';
if (hours > 24 || hours < 0)
return false;
if (*ptr++ != ':')
return false;
mins = (*ptr++ - '0') * 10;
mins += *ptr++ - '0';
if (mins > 60 || mins < 0)
return false;
if (*ptr++ != ':')
return false;
secs = (*ptr++ - '0') * 10;
secs += *ptr++ - '0';
if (secs > 60 || secs < 0)
return false;
if (*ptr++ != ',')
return false;
msecs = (*ptr++ - '0') * 100;
msecs += (*ptr++ - '0') * 10;
msecs += *ptr++ - '0';
if (msecs > 1000 || msecs < 0)
return false;
*res = 1000 * (3600 * hours + 60 * mins + secs) + msecs;
*pptr = ptr;
return true;
}
int SRTEntryComparator(const void *item1, const void *item2) {
const SRTEntry *l = *(const SRTEntry * const *)item1;
const SRTEntry *r = *(const SRTEntry * const *)item2;
return l->start - r->start;
}
int SRTEntryComparatorBSearch(const void *item1, const void *item2) {
const SRTEntry *k = *(const SRTEntry * const *)item1;
const SRTEntry *i = *(const SRTEntry * const *)item2;
if (k->start < i->start)
return -1;
if (k->start > i->end - 1)
return 1;
return 0;
}
bool SRTParser::parseFile(const Common::Path &fname) {
Common::File f;
cleanup();
if (!f.open(fname)) {
return false;
}
byte buf[3];
f.read(buf, 3);
int line = 0;
// Skip UTF header if present
if (buf[0] != 0xef || buf[1] != 0xbb || buf[2] != 0xbf)
f.seek(0);
while (!f.eos()) {
Common::String sseq, stimespec, stmp, text;
sseq = f.readLine(); line++;
stimespec = f.readLine(); line++;
text = f.readLine(); line++;
if (sseq.empty()) {
if (f.eos()) {
// Normal end of stream
break;
} else {
warning("Bad SRT file format (spec): %s at line %d", fname.toString().c_str(), line);
break;
}
}
if (stimespec.empty() || text.empty()) {
warning("Bad SRT file format (spec): %s at line %d", fname.toString().c_str(), line);
break;
}
// Read all multiline text
while (!f.eos()) {
stmp = f.readLine(); line++;
if (!stmp.empty()) {
text += '\n';
text += stmp;
} else {
break;
}
}
uint32 seq = atol(sseq.c_str());
if (seq == 0) {
warning("Bad SRT file format (seq): %s at line %d", fname.toString().c_str(), line);
break;
}
// 00:20:41,150 --> 00:20:45,109
if (stimespec.size() < 29) {
warning("Bad SRT file format (timespec length %d): %s at line %d", stimespec.size(), fname.toString().c_str(), line);
break;
}
const char *ptr = stimespec.c_str();
uint32 start, end;
if (!parseTime(&ptr, &start)) {
warning("Bad SRT file format (timespec start): %s at line %d", fname.toString().c_str(), line);
break;
}
while (*ptr == ' ')
ptr++;
while (*ptr == '-')
ptr++;
if (*ptr != '>') {
warning("Bad SRT file format (timespec middle ('%c')): %s at line %d", *ptr, fname.toString().c_str(), line);
break;
}
ptr++;
while (*ptr == ' ')
ptr++;
if (!parseTime(&ptr, &end)) {
warning("Bad SRT file format (timespec end): %s at line %d", fname.toString().c_str(), line);
break;
}
SRTEntry *entry = new SRTEntry(seq, start, end);
parseTextAndTags(text, entry->parts);
_entries.push_back(entry);
}
qsort(_entries.data(), _entries.size(), sizeof(SRTEntry *), &SRTEntryComparator);
debug(6, "SRTParser: Loaded %d entries", _entries.size());
return true;
}
const Common::Array<SubtitlePart> *SRTParser::getSubtitleParts(uint32 timestamp) const {
SRTEntry test(0, timestamp, 0, "");
SRTEntry *testptr = &test;
const SRTEntry **entry = (const SRTEntry **)bsearch(&testptr, _entries.data(), _entries.size(), sizeof(SRTEntry *), &SRTEntryComparatorBSearch);
if (entry == NULL)
return nullptr;
return &(*entry)->parts;
}
bool SRTParser::isSfx() const {
if (_entries.empty() || _entries[0]->parts.empty())
return false;
return _entries[0]->parts[0].tag == "sfx";
}
#define SHADOW 1
Subtitles::Subtitles() : _loaded(false), _hPad(0), _vPad(0), _overlayHasAlpha(true),
_lastOverlayWidth(-1), _lastOverlayHeight(-1) {
_subtitleDev = ConfMan.getBool("subtitle_dev");
}
Subtitles::~Subtitles() {
close();
_surface.free();
for (const auto &font : _fonts) {
FontMan.mayDeleteFont(font._value);
}
}
void Subtitles::setFont(const char *fontname, int height, FontStyle type) {
_fontHeight = height;
if (_fonts[type]) {
FontMan.mayDeleteFont(_fonts[type]);
_fonts[type] = nullptr;
}
#ifdef USE_FREETYPE2
Graphics::Font *font = nullptr;
Common::File *file = new Common::File();
if (file->open(fontname)) {
font = Graphics::loadTTFFont(file, DisposeAfterUse::YES, _fontHeight, Graphics::kTTFSizeModeCharacter, 96);
if (!font)
delete file;
else
_fonts[type] = font;
} else {
delete file;
}
if (!font) {
_fonts[type] = Graphics::loadTTFFontFromArchive(fontname, _fontHeight, Graphics::kTTFSizeModeCharacter, 96);
}
#endif
if (!_fonts[type]) {
debug(1, "Cannot load font %s directly", fontname);
_fonts[type] = FontMan.getFontByName(fontname);
}
if (!_fonts[type]) {
warning("Cannot load font %s", fontname);
_fonts[type] = FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont);
}
}
void Subtitles::loadSRTFile(const Common::Path &fname) {
debug(1, "loadSRTFile('%s')", fname.toString().c_str());
if (_subtitleDev)
_fname = fname;
_srtParser = new SRTParser();
_loaded = _srtParser->parseFile(fname);
}
void Subtitles::close() {
_loaded = false;
_parts = nullptr;
_fname.clear();
delete _srtParser;
_srtParser = nullptr;
}
void Subtitles::setBBox(const Common::Rect &bbox) {
_requestedBBox = bbox;
Graphics::PixelFormat overlayFormat = g_system->getOverlayFormat();
_overlayHasAlpha = overlayFormat.aBits() != 0;
_surface.create(_requestedBBox.width() + SHADOW * 2, _requestedBBox.height() + SHADOW * 2, overlayFormat);
// Force recalculation of real bounding box
_lastOverlayWidth = -1;
_lastOverlayHeight = -1;
}
void Subtitles::setColor(byte r, byte g, byte b) {
_color = _surface.format.ARGBToColor(255, r, g, b);
_blackColor = _surface.format.ARGBToColor(255, 0, 0, 0);
_transparentColor = _surface.format.ARGBToColor(0, 0, 0, 0);
}
void Subtitles::setPadding(uint16 horizontal, uint16 vertical) {
_hPad = horizontal;
_vPad = vertical;
}
bool Subtitles::recalculateBoundingBox() const {
int16 width = g_system->getOverlayWidth(),
height = g_system->getOverlayHeight();
if (width != _lastOverlayWidth ||
height != _lastOverlayHeight) {
_lastOverlayWidth = width;
_lastOverlayHeight = height;
// Recalculate the real bounding box to use
_realBBox = _requestedBBox;
if (_realBBox.bottom > height) {
// First try to move the bounding box
_realBBox.top -= _realBBox.bottom - height;
_realBBox.bottom = height;
}
if (_realBBox.top < 0) {
// Not enough space
_realBBox.top = 0;
}
if (_realBBox.right > width) {
// First try to move the bounding box
_realBBox.left -= _realBBox.right - width;
_realBBox.right = width;
}
if (_realBBox.left < 0) {
// Not enough space
_realBBox.left = 0;
}
return true;
}
return false;
}
bool Subtitles::drawSubtitle(uint32 timestamp, bool force, bool showSFX) const {
const Common::Array<SubtitlePart> *parts;
bool isSFX = false;
if (_loaded && _srtParser) {
parts = _srtParser->getSubtitleParts(timestamp);
if (parts && !parts->empty()) {
isSFX = (*parts)[0].tag == "sfx";
}
} else if (_subtitleDev) {
// Force refresh
_parts = nullptr;
Common::String subtitle = _fname.toString('/');
uint32 hours, mins, secs, msecs;
secs = timestamp / 1000;
hours = secs / 3600;
mins = (secs / 60) % 60;
secs %= 60;
msecs = timestamp % 1000;
subtitle += " " + Common::String::format("%02u:%02u:%02u,%03u", hours, mins, secs, msecs);
if (_devParts.empty()) {
_devParts.push_back(SubtitlePart("", ""));
}
_devParts[0].text = subtitle;
parts = &_devParts;
} else {
return false;
}
force |= recalculateBoundingBox();
if (!force && _overlayHasAlpha && parts == _parts)
return false;
if (force || parts != _parts) {
if (debugLevelSet(1)) {
Common::String subtitle;
if (parts) {
for (const auto &part : *parts) {
subtitle += part.text;
}
}
debug(1, "%d: %s", timestamp, subtitle.c_str());
}
_parts = parts;
if (!isSFX || showSFX)
renderSubtitle();
}
updateSubtitleOverlay();
return true;
}
void Subtitles::clearSubtitle() const {
if (!_loaded)
return;
g_system->hideOverlay();
_drawRect.setEmpty();
_surface.fillRect(Common::Rect(0, 0, _surface.w, _surface.h), _transparentColor);
}
void Subtitles::updateSubtitleOverlay() const {
if (!_loaded)
return;
if (!shouldShowSubtitle()) {
g_system->hideOverlay();
return;
}
if (!g_system->isOverlayVisible()) {
g_system->clearOverlay();
g_system->showOverlay(false);
}
if (_overlayHasAlpha) {
// When we have alpha, draw the whole surface without thinking it more
g_system->copyRectToOverlay(_surface.getPixels(), _surface.pitch, _realBBox.left, _realBBox.top, _realBBox.width(), _realBBox.height());
} else {
// When overlay doesn't have alpha, showing it hides the underlying game screen
// We force a copy of the game screen to the overlay by clearing it
// We then draw the smallest possible surface to minimize black rectangle behind text
g_system->clearOverlay();
g_system->copyRectToOverlay((byte *)_surface.getPixels() + _drawRect.top * _surface.pitch + _drawRect.left * _surface.format.bytesPerPixel, _surface.pitch,
_realBBox.left + _drawRect.left, _realBBox.top + _drawRect.top, _drawRect.width(), _drawRect.height());
}
}
struct SubtitleRenderingPart {
Common::U32String text;
const Graphics::Font *font;
bool newLine;
int left;
int right;
};
void Subtitles::renderSubtitle() const {
_surface.fillRect(Common::Rect(0, 0, _surface.w, _surface.h), _transparentColor);
if (!_parts || _parts->empty()) {
_drawRect.setEmpty();
return;
}
Common::Array<SubtitleRenderingPart> splitParts;
// First, calculate all positions as if we were left aligned
bool newLine = true;
int currentX = 0;
for (const auto &part : *_parts) {
const Graphics::Font *font = _fonts[part.tag == "i" ? kFontStyleItalic : kFontStyleRegular];
if (!font) font = _fonts[kFontStyleRegular];
Common::Array<Common::U32String> lines;
font->wordWrapText(part.text.decode(Common::kUtf8), _realBBox.width(), lines, currentX);
if (lines.empty()) {
continue;
}
splitParts.reserve(splitParts.size() + lines.size());
int width = 0;
for (auto line = lines.begin(); line != lines.end() - 1; line++) {
width = font->getStringWidth(*line);
splitParts.emplace_back(SubtitleRenderingPart{*line, font, newLine, currentX, currentX + width});
newLine = true;
currentX = 0;
}
width = font->getStringWidth(lines.back());
splitParts.emplace_back(SubtitleRenderingPart{lines.back(), font, newLine, currentX, currentX + width});
newLine = false;
currentX += width;
// Last newline doesn't trigger an empty line in wordWrapText
if (part.text.hasSuffix("\n")) {
newLine = true;
currentX = 0;
}
}
_splitPartCount = (uint16)splitParts.size();
// Then, center all lines and calculate the drawing box
auto lineBegin = splitParts.begin();
int minX = _realBBox.width();
int maxWidth = 0;
for (auto splitPart = splitParts.begin() + 1; splitPart != splitParts.end(); splitPart++) {
if (!splitPart->newLine) {
continue;
}
int width = MIN(splitPart[-1].right + 2 * _hPad, (int)_realBBox.width());
int origin = (_realBBox.width() - width) / 2;
minX = MIN(minX, origin);
maxWidth = MAX(maxWidth, width);
for(auto part = lineBegin; part != splitPart; part++) {
part->left += origin;
part->right += origin;
}
lineBegin = splitPart;
}
if (lineBegin != splitParts.end()) {
int width = MIN(splitParts.back().right + 2 * _hPad, (int)_realBBox.width());
int origin = (_realBBox.width() - width) / 2;
minX = MIN(minX, origin);
maxWidth = MAX(maxWidth, width);
for(auto part = lineBegin; part != splitParts.end(); part++) {
part->left += origin;
part->right += origin;
}
}
// Finally, render every part on the surface
int currentY = _vPad;
int lineHeight = 0;
for (const auto &part : splitParts) {
const Graphics::Font *font = part.font;
int partWidth = part.right - part.left;
if (part.newLine) {
currentY += lineHeight;
if (currentY + _vPad > _realBBox.bottom) {
lineHeight = 0;
break;
}
lineHeight = font->getFontHeight();
}
Common::U32String u32_text = convertBiDiU32String(part.text).visual;
font->drawString(&_surface, u32_text, part.left, currentY, partWidth, _blackColor, Graphics::kTextAlignLeft);
font->drawString(&_surface, u32_text, part.left + SHADOW * 2, currentY, partWidth, _blackColor, Graphics::kTextAlignLeft);
font->drawString(&_surface, u32_text, part.left, currentY + SHADOW * 2, partWidth, _blackColor, Graphics::kTextAlignLeft);
font->drawString(&_surface, u32_text, part.left + SHADOW * 2, currentY + SHADOW * 2, partWidth, _blackColor, Graphics::kTextAlignLeft);
font->drawString(&_surface, u32_text, part.left + SHADOW, currentY + SHADOW, partWidth, _color, Graphics::kTextAlignLeft);
}
currentY += lineHeight + _vPad;
_drawRect.left = minX;
_drawRect.top = 0;
_drawRect.setWidth(maxWidth + SHADOW * 2);
_drawRect.setHeight(currentY + SHADOW * 2);
_drawRect.clip(_realBBox.width(), _realBBox.height());
}
} // End of namespace Video

141
video/subtitles.h Normal file
View File

@@ -0,0 +1,141 @@
/* 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 VIDEO_SUBTITLES_H
#define VIDEO_SUBTITLES_H
#include "common/str.h"
#include "common/array.h"
#include "common/hashmap.h"
#include "common/rect.h"
#include "graphics/surface.h"
namespace Graphics {
class Font;
}
namespace Video {
struct SubtitlePart {
Common::String text;
Common::String tag;
SubtitlePart(const Common::String &text_, const Common::String &tag_) : text(text_), tag(tag_) {}
};
struct SRTEntry {
uint seq;
uint32 start;
uint32 end;
Common::Array<SubtitlePart> parts;
SRTEntry(uint seq_, uint32 start_, uint32 end_) {
seq = seq_; start = start_; end = end_;
}
// Dummy constructor for bsearch
SRTEntry(uint seq_, uint32 start_, uint32 end_, const Common::String &text, const Common::String &tag = "") {
seq = seq_; start = start_; end = end_;
parts.push_back(SubtitlePart(text, tag));
}
};
class SRTParser {
public:
SRTParser();
~SRTParser();
void cleanup();
bool parseFile(const Common::Path &fname);
void parseTextAndTags(const Common::String &text, Common::Array<SubtitlePart> &parts) const;
const Common::Array<SubtitlePart> *getSubtitleParts(uint32 timestamp) const;
bool isSfx() const;
private:
Common::Array<SRTEntry *> _entries;
};
class Subtitles {
public:
enum FontStyle : int {
kFontStyleRegular = 0,
kFontStyleItalic,
};
Subtitles();
virtual ~Subtitles();
void loadSRTFile(const Common::Path &fname);
void close();
void setFont(const char *fontname, int height = 18, FontStyle type = kFontStyleRegular);
void setBBox(const Common::Rect &bbox);
void setColor(byte r, byte g, byte b);
void setPadding(uint16 horizontal, uint16 vertical);
bool drawSubtitle(uint32 timestamp, bool force = false, bool showSFX = false) const;
bool isSfx() const {
if (!_srtParser)
return false;
return _srtParser->isSfx();
}
bool isLoaded() const { return _loaded || _subtitleDev; }
virtual void clearSubtitle() const;
protected:
bool recalculateBoundingBox() const;
void renderSubtitle() const;
void translateBBox(int16 dx, int16 dy) const { _realBBox.translate(dx, dy); }
virtual void updateSubtitleOverlay() const;
virtual bool shouldShowSubtitle() const { return true; }
bool _loaded;
mutable const Common::Array<SubtitlePart> *_parts = nullptr;
mutable uint16 _splitPartCount = 0;
private:
SRTParser *_srtParser = nullptr;
bool _subtitleDev;
bool _overlayHasAlpha;
mutable Common::Array<SubtitlePart> _devParts;
Common::HashMap<int, const Graphics::Font *> _fonts;
int _fontHeight;
mutable Graphics::Surface _surface;
mutable Common::Rect _drawRect;
Common::Rect _requestedBBox;
mutable Common::Rect _realBBox;
mutable int16 _lastOverlayWidth, _lastOverlayHeight;
Common::Path _fname;
uint32 _color;
uint32 _blackColor;
uint32 _transparentColor;
uint16 _hPad;
uint16 _vPad;
};
} // End of namespace Video
#endif

549
video/theora_decoder.cpp Normal file
View File

@@ -0,0 +1,549 @@
/* 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/>.
*
*/
/*
* Source is based on the player example from libvorbis package,
* available at: https://gitlab.xiph.org/xiph/theora/-/blob/main/examples/player_example.c
*
* THIS FILE IS PART OF THE OggTheora SOFTWARE CODEC SOURCE CODE.
* USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS
* GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE
* IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING.
*
* THE Theora SOURCE CODE IS COPYRIGHT (C) 2002-2009
* by the Xiph.Org Foundation and contributors http://www.xiph.org/
*
*/
#include "video/theora_decoder.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
#include "common/stream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "graphics/pixelformat.h"
#include "graphics/yuv_to_rgb.h"
#include "image/codecs/codec.h"
namespace Video {
TheoraDecoder::TheoraDecoder() {
_fileStream = 0;
_videoTrack = 0;
_audioTrack = 0;
_hasVideo = _hasAudio = false;
}
TheoraDecoder::~TheoraDecoder() {
close();
}
bool TheoraDecoder::loadStream(Common::SeekableReadStream *stream) {
close();
_fileStream = stream;
// start up Ogg stream synchronization layer
ogg_sync_init(&_oggSync);
// init supporting Vorbis structures needed in header parsing
vorbis_info_init(&_vorbisInfo);
vorbis_comment vorbisComment;
vorbis_comment_init(&vorbisComment);
// init supporting Theora structures needed in header parsing
th_info theoraInfo;
th_info_init(&theoraInfo);
th_comment theoraComment;
th_comment_init(&theoraComment);
th_setup_info *theoraSetup = 0;
uint theoraPackets = 0, vorbisPackets = 0;
// Ogg file open; parse the headers
// Only interested in Vorbis/Theora streams
bool foundHeader = false;
while (!foundHeader) {
int ret = bufferData();
if (ret == 0)
break; // FIXME: Shouldn't this error out?
while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) {
ogg_stream_state test;
// is this a mandated initial header? If not, stop parsing
if (!ogg_page_bos(&_oggPage)) {
// don't leak the page; get it into the appropriate stream
queuePage(&_oggPage);
foundHeader = true;
break;
}
ogg_stream_init(&test, ogg_page_serialno(&_oggPage));
ogg_stream_pagein(&test, &_oggPage);
ogg_stream_packetout(&test, &_oggPacket);
// identify the codec: try theora
if (theoraPackets == 0 && th_decode_headerin(&theoraInfo, &theoraComment, &theoraSetup, &_oggPacket) >= 0) {
// it is theora
memcpy(&_theoraOut, &test, sizeof(test));
theoraPackets = 1;
_hasVideo = true;
} else if (vorbisPackets == 0 && vorbis_synthesis_headerin(&_vorbisInfo, &vorbisComment, &_oggPacket) >= 0) {
// it is vorbis
memcpy(&_vorbisOut, &test, sizeof(test));
vorbisPackets = 1;
_hasAudio = true;
} else {
// whatever it is, we don't care about it
ogg_stream_clear(&test);
}
}
// fall through to non-bos page parsing
}
// we're expecting more header packets.
while ((theoraPackets && theoraPackets < 3) || (vorbisPackets && vorbisPackets < 3)) {
int ret;
// look for further theora headers
while (theoraPackets && (theoraPackets < 3) && (ret = ogg_stream_packetout(&_theoraOut, &_oggPacket))) {
if (ret < 0)
error("Error parsing Theora stream headers; corrupt stream?");
if (!th_decode_headerin(&theoraInfo, &theoraComment, &theoraSetup, &_oggPacket))
error("Error parsing Theora stream headers; corrupt stream?");
theoraPackets++;
}
// look for more vorbis header packets
while (vorbisPackets && (vorbisPackets < 3) && (ret = ogg_stream_packetout(&_vorbisOut, &_oggPacket))) {
if (ret < 0)
error("Error parsing Vorbis stream headers; corrupt stream?");
if (vorbis_synthesis_headerin(&_vorbisInfo, &vorbisComment, &_oggPacket))
error("Error parsing Vorbis stream headers; corrupt stream?");
vorbisPackets++;
if (vorbisPackets == 3)
break;
}
// The header pages/packets will arrive before anything else we
// care about, or the stream is not obeying spec
if (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) {
queuePage(&_oggPage); // demux into the appropriate stream
} else {
ret = bufferData(); // someone needs more data
if (ret == 0)
error("End of file while searching for codec headers.");
}
}
// And now we have it all. Initialize decoders next
if (_hasVideo) {
_videoTrack = new TheoraVideoTrack(theoraInfo, theoraSetup);
addTrack(_videoTrack);
}
th_info_clear(&theoraInfo);
th_comment_clear(&theoraComment);
th_setup_free(theoraSetup);
if (_hasAudio) {
_audioTrack = new VorbisAudioTrack(getSoundType(), _vorbisInfo);
// Get enough audio data to start us off
while (!_audioTrack->hasAudio()) {
// Queue more data
bufferData();
while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0)
queuePage(&_oggPage);
queueAudio();
}
addTrack(_audioTrack);
}
vorbis_comment_clear(&vorbisComment);
return true;
}
void TheoraDecoder::close() {
VideoDecoder::close();
if (!_fileStream)
return;
if (_videoTrack) {
ogg_stream_clear(&_theoraOut);
_videoTrack = 0;
}
if (_audioTrack) {
ogg_stream_clear(&_vorbisOut);
_audioTrack = 0;
}
ogg_sync_clear(&_oggSync);
vorbis_info_clear(&_vorbisInfo);
delete _fileStream;
_fileStream = 0;
_hasVideo = _hasAudio = false;
}
void TheoraDecoder::readNextPacket() {
// First, let's get our frame
if (_hasVideo) {
while (!_videoTrack->endOfTrack()) {
// theora is one in, one out...
if (ogg_stream_packetout(&_theoraOut, &_oggPacket) > 0) {
if (_videoTrack->decodePacket(_oggPacket))
break;
} else if (_theoraOut.e_o_s || _fileStream->eos()) {
// If we can't get any more frames, we're done.
_videoTrack->setEndOfVideo();
} else {
// Queue more data
bufferData();
while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0)
queuePage(&_oggPage);
}
// Update audio if we can
queueAudio();
}
}
// Then make sure we have enough audio buffered
ensureAudioBufferSize();
}
Common::Rational TheoraDecoder::getFrameRate() const {
if (_videoTrack)
return _videoTrack->getFrameRate();
return Common::Rational();
}
TheoraDecoder::TheoraVideoTrack::TheoraVideoTrack(th_info &theoraInfo, th_setup_info *theoraSetup) {
_theoraDecode = th_decode_alloc(&theoraInfo, theoraSetup);
if (theoraInfo.pixel_fmt != TH_PF_420 && theoraInfo.pixel_fmt != TH_PF_422 && theoraInfo.pixel_fmt != TH_PF_444) {
error("Found unknown Theora format (must be YUV420, YUV422 or YUV444)");
}
int postProcessingMax;
th_decode_ctl(_theoraDecode, TH_DECCTL_GET_PPLEVEL_MAX, &postProcessingMax, sizeof(postProcessingMax));
th_decode_ctl(_theoraDecode, TH_DECCTL_SET_PPLEVEL, &postProcessingMax, sizeof(postProcessingMax));
_x = theoraInfo.pic_x;
_y = theoraInfo.pic_y;
_width = theoraInfo.pic_width;
_height = theoraInfo.pic_height;
_surfaceWidth = theoraInfo.frame_width;
_surfaceHeight = theoraInfo.frame_height;
_pixelFormat = Image::Codec::getDefaultYUVFormat();
_theoraPixelFormat = theoraInfo.pixel_fmt;
// Set the frame rate
_frameRate = Common::Rational(theoraInfo.fps_numerator, theoraInfo.fps_denominator);
_endOfVideo = false;
_nextFrameStartTime = 0.0;
_curFrame = -1;
_surface = nullptr;
_displaySurface = nullptr;
}
TheoraDecoder::TheoraVideoTrack::~TheoraVideoTrack() {
th_decode_free(_theoraDecode);
if (_surface) {
_surface->free();
delete _surface;
_surface = nullptr;
}
if (_displaySurface) {
_displaySurface->setPixels(0);
delete _displaySurface;
_displaySurface = nullptr;
}
}
bool TheoraDecoder::TheoraVideoTrack::decodePacket(ogg_packet &oggPacket) {
int decodeRes = th_decode_packetin(_theoraDecode, &oggPacket, 0);
bool gotNewFrame = decodeRes == 0; // new frame, decoding needed
bool gotDupFrame = decodeRes == TH_DUPFRAME; // no decoding needed, just update timing
if (gotNewFrame || gotDupFrame) {
if (gotNewFrame) {
// Convert YUV data to RGB data
th_ycbcr_buffer yuv;
th_decode_ycbcr_out(_theoraDecode, yuv);
translateYUVtoRGBA(yuv);
}
// We set the current frame counter, delegating the calculation to libtheora
_curFrame = (int) th_granule_frame(_theoraDecode, oggPacket.granulepos);
double time = th_granule_time(_theoraDecode, oggPacket.granulepos);
// We need to calculate when the next frame should be shown
// This is all in floating point because that's what the Ogg code gives us
// Ogg is a lossy container format, so it doesn't always list the time to the
// next frame. In such cases, we need to calculate it ourselves.
if (time == -1.0)
_nextFrameStartTime += _frameRate.getInverse().toDouble();
else
_nextFrameStartTime = time;
return true;
}
return false;
}
enum TheoraYUVBuffers {
kBufferY = 0,
kBufferU = 1,
kBufferV = 2
};
void TheoraDecoder::TheoraVideoTrack::translateYUVtoRGBA(th_ycbcr_buffer &YUVBuffer) {
// Width and height of all buffers have to be divisible by 2.
assert((YUVBuffer[kBufferY].width & 1) == 0);
assert((YUVBuffer[kBufferY].height & 1) == 0);
assert((YUVBuffer[kBufferU].width & 1) == 0);
assert((YUVBuffer[kBufferV].width & 1) == 0);
// UV components must be half or equal the Y component
assert((YUVBuffer[kBufferU].width == YUVBuffer[kBufferY].width >> 1) || (YUVBuffer[kBufferU].width == YUVBuffer[kBufferY].width));
assert((YUVBuffer[kBufferV].width == YUVBuffer[kBufferY].width >> 1) || (YUVBuffer[kBufferV].width == YUVBuffer[kBufferY].width));
assert((YUVBuffer[kBufferU].height == YUVBuffer[kBufferY].height >> 1) || (YUVBuffer[kBufferU].height == YUVBuffer[kBufferY].height));
assert((YUVBuffer[kBufferV].height == YUVBuffer[kBufferY].height >> 1) || (YUVBuffer[kBufferV].height == YUVBuffer[kBufferY].height));
if (!_surface) {
_surface = new Graphics::Surface();
_surface->create(_surfaceWidth, _surfaceHeight, _pixelFormat);
}
// Set up a display surface
if (!_displaySurface) {
_displaySurface = new Graphics::Surface();
_displaySurface->init(_width, _height, _surface->pitch,
_surface->getBasePtr(_x, _y), _surface->format);
}
switch (_theoraPixelFormat) {
case TH_PF_420:
YUVToRGBMan.convert420(_surface, Graphics::YUVToRGBManager::kScaleITU, YUVBuffer[kBufferY].data, YUVBuffer[kBufferU].data, YUVBuffer[kBufferV].data, YUVBuffer[kBufferY].width, YUVBuffer[kBufferY].height, YUVBuffer[kBufferY].stride, YUVBuffer[kBufferU].stride);
break;
case TH_PF_422:
YUVToRGBMan.convert422(_surface, Graphics::YUVToRGBManager::kScaleITU, YUVBuffer[kBufferY].data, YUVBuffer[kBufferU].data, YUVBuffer[kBufferV].data, YUVBuffer[kBufferY].width, YUVBuffer[kBufferY].height, YUVBuffer[kBufferY].stride, YUVBuffer[kBufferU].stride);
break;
case TH_PF_444:
YUVToRGBMan.convert444(_surface, Graphics::YUVToRGBManager::kScaleITU, YUVBuffer[kBufferY].data, YUVBuffer[kBufferU].data, YUVBuffer[kBufferV].data, YUVBuffer[kBufferY].width, YUVBuffer[kBufferY].height, YUVBuffer[kBufferY].stride, YUVBuffer[kBufferU].stride);
break;
default:
error("Unsupported Theora pixel format");
}
}
static vorbis_info *info = 0;
TheoraDecoder::VorbisAudioTrack::VorbisAudioTrack(Audio::Mixer::SoundType soundType, vorbis_info &vorbisInfo) :
AudioTrack(soundType) {
vorbis_synthesis_init(&_vorbisDSP, &vorbisInfo);
vorbis_block_init(&_vorbisDSP, &_vorbisBlock);
info = &vorbisInfo;
_audStream = Audio::makeQueuingAudioStream(vorbisInfo.rate, vorbisInfo.channels != 1);
_audioBufferFill = 0;
_audioBuffer = 0;
_endOfAudio = false;
}
TheoraDecoder::VorbisAudioTrack::~VorbisAudioTrack() {
vorbis_dsp_clear(&_vorbisDSP);
vorbis_block_clear(&_vorbisBlock);
delete _audStream;
free(_audioBuffer);
}
Audio::AudioStream *TheoraDecoder::VorbisAudioTrack::getAudioStream() const {
return _audStream;
}
#define AUDIOFD_FRAGSIZE 10240
#ifndef USE_TREMOR
static double rint(double v) {
return floor(v + 0.5);
}
#endif
bool TheoraDecoder::VorbisAudioTrack::decodeSamples() {
#ifdef USE_TREMOR
ogg_int32_t **pcm;
#else
float **pcm;
#endif
// if there's pending, decoded audio, grab it
int ret = vorbis_synthesis_pcmout(&_vorbisDSP, &pcm);
if (ret > 0) {
if (!_audioBuffer) {
_audioBuffer = (ogg_int16_t *)malloc(AUDIOFD_FRAGSIZE * sizeof(ogg_int16_t));
assert(_audioBuffer);
}
int channels = _audStream->isStereo() ? 2 : 1;
int count = _audioBufferFill / 2;
int maxsamples = ((AUDIOFD_FRAGSIZE - _audioBufferFill) / channels) >> 1;
int i;
for (i = 0; i < ret && i < maxsamples; i++) {
for (int j = 0; j < channels; j++) {
#ifdef USE_TREMOR
int val = CLIP((int)pcm[j][i] >> 9, -32768, 32767);
#else
int val = CLIP((int)rint(pcm[j][i] * 32767.f), -32768, 32767);
#endif
_audioBuffer[count++] = val;
}
}
vorbis_synthesis_read(&_vorbisDSP, i);
_audioBufferFill += (i * channels) << 1;
if (_audioBufferFill == AUDIOFD_FRAGSIZE) {
byte flags = Audio::FLAG_16BITS;
if (_audStream->isStereo())
flags |= Audio::FLAG_STEREO;
#ifdef SCUMM_LITTLE_ENDIAN
flags |= Audio::FLAG_LITTLE_ENDIAN;
#endif
_audStream->queueBuffer((byte *)_audioBuffer, AUDIOFD_FRAGSIZE, DisposeAfterUse::YES, flags);
// The audio mixer is now responsible for the old audio buffer.
// We need to create a new one.
_audioBuffer = 0;
_audioBufferFill = 0;
}
return true;
}
return false;
}
bool TheoraDecoder::VorbisAudioTrack::hasAudio() const {
return _audStream->numQueuedStreams() > 0;
}
bool TheoraDecoder::VorbisAudioTrack::needsAudio() const {
// TODO: 5 is very arbitrary. We probably should do something like QuickTime does.
return !_endOfAudio && _audStream->numQueuedStreams() < 5;
}
void TheoraDecoder::VorbisAudioTrack::synthesizePacket(ogg_packet &oggPacket) {
if (vorbis_synthesis(&_vorbisBlock, &oggPacket) == 0) // test for success
vorbis_synthesis_blockin(&_vorbisDSP, &_vorbisBlock);
}
void TheoraDecoder::queuePage(ogg_page *page) {
if (_hasVideo)
ogg_stream_pagein(&_theoraOut, page);
if (_hasAudio)
ogg_stream_pagein(&_vorbisOut, page);
}
int TheoraDecoder::bufferData() {
char *buffer = ogg_sync_buffer(&_oggSync, 4096);
int bytes = _fileStream->read(buffer, 4096);
ogg_sync_wrote(&_oggSync, bytes);
return bytes;
}
bool TheoraDecoder::queueAudio() {
if (!_hasAudio)
return false;
bool queuedAudio = false;
for (;;) {
if (_audioTrack->decodeSamples()) {
// we queued some pending audio
queuedAudio = true;
} else if (ogg_stream_packetout(&_vorbisOut, &_oggPacket) > 0) {
// no pending audio; is there a pending packet to decode?
_audioTrack->synthesizePacket(_oggPacket);
} else {
// we've buffered all we have, break out for now
break;
}
}
return queuedAudio;
}
void TheoraDecoder::ensureAudioBufferSize() {
if (!_hasAudio)
return;
// Force at least some audio to be buffered
while (_audioTrack->needsAudio()) {
bufferData();
while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0)
queuePage(&_oggPage);
bool queuedAudio = queueAudio();
if ((_vorbisOut.e_o_s || _fileStream->eos()) && !queuedAudio) {
_audioTrack->setEndOfAudio();
break;
}
}
}
} // End of namespace Video

181
video/theora_decoder.h Normal file
View File

@@ -0,0 +1,181 @@
/* 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/scummsys.h" // for USE_THEORADEC
#ifdef USE_THEORADEC
#ifndef VIDEO_THEORA_DECODER_H
#define VIDEO_THEORA_DECODER_H
#include "common/rational.h"
#include "video/video_decoder.h"
#include "audio/mixer.h"
#include "graphics/surface.h"
#include <theora/theoradec.h>
#ifdef USE_TREMOR
#include <tremor/ivorbiscodec.h>
#else
#include <vorbis/codec.h>
#endif
namespace Common {
class SeekableReadStream;
}
namespace Audio {
class AudioStream;
class QueuingAudioStream;
}
namespace Video {
/**
*
* Decoder for Theora videos.
* Video decoder used in engines:
* - ags
* - asylum
* - grim
* - pegasus
* - sword25
* - tetraedge
* - wintermute
*/
class TheoraDecoder : public VideoDecoder {
public:
TheoraDecoder();
virtual ~TheoraDecoder();
/**
* Load a video file
* @param stream the stream to load
*/
bool loadStream(Common::SeekableReadStream *stream);
void close();
/** Frames per second of the loaded video. */
Common::Rational getFrameRate() const;
protected:
void readNextPacket();
private:
class TheoraVideoTrack : public VideoTrack {
public:
TheoraVideoTrack(th_info &theoraInfo, th_setup_info *theoraSetup);
~TheoraVideoTrack();
bool endOfTrack() const { return _endOfVideo; }
uint16 getWidth() const { return _width; }
uint16 getHeight() const { return _height; }
Graphics::PixelFormat getPixelFormat() const { return _pixelFormat; }
bool setOutputPixelFormat(const Graphics::PixelFormat &format) {
if (format.bytesPerPixel != 2 && format.bytesPerPixel != 4)
return false;
_pixelFormat = format;
return true;
}
int getCurFrame() const { return _curFrame; }
const Common::Rational &getFrameRate() const { return _frameRate; }
uint32 getNextFrameStartTime() const { return (uint32)(_nextFrameStartTime * 1000); }
const Graphics::Surface *decodeNextFrame() { return _displaySurface; }
bool decodePacket(ogg_packet &oggPacket);
void setEndOfVideo() { _endOfVideo = true; }
private:
int _curFrame;
bool _endOfVideo;
Common::Rational _frameRate;
double _nextFrameStartTime;
Graphics::Surface *_surface;
Graphics::Surface *_displaySurface;
Graphics::PixelFormat _pixelFormat;
int _x;
int _y;
uint16 _width;
uint16 _height;
uint16 _surfaceWidth;
uint16 _surfaceHeight;
th_dec_ctx *_theoraDecode;
th_pixel_fmt _theoraPixelFormat;
void translateYUVtoRGBA(th_ycbcr_buffer &YUVBuffer);
};
class VorbisAudioTrack : public AudioTrack {
public:
VorbisAudioTrack(Audio::Mixer::SoundType soundType, vorbis_info &vorbisInfo);
~VorbisAudioTrack();
bool decodeSamples();
bool hasAudio() const;
bool needsAudio() const;
void synthesizePacket(ogg_packet &oggPacket);
void setEndOfAudio() { _endOfAudio = true; }
protected:
Audio::AudioStream *getAudioStream() const;
private:
// single audio fragment audio buffering
int _audioBufferFill;
ogg_int16_t *_audioBuffer;
Audio::QueuingAudioStream *_audStream;
vorbis_block _vorbisBlock;
vorbis_dsp_state _vorbisDSP;
bool _endOfAudio;
};
void queuePage(ogg_page *page);
int bufferData();
bool queueAudio();
void ensureAudioBufferSize();
Common::SeekableReadStream *_fileStream;
ogg_sync_state _oggSync;
ogg_page _oggPage;
ogg_packet _oggPacket;
ogg_stream_state _theoraOut, _vorbisOut;
bool _hasVideo, _hasAudio;
vorbis_info _vorbisInfo;
TheoraVideoTrack *_videoTrack;
VorbisAudioTrack *_audioTrack;
};
} // End of namespace Video
#endif
#endif

1086
video/video_decoder.cpp Normal file

File diff suppressed because it is too large Load Diff

1060
video/video_decoder.h Normal file

File diff suppressed because it is too large Load Diff