Initial commit
This commit is contained in:
484
video/3do_decoder.cpp
Normal file
484
video/3do_decoder.cpp
Normal 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
136
video/3do_decoder.h
Normal 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
1250
video/avi_decoder.cpp
Normal file
File diff suppressed because it is too large
Load Diff
375
video/avi_decoder.h
Normal file
375
video/avi_decoder.h
Normal 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
1747
video/bink_decoder.cpp
Normal file
File diff suppressed because it is too large
Load Diff
392
video/bink_decoder.h
Normal file
392
video/bink_decoder.h
Normal 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
577
video/binkdata.h
Normal 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
3101
video/coktel_decoder.cpp
Normal file
File diff suppressed because it is too large
Load Diff
658
video/coktel_decoder.h
Normal file
658
video/coktel_decoder.h
Normal 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
549
video/dxa_decoder.cpp
Normal 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
116
video/dxa_decoder.h
Normal 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
389
video/flic_decoder.cpp
Normal 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
116
video/flic_decoder.h
Normal 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
1215
video/hnm_decoder.cpp
Normal file
File diff suppressed because it is too large
Load Diff
240
video/hnm_decoder.h
Normal file
240
video/hnm_decoder.h
Normal 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
4
video/mkv/AUTHORS.TXT
Normal 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
30
video/mkv/LICENSE.TXT
Normal 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
23
video/mkv/PATENTS.TXT
Normal 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
143
video/mkv/README.libwebm
Normal 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
8062
video/mkv/mkvparser.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1147
video/mkv/mkvparser.h
Normal file
1147
video/mkv/mkvparser.h
Normal file
File diff suppressed because it is too large
Load Diff
193
video/mkv/webmids.h
Normal file
193
video/mkv/webmids.h
Normal 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
547
video/mkv_decoder.cpp
Normal 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
183
video/mkv_decoder.h
Normal 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
41
video/module.mk
Normal 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
915
video/mpegps_decoder.cpp
Normal 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
256
video/mpegps_decoder.h
Normal 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
547
video/mve_decoder.cpp
Normal 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
173
video/mve_decoder.h
Normal 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
615
video/paco_decoder.cpp
Normal 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
128
video/paco_decoder.h
Normal 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
642
video/psx_decoder.cpp
Normal 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
171
video/psx_decoder.h
Normal 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
323
video/qt_data.h
Normal 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
972
video/qt_decoder.cpp
Normal 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
451
video/qt_decoder.h
Normal 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
2036
video/qtvr_decoder.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1043
video/smk_decoder.cpp
Normal file
1043
video/smk_decoder.cpp
Normal file
File diff suppressed because it is too large
Load Diff
229
video/smk_decoder.h
Normal file
229
video/smk_decoder.h
Normal 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
636
video/subtitles.cpp
Normal 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
141
video/subtitles.h
Normal 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
549
video/theora_decoder.cpp
Normal 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
181
video/theora_decoder.h
Normal 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
1086
video/video_decoder.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1060
video/video_decoder.h
Normal file
1060
video/video_decoder.h
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user