aboutsummaryrefslogtreecommitdiff
path: root/video
diff options
context:
space:
mode:
Diffstat (limited to 'video')
-rw-r--r--video/avi_decoder.cpp6
-rw-r--r--video/avi_decoder.h4
-rw-r--r--video/bink_decoder.cpp4
-rw-r--r--video/bink_decoder.h2
-rw-r--r--video/codecs/cdtoons.h6
-rw-r--r--video/codecs/cinepak.cpp46
-rw-r--r--video/codecs/cinepak.h13
-rw-r--r--video/codecs/codec.h32
-rw-r--r--video/codecs/indeo3.h7
-rw-r--r--video/codecs/mjpeg.h10
-rw-r--r--video/codecs/msrle.h6
-rw-r--r--video/codecs/msvideo1.h6
-rw-r--r--video/codecs/qtrle.h6
-rw-r--r--video/codecs/rpza.cpp4
-rw-r--r--video/codecs/rpza.h6
-rw-r--r--video/codecs/smc.h6
-rw-r--r--video/codecs/svq1.cpp4
-rw-r--r--video/codecs/svq1.h6
-rw-r--r--video/codecs/truemotion1.h6
-rw-r--r--video/coktel_decoder.cpp2
-rw-r--r--video/flic_decoder.cpp12
-rw-r--r--video/flic_decoder.h1
-rw-r--r--video/psx_decoder.cpp2
-rw-r--r--video/qt_decoder.cpp214
-rw-r--r--video/qt_decoder.h6
-rw-r--r--video/smk_decoder.cpp4
-rw-r--r--video/theora_decoder.h1
-rw-r--r--video/video_decoder.cpp230
-rw-r--r--video/video_decoder.h101
29 files changed, 600 insertions, 153 deletions
diff --git a/video/avi_decoder.cpp b/video/avi_decoder.cpp
index 6062049b72..6fe9c773b8 100644
--- a/video/avi_decoder.cpp
+++ b/video/avi_decoder.cpp
@@ -457,6 +457,10 @@ void AVIDecoder::AVIAudioTrack::queueSound(Common::SeekableReadStream *stream) {
flags |= Audio::FLAG_STEREO;
_audStream->queueAudioStream(Audio::makeRawStream(stream, _wvInfo.samplesPerSec, flags, DisposeAfterUse::YES), DisposeAfterUse::YES);
+ } else if (_wvInfo.tag == kWaveFormatMSADPCM) {
+ _audStream->queueAudioStream(Audio::makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), Audio::kADPCMMS, _wvInfo.samplesPerSec, _wvInfo.channels, _wvInfo.blockAlign), DisposeAfterUse::YES);
+ } else if (_wvInfo.tag == kWaveFormatMSIMAADPCM) {
+ _audStream->queueAudioStream(Audio::makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), Audio::kADPCMMSIma, _wvInfo.samplesPerSec, _wvInfo.channels, _wvInfo.blockAlign), DisposeAfterUse::YES);
} else if (_wvInfo.tag == kWaveFormatDK3) {
_audStream->queueAudioStream(Audio::makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), Audio::kADPCMDK3, _wvInfo.samplesPerSec, _wvInfo.channels, _wvInfo.blockAlign), DisposeAfterUse::YES);
}
@@ -470,7 +474,7 @@ Audio::AudioStream *AVIDecoder::AVIAudioTrack::getAudioStream() const {
}
Audio::QueuingAudioStream *AVIDecoder::AVIAudioTrack::createAudioStream() {
- if (_wvInfo.tag == kWaveFormatPCM || _wvInfo.tag == kWaveFormatDK3)
+ if (_wvInfo.tag == kWaveFormatPCM || _wvInfo.tag == kWaveFormatMSADPCM || _wvInfo.tag == kWaveFormatMSIMAADPCM || _wvInfo.tag == kWaveFormatDK3)
return Audio::makeQueuingAudioStream(_wvInfo.samplesPerSec, _wvInfo.channels == 2);
else if (_wvInfo.tag != kWaveFormatNone) // No sound
warning("Unsupported AVI audio format %d", _wvInfo.tag);
diff --git a/video/avi_decoder.h b/video/avi_decoder.h
index 3bdc0561d1..34a67f4c28 100644
--- a/video/avi_decoder.h
+++ b/video/avi_decoder.h
@@ -203,7 +203,9 @@ private:
enum {
kWaveFormatNone = 0,
kWaveFormatPCM = 1,
- kWaveFormatDK3 = 98
+ kWaveFormatMSADPCM = 2,
+ kWaveFormatMSIMAADPCM = 17,
+ kWaveFormatDK3 = 98 // rogue format number
};
AVIStreamHeader _audsHeader;
diff --git a/video/bink_decoder.cpp b/video/bink_decoder.cpp
index 1ece22c963..45dec0887b 100644
--- a/video/bink_decoder.cpp
+++ b/video/bink_decoder.cpp
@@ -557,8 +557,8 @@ void BinkDecoder::BinkVideoTrack::initBundles() {
_bundles[i].dataEnd = _bundles[i].data + blocks * 64;
}
- uint32 cbw[2] = { (_surface.w + 7) >> 3, (_surface.w + 15) >> 4 };
- uint32 cw [2] = { _surface.w , _surface.w >> 1 };
+ uint32 cbw[2] = { (uint32)((_surface.w + 7) >> 3), (uint32)((_surface.w + 15) >> 4) };
+ uint32 cw [2] = { (uint32)( _surface.w ), (uint32)( _surface.w >> 1) };
// Calculate the lengths of an element count in bits
for (int i = 0; i < 2; i++) {
diff --git a/video/bink_decoder.h b/video/bink_decoder.h
index 27d3aa3691..08800c2223 100644
--- a/video/bink_decoder.h
+++ b/video/bink_decoder.h
@@ -36,6 +36,8 @@
#include "video/video_decoder.h"
+#include "graphics/surface.h"
+
namespace Audio {
class AudioStream;
class QueuingAudioStream;
diff --git a/video/codecs/cdtoons.h b/video/codecs/cdtoons.h
index 8f6d3acb6e..e6b7aab5f8 100644
--- a/video/codecs/cdtoons.h
+++ b/video/codecs/cdtoons.h
@@ -38,6 +38,12 @@ struct CDToonsBlock {
byte *data;
};
+/**
+ * Broderbund CDToons decoder.
+ *
+ * Used in video:
+ * - QuickTimeDecoder
+ */
class CDToonsDecoder : public Codec {
public:
CDToonsDecoder(uint16 width, uint16 height);
diff --git a/video/codecs/cinepak.cpp b/video/codecs/cinepak.cpp
index c197e0cc35..bcf0cf1180 100644
--- a/video/codecs/cinepak.cpp
+++ b/video/codecs/cinepak.cpp
@@ -34,16 +34,12 @@
namespace Video {
-// Convert a color from YUV to RGB colorspace, Cinepak style.
-inline static void CPYUV2RGB(byte y, byte u, byte v, byte &r, byte &g, byte &b) {
- r = CLIP<int>(y + 2 * (v - 128), 0, 255);
- g = CLIP<int>(y - (u - 128) / 2 - (v - 128), 0, 255);
- b = CLIP<int>(y + 2 * (u - 128), 0, 255);
-}
-
#define PUT_PIXEL(offset, lum, u, v) \
if (_pixelFormat.bytesPerPixel != 1) { \
- CPYUV2RGB(lum, u, v, r, g, b); \
+ byte r = _clipTable[lum + (v << 1)]; \
+ byte g = _clipTable[lum - (u >> 1) - v]; \
+ byte b = _clipTable[lum + (u << 1)]; \
+ \
if (_pixelFormat.bytesPerPixel == 2) \
*((uint16 *)_curFrame.surface->pixels + offset) = _pixelFormat.RGBToColor(r, g, b); \
else \
@@ -60,6 +56,21 @@ CinepakDecoder::CinepakDecoder(int bitsPerPixel) : Codec() {
_pixelFormat = Graphics::PixelFormat::createFormatCLUT8();
else
_pixelFormat = g_system->getScreenFormat();
+
+ // Create a lookup for the clip function
+ // This dramatically improves the performance of the color conversion
+ _clipTableBuf = new byte[1024];
+
+ for (uint i = 0; i < 1024; i++) {
+ if (i <= 512)
+ _clipTableBuf[i] = 0;
+ else if (i >= 768)
+ _clipTableBuf[i] = 255;
+ else
+ _clipTableBuf[i] = i - 512;
+ }
+
+ _clipTable = _clipTableBuf + 512;
}
CinepakDecoder::~CinepakDecoder() {
@@ -69,6 +80,7 @@ CinepakDecoder::~CinepakDecoder() {
}
delete[] _curFrame.strips;
+ delete[] _clipTableBuf;
}
const Graphics::Surface *CinepakDecoder::decodeImage(Common::SeekableReadStream *stream) {
@@ -82,15 +94,14 @@ const Graphics::Surface *CinepakDecoder::decodeImage(Common::SeekableReadStream
if (_curFrame.strips == NULL)
_curFrame.strips = new CinepakStrip[_curFrame.stripCount];
- debug (4, "Cinepak Frame: Width = %d, Height = %d, Strip Count = %d", _curFrame.width, _curFrame.height, _curFrame.stripCount);
+ debug(4, "Cinepak Frame: Width = %d, Height = %d, Strip Count = %d", _curFrame.width, _curFrame.height, _curFrame.stripCount);
// Borrowed from FFMPEG. This should cut out the extra data Cinepak for Sega has (which is useless).
// The theory behind this is that this is here to confuse standard Cinepak decoders. But, we won't let that happen! ;)
if (_curFrame.length != (uint32)stream->size()) {
- uint16 temp = stream->readUint16BE();
- if (temp == 0xFE00)
+ if (stream->readUint16BE() == 0xFE00)
stream->readUint32BE();
- else if (temp != _curFrame.width)
+ else if ((stream->size() % _curFrame.length) == 0)
stream->seek(-2, SEEK_CUR);
}
@@ -191,14 +202,14 @@ void CinepakDecoder::loadCodebook(Common::SeekableReadStream *stream, uint16 str
codebook[i].y[j] = stream->readByte();
if (n == 6) {
- codebook[i].u = stream->readByte() + 128;
- codebook[i].v = stream->readByte() + 128;
+ codebook[i].u = stream->readSByte();
+ codebook[i].v = stream->readSByte();
} else {
// This codebook type indicates either greyscale or
// palettized video. For greyscale, default us to
- // 128 for both u and v.
- codebook[i].u = 128;
- codebook[i].v = 128;
+ // 0 for both u and v.
+ codebook[i].u = 0;
+ codebook[i].v = 0;
}
}
}
@@ -208,7 +219,6 @@ void CinepakDecoder::decodeVectors(Common::SeekableReadStream *stream, uint16 st
uint32 flag = 0, mask = 0;
uint32 iy[4];
int32 startPos = stream->pos();
- byte r = 0, g = 0, b = 0;
for (uint16 y = _curFrame.strips[strip].rect.top; y < _curFrame.strips[strip].rect.bottom; y += 4) {
iy[0] = _curFrame.strips[strip].rect.left + y * _curFrame.width;
diff --git a/video/codecs/cinepak.h b/video/codecs/cinepak.h
index ca4552fae6..f4adfd50fe 100644
--- a/video/codecs/cinepak.h
+++ b/video/codecs/cinepak.h
@@ -36,8 +36,9 @@ class SeekableReadStream;
namespace Video {
struct CinepakCodebook {
- byte y[4];
- byte u, v;
+ // These are not in the normal YUV colorspace, but in the Cinepak YUV colorspace instead.
+ byte y[4]; // [0, 255]
+ int8 u, v; // [-128, 127]
};
struct CinepakStrip {
@@ -58,6 +59,13 @@ struct CinepakFrame {
Graphics::Surface *surface;
};
+/**
+ * Cinepak decoder.
+ *
+ * Used in video:
+ * - AVIDecoder
+ * - QuickTimeDecoder
+ */
class CinepakDecoder : public Codec {
public:
CinepakDecoder(int bitsPerPixel = 24);
@@ -70,6 +78,7 @@ private:
CinepakFrame _curFrame;
int32 _y;
Graphics::PixelFormat _pixelFormat;
+ byte *_clipTable, *_clipTableBuf;
void loadCodebook(Common::SeekableReadStream *stream, uint16 strip, byte codebookType, byte chunkID, uint32 chunkSize);
void decodeVectors(Common::SeekableReadStream *stream, uint16 strip, byte chunkID, uint32 chunkSize);
diff --git a/video/codecs/codec.h b/video/codecs/codec.h
index 8e4691ca3c..c1194e461b 100644
--- a/video/codecs/codec.h
+++ b/video/codecs/codec.h
@@ -32,16 +32,48 @@ class SeekableReadStream;
namespace Video {
+/**
+ * An abstract representation of a video codec used for decoding
+ * video frames.
+ *
+ * Used in video:
+ * - AVIDecoder
+ * - QuickTimeDecoder
+ * - VMDDecoder
+ */
class Codec {
public:
Codec() {}
virtual ~Codec() {}
+ /**
+ * Decode the frame for the given data and return a pointer to a surface
+ * containing the decoded frame.
+ *
+ * @return a pointer to the decoded frame
+ * @note stream is not deleted
+ */
virtual const Graphics::Surface *decodeImage(Common::SeekableReadStream *stream) = 0;
+
+ /**
+ * Get the format that the surface returned from decodeImage() will
+ * be in.
+ */
virtual Graphics::PixelFormat getPixelFormat() const = 0;
+ /**
+ * Can this codec's frames contain a palette?
+ */
virtual bool containsPalette() const { return false; }
+
+ /**
+ * Get the palette last decoded from decodeImage
+ */
virtual const byte *getPalette() { return 0; }
+
+ /**
+ * Does the codec have a dirty palette?
+ */
virtual bool hasDirtyPalette() const { return false; }
};
diff --git a/video/codecs/indeo3.h b/video/codecs/indeo3.h
index a07d779dec..880901df13 100644
--- a/video/codecs/indeo3.h
+++ b/video/codecs/indeo3.h
@@ -36,6 +36,13 @@
namespace Video {
+/**
+ * Intel Indeo 3 decoder.
+ *
+ * Used in video:
+ * - AVIDecoder
+ * - VMDDecoder
+ */
class Indeo3Decoder : public Codec {
public:
Indeo3Decoder(uint16 width, uint16 height);
diff --git a/video/codecs/mjpeg.h b/video/codecs/mjpeg.h
index 0c3b668a74..d71454799c 100644
--- a/video/codecs/mjpeg.h
+++ b/video/codecs/mjpeg.h
@@ -36,10 +36,12 @@ struct Surface;
namespace Video {
-// Motion JPEG Decoder
-// Basically a wrapper around JPEG which converts to RGB and also functions
-// as a Codec.
-
+/**
+ * Motion JPEG decoder.
+ *
+ * Used in video:
+ * - QuickTimeDecoder
+ */
class JPEGDecoder : public Codec {
public:
JPEGDecoder();
diff --git a/video/codecs/msrle.h b/video/codecs/msrle.h
index 2aea66d113..64ebaaee51 100644
--- a/video/codecs/msrle.h
+++ b/video/codecs/msrle.h
@@ -27,6 +27,12 @@
namespace Video {
+/**
+ * Microsoft Run-Length Encoding decoder.
+ *
+ * Used in video:
+ * - AVIDecoder
+ */
class MSRLEDecoder : public Codec {
public:
MSRLEDecoder(uint16 width, uint16 height, byte bitsPerPixel);
diff --git a/video/codecs/msvideo1.h b/video/codecs/msvideo1.h
index 767eece580..047d542743 100644
--- a/video/codecs/msvideo1.h
+++ b/video/codecs/msvideo1.h
@@ -27,6 +27,12 @@
namespace Video {
+/**
+ * Microsoft Video 1 decoder.
+ *
+ * Used in video:
+ * - AVIDecoder
+ */
class MSVideo1Decoder : public Codec {
public:
MSVideo1Decoder(uint16 width, uint16 height, byte bitsPerPixel);
diff --git a/video/codecs/qtrle.h b/video/codecs/qtrle.h
index d9db58ab23..a1dd9c9188 100644
--- a/video/codecs/qtrle.h
+++ b/video/codecs/qtrle.h
@@ -28,6 +28,12 @@
namespace Video {
+/**
+ * QuickTime Run-Length Encoding decoder.
+ *
+ * Used in video:
+ * - QuickTimeDecoder
+ */
class QTRLEDecoder : public Codec {
public:
QTRLEDecoder(uint16 width, uint16 height, byte bitsPerPixel);
diff --git a/video/codecs/rpza.cpp b/video/codecs/rpza.cpp
index acc1b7f358..0a9f87747e 100644
--- a/video/codecs/rpza.cpp
+++ b/video/codecs/rpza.cpp
@@ -163,7 +163,7 @@ const Graphics::Surface *RPZADecoder::decodeImage(Common::SeekableReadStream *st
blockPtr = rowPtr + pixelPtr;
for (byte pixel_y = 0; pixel_y < 4; pixel_y++) {
byte index = stream->readByte();
- for (byte pixel_x = 0; pixel_x < 4; pixel_x++){
+ for (byte pixel_x = 0; pixel_x < 4; pixel_x++) {
byte idx = (index >> (2 * (3 - pixel_x))) & 0x03;
PUT_PIXEL(color4[idx]);
}
@@ -177,7 +177,7 @@ const Graphics::Surface *RPZADecoder::decodeImage(Common::SeekableReadStream *st
case 0x00:
blockPtr = rowPtr + pixelPtr;
for (byte pixel_y = 0; pixel_y < 4; pixel_y++) {
- for (byte pixel_x = 0; pixel_x < 4; pixel_x++){
+ for (byte pixel_x = 0; pixel_x < 4; pixel_x++) {
// We already have color of upper left pixel
if (pixel_y != 0 || pixel_x != 0)
colorA = stream->readUint16BE();
diff --git a/video/codecs/rpza.h b/video/codecs/rpza.h
index f082d25549..67e0699692 100644
--- a/video/codecs/rpza.h
+++ b/video/codecs/rpza.h
@@ -28,6 +28,12 @@
namespace Video {
+/**
+ * Apple RPZA decoder.
+ *
+ * Used in video:
+ * - QuickTimeDecoder
+ */
class RPZADecoder : public Codec {
public:
RPZADecoder(uint16 width, uint16 height);
diff --git a/video/codecs/smc.h b/video/codecs/smc.h
index f2caca977a..4b9f57410a 100644
--- a/video/codecs/smc.h
+++ b/video/codecs/smc.h
@@ -34,6 +34,12 @@ enum {
COLORS_PER_TABLE = 256
};
+/**
+ * Apple SMC decoder.
+ *
+ * Used in video:
+ * - QuickTimeDecoder
+ */
class SMCDecoder : public Codec {
public:
SMCDecoder(uint16 width, uint16 height);
diff --git a/video/codecs/svq1.cpp b/video/codecs/svq1.cpp
index 56b376f590..57e84968a3 100644
--- a/video/codecs/svq1.cpp
+++ b/video/codecs/svq1.cpp
@@ -223,9 +223,9 @@ const Graphics::Surface *SVQ1Decoder::decodeImage(Common::SeekableReadStream *st
// Prediction Motion Vector
Common::Point *pmv = new Common::Point[(width / 8) + 3];
- byte *previous;
+ byte *previous = 0;
if (frameType == 2) { // B Frame
- warning("B Frame not supported currently");
+ error("SVQ1 Video: B Frames not supported");
//previous = _next[i];
} else {
previous = _last[i];
diff --git a/video/codecs/svq1.h b/video/codecs/svq1.h
index e5066abfd4..6667fea344 100644
--- a/video/codecs/svq1.h
+++ b/video/codecs/svq1.h
@@ -33,6 +33,12 @@ struct Point;
namespace Video {
+/**
+ * Sorenson Vector Quantizer 1 decoder.
+ *
+ * Used in video:
+ * - QuickTimeDecoder
+ */
class SVQ1Decoder : public Codec {
public:
SVQ1Decoder(uint16 width, uint16 height);
diff --git a/video/codecs/truemotion1.h b/video/codecs/truemotion1.h
index 628cfa4584..b2a35cf873 100644
--- a/video/codecs/truemotion1.h
+++ b/video/codecs/truemotion1.h
@@ -32,6 +32,12 @@
namespace Video {
+/**
+ * Duck TrueMotion 1 decoder.
+ *
+ * Used in video:
+ * - AVIDecoder
+ */
class TrueMotion1Decoder : public Codec {
public:
TrueMotion1Decoder(uint16 width, uint16 height);
diff --git a/video/coktel_decoder.cpp b/video/coktel_decoder.cpp
index 08340a19a6..d24a021f3b 100644
--- a/video/coktel_decoder.cpp
+++ b/video/coktel_decoder.cpp
@@ -2296,7 +2296,7 @@ bool VMDDecoder::renderFrame(Common::Rect &rect) {
// Directly uncompress onto the video surface
const int offsetX = rect.left * _surface.format.bytesPerPixel;
const int offsetY = (_y + rect.top) * _surface.pitch;
- const int offset = offsetX - offsetY;
+ const int offset = offsetX + offsetY;
if (deLZ77((byte *)_surface.pixels + offset, dataPtr, dataSize,
_surface.w * _surface.h * _surface.format.bytesPerPixel - offset))
diff --git a/video/flic_decoder.cpp b/video/flic_decoder.cpp
index 1a0627615b..de545366b1 100644
--- a/video/flic_decoder.cpp
+++ b/video/flic_decoder.cpp
@@ -152,6 +152,7 @@ Graphics::PixelFormat FlicDecoder::FlicVideoTrack::getPixelFormat() const {
#define FLI_SETPAL 4
#define FLI_SS2 7
#define FLI_BRUN 15
+#define FLI_COPY 16
#define PSTAMP 18
#define FRAME_TYPE 0xF1FA
@@ -212,6 +213,9 @@ const Graphics::Surface *FlicDecoder::FlicVideoTrack::decodeNextFrame() {
case FLI_BRUN:
decodeByteRun(data);
break;
+ case FLI_COPY:
+ copyFrame(data);
+ break;
case PSTAMP:
/* PSTAMP - skip for now */
break;
@@ -247,6 +251,14 @@ void FlicDecoder::FlicVideoTrack::copyDirtyRectsToBuffer(uint8 *dst, uint pitch)
clearDirtyRects();
}
+void FlicDecoder::FlicVideoTrack::copyFrame(uint8 *data) {
+ memcpy((byte *)_surface->pixels, 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->pixels;
while ((int32)(ptr - (byte *)_surface->pixels) < (getWidth() * getHeight())) {
diff --git a/video/flic_decoder.h b/video/flic_decoder.h
index 9037af05d6..c20a092a32 100644
--- a/video/flic_decoder.h
+++ b/video/flic_decoder.h
@@ -97,6 +97,7 @@ private:
Common::List<Common::Rect> _dirtyRects;
+ void copyFrame(uint8 *data);
void decodeByteRun(uint8 *data);
void decodeDeltaFLC(uint8 *data);
void unpackPalette(uint8 *mem);
diff --git a/video/psx_decoder.cpp b/video/psx_decoder.cpp
index 57c8972ee5..fd45005770 100644
--- a/video/psx_decoder.cpp
+++ b/video/psx_decoder.cpp
@@ -151,6 +151,8 @@ static const uint32 s_huffmanACSymbols[AC_CODE_COUNT] = {
PSXStreamDecoder::PSXStreamDecoder(CDSpeed speed, uint32 frameCount) : _speed(speed), _frameCount(frameCount) {
_stream = 0;
+ _videoTrack = 0;
+ _audioTrack = 0;
}
PSXStreamDecoder::~PSXStreamDecoder() {
diff --git a/video/qt_decoder.cpp b/video/qt_decoder.cpp
index b4dab9ddfb..7539d4a1e2 100644
--- a/video/qt_decoder.cpp
+++ b/video/qt_decoder.cpp
@@ -110,7 +110,7 @@ const Graphics::Surface *QuickTimeDecoder::decodeNextFrame() {
return frame;
}
-Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format) {
+Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format, uint32 descSize) {
if (track->codecType == CODEC_TYPE_VIDEO) {
debug(0, "Video Codec FourCC: \'%s\'", tag2str(format));
@@ -205,7 +205,7 @@ Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(Common::Qu
}
// Pass it on up
- return Audio::QuickTimeAudioDecoder::readSampleDesc(track, format);
+ return Audio::QuickTimeAudioDecoder::readSampleDesc(track, format, descSize);
}
void QuickTimeDecoder::init() {
@@ -333,6 +333,7 @@ QuickTimeDecoder::VideoTrackHandler::VideoTrackHandler(QuickTimeDecoder *decoder
_scaledSurface = 0;
_curPalette = 0;
_dirtyPalette = false;
+ _reversed = false;
}
QuickTimeDecoder::VideoTrackHandler::~VideoTrackHandler() {
@@ -344,29 +345,39 @@ QuickTimeDecoder::VideoTrackHandler::~VideoTrackHandler() {
bool QuickTimeDecoder::VideoTrackHandler::endOfTrack() const {
// A track is over when we've finished going through all edits
- return _curEdit == _parent->editCount;
+ return _reversed ? (_curEdit == 0 && _curFrame < 0) : atLastEdit();
}
bool QuickTimeDecoder::VideoTrackHandler::seek(const Audio::Timestamp &requestedTime) {
- // First, figure out what edit we're in
- Audio::Timestamp time = requestedTime.convertToFramerate(_parent->timeScale);
-
- // Continue until we get to where we need to be
- for (_curEdit = 0; !endOfTrack(); _curEdit++)
- if ((uint32)time.totalNumberOfFrames() >= getCurEditTimeOffset() && (uint32)time.totalNumberOfFrames() < getCurEditTimeOffset() + getCurEditTrackDuration())
+ 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;
- // This track is done
- if (endOfTrack())
+ // If we did reach the end of the track, break out
+ if (atLastEdit())
+ 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())
+ enterNewEditList(true);
+
return true;
+ }
enterNewEditList(false);
// One extra check for the end of a track
- if (endOfTrack())
+ if (atLastEdit())
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) {
@@ -381,17 +392,24 @@ bool QuickTimeDecoder::VideoTrackHandler::seek(const Audio::Timestamp &requested
// Compare the starting point for the frame to where we need to be
_holdNextFrameStartTime = getRateAdjustedFrameTime() != (uint32)time.totalNumberOfFrames();
- // If we went past the time, go back a frame
+ // If we went past the time, go back a frame. _curFrame before this point is at the frame
+ // that should be displayed. This adjustment ensures it is on the frame before the one that
+ // should be displayed.
if (_holdNextFrameStartTime)
_curFrame--;
- // Handle the keyframe here
- int32 destinationFrame = _curFrame + 1;
+ 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();
+ assert(destinationFrame < (int32)_parent->frameCount);
+ _curFrame = findKeyFrame(destinationFrame) - 1;
+ while (_curFrame < destinationFrame - 1)
+ bufferNextFrame();
+ }
return true;
}
@@ -428,27 +446,54 @@ 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();
+ }
+
const Graphics::Surface *frame = bufferNextFrame();
- if (_holdNextFrameStartTime) {
- // Don't set the next frame start time here; we just did a seek
- _holdNextFrameStartTime = false;
- } else if (_durationOverride >= 0) {
- // Use our own duration from the edit list calculation
- _nextFrameStartTime += _durationOverride;
- _durationOverride = -1;
+ if (_reversed) {
+ if (_holdNextFrameStartTime) {
+ // Don't set the next frame start time here; we just did a seek
+ _holdNextFrameStartTime = false;
+ } else {
+ // Just need to subtract the time
+ _nextFrameStartTime -= getFrameDuration();
+ }
} else {
- _nextFrameStartTime += getFrameDuration();
- }
+ if (_holdNextFrameStartTime) {
+ // Don't set the next frame start time here; we just did a seek
+ _holdNextFrameStartTime = false;
+ } else if (_durationOverride >= 0) {
+ // Use our own duration from the edit list calculation
+ _nextFrameStartTime += _durationOverride;
+ _durationOverride = -1;
+ } else {
+ _nextFrameStartTime += getFrameDuration();
+ }
- // Update the edit list, if applicable
- // HACK: We're also accepting the time minus one because edit lists
- // aren't as accurate as one would hope.
- if (!endOfTrack() && getRateAdjustedFrameTime() >= getCurEditTimeOffset() + getCurEditTrackDuration() - 1) {
- _curEdit++;
+ // Update the edit list, if applicable
+ // HACK: We're also accepting the time minus one because edit lists
+ // aren't as accurate as one would hope.
+ if (!atLastEdit() && getRateAdjustedFrameTime() >= getCurEditTimeOffset() + getCurEditTrackDuration() - 1) {
+ _curEdit++;
- if (!endOfTrack())
- enterNewEditList(true);
+ if (!atLastEdit())
+ enterNewEditList(true);
+ }
}
if (frame && (_parent->scaleFactorX != 1 || _parent->scaleFactorY != 1)) {
@@ -464,6 +509,68 @@ const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::decodeNextFrame()
return frame;
}
+bool QuickTimeDecoder::VideoTrackHandler::setReverse(bool reverse) {
+ _reversed = reverse;
+
+ if (_reversed) {
+ if (_parent->editCount != 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->editCount - 1;
+ _curFrame = _parent->frameCount;
+ _nextFrameStartTime = _parent->editList[_curEdit].trackDuration + _parent->editList[_curEdit].timeOffset;
+ } else if (_holdNextFrameStartTime) {
+ // We just seeked, so "pivot" around the frame that should be displayed
+ _curFrame++;
+ _nextFrameStartTime -= getFrameDuration();
+ _curFrame++;
+ } 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
+ // HACK: We're also accepting the time minus one because edit lists
+ // aren't as accurate as one would hope.
+ if (!atLastEdit() && getRateAdjustedFrameTime() >= getCurEditTimeOffset() + getCurEditTrackDuration() - 1) {
+ _curEdit++;
+
+ if (atLastEdit())
+ return true;
+ }
+
+ if (_holdNextFrameStartTime) {
+ // We just seeked, so "pivot" around the frame that should be displayed
+ _curFrame--;
+ _nextFrameStartTime += getFrameDuration();
+ }
+
+ // 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;
}
@@ -545,10 +652,10 @@ uint32 QuickTimeDecoder::VideoTrackHandler::findKeyFrame(uint32 frame) const {
void QuickTimeDecoder::VideoTrackHandler::enterNewEditList(bool bufferFrames) {
// Bypass all empty edit lists first
- while (!endOfTrack() && _parent->editList[_curEdit].mediaTime == -1)
+ while (!atLastEdit() && _parent->editList[_curEdit].mediaTime == -1)
_curEdit++;
- if (endOfTrack())
+ if (atLastEdit())
return;
uint32 frameNum = 0;
@@ -557,6 +664,8 @@ void QuickTimeDecoder::VideoTrackHandler::enterNewEditList(bool bufferFrames) {
uint32 prevDuration = 0;
// 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 && !done; i++) {
for (int32 j = 0; j < _parent->timeToSample[i].count; j++) {
if (totalDuration == (uint32)_parent->editList[_curEdit].mediaTime) {
@@ -577,10 +686,13 @@ void QuickTimeDecoder::VideoTrackHandler::enterNewEditList(bool bufferFrames) {
if (bufferFrames) {
// Track down the keyframe
+ // Then decode until the frame before target
_curFrame = findKeyFrame(frameNum) - 1;
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;
}
@@ -589,6 +701,8 @@ void QuickTimeDecoder::VideoTrackHandler::enterNewEditList(bool bufferFrames) {
// Set an override for the duration since we came up in-between two frames
if (prevDuration != totalDuration)
_durationOverride = totalDuration - prevDuration;
+ else
+ _durationOverride = -1;
}
const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::bufferNextFrame() {
@@ -598,14 +712,18 @@ const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::bufferNextFrame()
uint32 descId;
Common::SeekableReadStream *frameData = getNextFramePacket(descId);
- if (!frameData || !descId || descId > _parent->sampleDescs.size())
+ 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)
+ if (!entry->_videoCodec) {
+ delete frameData;
return 0;
+ }
const Graphics::Surface *frame = entry->_videoCodec->decodeImage(frameData);
delete frameData;
@@ -638,7 +756,19 @@ uint32 QuickTimeDecoder::VideoTrackHandler::getRateAdjustedFrameTime() const {
uint32 QuickTimeDecoder::VideoTrackHandler::getCurEditTimeOffset() const {
// Need to convert to the track scale
- return _parent->editList[_curEdit].timeOffset * _parent->timeScale / _decoder->_timeScale;
+
+ // 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 {
@@ -646,4 +776,8 @@ uint32 QuickTimeDecoder::VideoTrackHandler::getCurEditTrackDuration() const {
return _parent->editList[_curEdit].trackDuration * _parent->timeScale / _decoder->_timeScale;
}
+bool QuickTimeDecoder::VideoTrackHandler::atLastEdit() const {
+ return _curEdit == _parent->editCount;
+}
+
} // End of namespace Video
diff --git a/video/qt_decoder.h b/video/qt_decoder.h
index 45ab155c2c..28314f2e63 100644
--- a/video/qt_decoder.h
+++ b/video/qt_decoder.h
@@ -70,7 +70,7 @@ public:
Audio::Timestamp getDuration() const { return Audio::Timestamp(0, _duration, _timeScale); }
protected:
- Common::QuickTimeParser::SampleDesc *readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format);
+ Common::QuickTimeParser::SampleDesc *readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format, uint32 descSize);
private:
void init();
@@ -136,6 +136,8 @@ private:
const Graphics::Surface *decodeNextFrame();
const byte *getPalette() const { _dirtyPalette = false; return _curPalette; }
bool hasDirtyPalette() const { return _curPalette; }
+ bool setReverse(bool reverse);
+ bool isReversed() const { return _reversed; }
Common::Rational getScaledWidth() const;
Common::Rational getScaledHeight() const;
@@ -151,6 +153,7 @@ private:
int32 _durationOverride;
const byte *_curPalette;
mutable bool _dirtyPalette;
+ bool _reversed;
Common::SeekableReadStream *getNextFramePacket(uint32 &descId);
uint32 getFrameDuration();
@@ -160,6 +163,7 @@ private:
uint32 getRateAdjustedFrameTime() const;
uint32 getCurEditTimeOffset() const;
uint32 getCurEditTrackDuration() const;
+ bool atLastEdit() const;
};
};
diff --git a/video/smk_decoder.cpp b/video/smk_decoder.cpp
index c49791100d..4e18268e72 100644
--- a/video/smk_decoder.cpp
+++ b/video/smk_decoder.cpp
@@ -119,7 +119,7 @@ uint16 SmallHuffmanTree::decodeTree(uint32 prefix, int length) {
}
uint16 SmallHuffmanTree::getCode(Common::BitStream &bs) {
- byte peek = bs.peekBits(8);
+ byte peek = bs.peekBits(MIN<uint32>(bs.size() - bs.pos(), 8));
uint16 *p = &_tree[_prefixtree[peek]];
bs.skip(_prefixlength[peek]);
@@ -257,7 +257,7 @@ uint32 BigHuffmanTree::decodeTree(uint32 prefix, int length) {
}
uint32 BigHuffmanTree::getCode(Common::BitStream &bs) {
- byte peek = bs.peekBits(8);
+ byte peek = bs.peekBits(MIN<uint32>(bs.size() - bs.pos(), 8));
uint32 *p = &_tree[_prefixtree[peek]];
bs.skip(_prefixlength[peek]);
diff --git a/video/theora_decoder.h b/video/theora_decoder.h
index 7e36d829e7..ac808cdfe6 100644
--- a/video/theora_decoder.h
+++ b/video/theora_decoder.h
@@ -51,6 +51,7 @@ namespace Video {
* Decoder for Theora videos.
* Video decoder used in engines:
* - sword25
+ * - wintermute
*/
class TheoraDecoder : public VideoDecoder {
public:
diff --git a/video/video_decoder.cpp b/video/video_decoder.cpp
index 110afa7755..5df811008c 100644
--- a/video/video_decoder.cpp
+++ b/video/video_decoder.cpp
@@ -37,7 +37,7 @@ VideoDecoder::VideoDecoder() {
_startTime = 0;
_dirtyPalette = false;
_palette = 0;
- _isPlaying = false;
+ _playbackRate = 0;
_audioVolume = Audio::Mixer::kMaxChannelVolume;
_audioBalance = 0;
_pauseLevel = 0;
@@ -45,6 +45,7 @@ VideoDecoder::VideoDecoder() {
_lastTimeChange = 0;
_endTime = 0;
_endTimeSet = false;
+ _nextVideoTrack = 0;
// Find the best format for output
_defaultHighColorFormat = g_system->getScreenFormat();
@@ -71,6 +72,7 @@ void VideoDecoder::close() {
_lastTimeChange = 0;
_endTime = 0;
_endTimeSet = false;
+ _nextVideoTrack = 0;
}
bool VideoDecoder::loadFile(const Common::String &filename) {
@@ -167,21 +169,44 @@ const Graphics::Surface *VideoDecoder::decodeNextFrame() {
_needsUpdate = false;
readNextPacket();
- VideoTrack *track = findNextVideoTrack();
- if (!track)
+ // If we have no next video track at this point, there shouldn't be
+ // any frame available for us to display.
+ if (!_nextVideoTrack)
return 0;
- const Graphics::Surface *frame = track->decodeNextFrame();
+ const Graphics::Surface *frame = _nextVideoTrack->decodeNextFrame();
- if (track->hasDirtyPalette()) {
- _palette = track->getPalette();
+ if (_nextVideoTrack->hasDirtyPalette()) {
+ _palette = _nextVideoTrack->getPalette();
_dirtyPalette = true;
}
+ // Look for the next video track here for the next decode.
+ findNextVideoTrack();
+
return frame;
}
+bool VideoDecoder::setReverse(bool reverse) {
+ // Can only reverse video-only videos
+ if (reverse && hasAudio())
+ return false;
+
+ // Attempt to make sure all the tracks are in the requested direction
+ for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) {
+ if ((*it)->getTrackType() == Track::kTrackTypeVideo && ((VideoTrack *)*it)->isReversed() != reverse) {
+ if (!((VideoTrack *)*it)->setReverse(reverse))
+ return false;
+
+ _needsUpdate = true; // force an update
+ }
+ }
+
+ findNextVideoTrack();
+ return true;
+}
+
const byte *VideoDecoder::getPalette() {
_dirtyPalette = false;
return _palette;
@@ -212,7 +237,7 @@ uint32 VideoDecoder::getTime() const {
return _lastTimeChange.msecs();
if (isPaused())
- return _pauseStartTime - _startTime;
+ return MAX<int>((_playbackRate * (_pauseStartTime - _startTime)).toInt(), 0);
if (useAudioSync()) {
for (TrackList::const_iterator it = _tracks.begin(); it != _tracks.end(); it++) {
@@ -225,25 +250,29 @@ uint32 VideoDecoder::getTime() const {
}
}
- return g_system->getMillis() - _startTime;
+ return MAX<int>((_playbackRate * (g_system->getMillis() - _startTime)).toInt(), 0);
}
uint32 VideoDecoder::getTimeToNextFrame() const {
- if (endOfVideo() || _needsUpdate)
+ if (endOfVideo() || _needsUpdate || !_nextVideoTrack)
return 0;
- const VideoTrack *track = findNextVideoTrack();
+ uint32 currentTime = getTime();
+ uint32 nextFrameStartTime = _nextVideoTrack->getNextFrameStartTime();
- if (!track)
- return 0;
+ if (_nextVideoTrack->isReversed()) {
+ // For reversed videos, we need to handle the time difference the opposite way.
+ if (nextFrameStartTime >= currentTime)
+ return 0;
- uint32 elapsedTime = getTime();
- uint32 nextFrameStartTime = track->getNextFrameStartTime();
+ return currentTime - nextFrameStartTime;
+ }
- if (nextFrameStartTime <= elapsedTime)
+ // Otherwise, handle it normally.
+ if (nextFrameStartTime <= currentTime)
return 0;
- return nextFrameStartTime - elapsedTime;
+ return nextFrameStartTime - currentTime;
}
bool VideoDecoder::endOfVideo() const {
@@ -284,6 +313,7 @@ bool VideoDecoder::rewind() {
_lastTimeChange = 0;
_startTime = g_system->getMillis();
resetPauseStartTime();
+ findNextVideoTrack();
return true;
}
@@ -316,26 +346,47 @@ bool VideoDecoder::seek(const Audio::Timestamp &time) {
// Also reset our start time
if (isPlaying()) {
startAudio();
- _startTime = g_system->getMillis() - time.msecs();
+ _startTime = g_system->getMillis() - (time.msecs() / _playbackRate).toInt();
}
resetPauseStartTime();
+ findNextVideoTrack();
_needsUpdate = true;
return true;
}
-void VideoDecoder::start() {
- if (isPlaying() || !isVideoLoaded())
- return;
+bool VideoDecoder::seekToFrame(uint frame) {
+ VideoTrack *track = 0;
- _isPlaying = true;
- _startTime = g_system->getMillis();
+ for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) {
+ if (!(*it)->isSeekable())
+ return false;
- // Adjust start time if we've seeked to something besides zero time
- if (_lastTimeChange.totalNumberOfFrames() != 0)
- _startTime -= _lastTimeChange.msecs();
+ if ((*it)->getTrackType() == Track::kTrackTypeVideo) {
+ // We only allow seeking by frame when one video track
+ // is present
+ if (track)
+ return false;
- startAudio();
+ track = (VideoTrack *)*it;
+ }
+ }
+
+ // If we didn't find a video track, we can't seek by frame (of course)
+ if (!track)
+ return false;
+
+ Audio::Timestamp time = track->getFrameTime(frame);
+
+ if (time < 0)
+ return false;
+
+ return seek(time);
+}
+
+void VideoDecoder::start() {
+ if (!isPlaying())
+ setRate(1);
}
void VideoDecoder::stop() {
@@ -346,12 +397,12 @@ void VideoDecoder::stop() {
stopAudio();
// Keep the time marked down in case we start up again
- // We do this before _isPlaying is set so we don't get
+ // We do this before _playbackRate is set so we don't get
// _lastTimeChange returned, but before _pauseLevel is
// reset.
_lastTimeChange = getTime();
- _isPlaying = false;
+ _playbackRate = 0;
_startTime = 0;
_palette = 0;
_dirtyPalette = false;
@@ -365,6 +416,48 @@ void VideoDecoder::stop() {
(*it)->pause(false);
}
+void VideoDecoder::setRate(const Common::Rational &rate) {
+ if (!isVideoLoaded() || _playbackRate == rate)
+ return;
+
+ if (rate == 0) {
+ stop();
+ return;
+ } else if (rate != 1 && hasAudio()) {
+ warning("Cannot set custom rate in videos with audio");
+ return;
+ }
+
+ Common::Rational targetRate = rate;
+
+ // Attempt to set the reverse
+ if (!setReverse(rate < 0)) {
+ assert(rate < 0); // We shouldn't fail for forward.
+ warning("Cannot set custom rate to backwards");
+ setReverse(false);
+ targetRate = 1;
+
+ if (_playbackRate == targetRate)
+ return;
+ }
+
+ if (_playbackRate != 0)
+ _lastTimeChange = getTime();
+
+ _playbackRate = targetRate;
+ _startTime = g_system->getMillis();
+
+ // Adjust start time if we've seeked to something besides zero time
+ if (_lastTimeChange != 0)
+ _startTime -= (_lastTimeChange.msecs() / _playbackRate).toInt();
+
+ startAudio();
+}
+
+bool VideoDecoder::isPlaying() const {
+ return _playbackRate != 0;
+}
+
Audio::Timestamp VideoDecoder::getDuration() const {
Audio::Timestamp maxDuration(0, 1000);
@@ -403,21 +496,45 @@ bool VideoDecoder::VideoTrack::endOfTrack() const {
return getCurFrame() >= (getFrameCount() - 1);
}
+Audio::Timestamp VideoDecoder::VideoTrack::getFrameTime(uint frame) const {
+ // Default implementation: Return an invalid (negative) number
+ return Audio::Timestamp().addFrames(-1);
+}
+
uint32 VideoDecoder::FixedRateVideoTrack::getNextFrameStartTime() const {
if (endOfTrack() || getCurFrame() < 0)
return 0;
- Common::Rational time = (getCurFrame() + 1) * 1000;
- time /= getFrameRate();
- return time.toInt();
+ return getFrameTime(getCurFrame() + 1).msecs();
+}
+
+Audio::Timestamp VideoDecoder::FixedRateVideoTrack::getFrameTime(uint frame) const {
+ // Try to get as accurate as possible, considering we have a fractional frame rate
+ // (which Audio::Timestamp doesn't support).
+ Common::Rational frameRate = getFrameRate();
+
+ if (frameRate == frameRate.toInt()) // The nice case (a whole number)
+ return Audio::Timestamp(0, frame, frameRate.toInt());
+
+ // Just convert to milliseconds.
+ Common::Rational time = frame * 1000;
+ time /= frameRate;
+ return Audio::Timestamp(time.toInt(), 1000);
+}
+
+uint VideoDecoder::FixedRateVideoTrack::getFrameAtTime(const Audio::Timestamp &time) const {
+ Common::Rational frameRate = getFrameRate();
+
+ // Easy conversion
+ if (frameRate == time.framerate())
+ return time.totalNumberOfFrames();
+
+ // Default case
+ return (time.totalNumberOfFrames() * frameRate / time.framerate()).toInt();
}
Audio::Timestamp VideoDecoder::FixedRateVideoTrack::getDuration() const {
- // Since Audio::Timestamp doesn't support a fractional frame rate, we're currently
- // just converting to milliseconds.
- Common::Rational time = getFrameCount() * 1000;
- time /= getFrameRate();
- return time.toInt();
+ return getFrameTime(getFrameCount());
}
bool VideoDecoder::AudioTrack::endOfTrack() const {
@@ -527,10 +644,14 @@ bool VideoDecoder::StreamFileAudioTrack::loadFromFile(const Common::String &base
void VideoDecoder::addTrack(Track *track) {
_tracks.push_back(track);
- // Update volume settings if it's an audio track
if (track->getTrackType() == Track::kTrackTypeAudio) {
+ // Update volume settings if it's an audio track
((AudioTrack *)track)->setVolume(_audioVolume);
((AudioTrack *)track)->setBalance(_audioBalance);
+ } else if (track->getTrackType() == Track::kTrackTypeVideo) {
+ // If this track has a better time, update _nextVideoTrack
+ if (!_nextVideoTrack || ((VideoTrack *)track)->getNextFrameStartTime() < _nextVideoTrack->getNextFrameStartTime())
+ _nextVideoTrack = (VideoTrack *)track;
}
// Keep the track paused if we're paused
@@ -553,6 +674,8 @@ bool VideoDecoder::addStreamFileTrack(const Common::String &baseName) {
if (result)
addTrack(track);
+ else
+ delete track;
return result;
}
@@ -603,7 +726,7 @@ bool VideoDecoder::endOfVideoTracks() const {
}
VideoDecoder::VideoTrack *VideoDecoder::findNextVideoTrack() {
- VideoTrack *bestTrack = 0;
+ _nextVideoTrack = 0;
uint32 bestTime = 0xFFFFFFFF;
for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) {
@@ -613,31 +736,12 @@ VideoDecoder::VideoTrack *VideoDecoder::findNextVideoTrack() {
if (time < bestTime) {
bestTime = time;
- bestTrack = track;
- }
- }
- }
-
- return bestTrack;
-}
-
-const VideoDecoder::VideoTrack *VideoDecoder::findNextVideoTrack() const {
- const VideoTrack *bestTrack = 0;
- uint32 bestTime = 0xFFFFFFFF;
-
- for (TrackList::const_iterator it = _tracks.begin(); it != _tracks.end(); it++) {
- if ((*it)->getTrackType() == Track::kTrackTypeVideo && !(*it)->endOfTrack()) {
- const VideoTrack *track = (const VideoTrack *)*it;
- uint32 time = track->getNextFrameStartTime();
-
- if (time < bestTime) {
- bestTime = time;
- bestTrack = track;
+ _nextVideoTrack = track;
}
}
}
- return bestTrack;
+ return _nextVideoTrack;
}
void VideoDecoder::startAudio() {
@@ -676,4 +780,12 @@ bool VideoDecoder::hasFramesLeft() const {
return false;
}
+bool VideoDecoder::hasAudio() const {
+ for (TrackList::const_iterator it = _tracks.begin(); it != _tracks.end(); it++)
+ if ((*it)->getTrackType() == Track::kTrackTypeAudio)
+ return true;
+
+ return false;
+}
+
} // End of namespace Video
diff --git a/video/video_decoder.h b/video/video_decoder.h
index cc7d1df51b..d0a6e08005 100644
--- a/video/video_decoder.h
+++ b/video/video_decoder.h
@@ -26,6 +26,7 @@
#include "audio/mixer.h"
#include "audio/timestamp.h" // TODO: Move this to common/ ?
#include "common/array.h"
+#include "common/rational.h"
#include "common/str.h"
#include "graphics/pixelformat.h"
@@ -36,7 +37,6 @@ class SeekableAudioStream;
}
namespace Common {
-class Rational;
class SeekableReadStream;
}
@@ -100,7 +100,7 @@ public:
/////////////////////////////////////////
/**
- * Begin playback of the video.
+ * Begin playback of the video at normal speed.
*
* @note This has no effect if the video is already playing.
*/
@@ -114,6 +114,26 @@ public:
void stop();
/**
+ * Set the rate of playback.
+ *
+ * For instance, a rate of 0 would stop the video, while a rate of 1
+ * would play the video normally. Passing 2 to this function would
+ * play the video at twice the normal speed.
+ *
+ * @note This function does not work for non-0/1 rates on videos that
+ * have audio tracks.
+ *
+ * @todo This currently does not implement backwards playback, but will
+ * be implemented soon.
+ */
+ void setRate(const Common::Rational &rate);
+
+ /**
+ * Returns the rate at which the video is being played.
+ */
+ Common::Rational getRate() const { return _playbackRate; }
+
+ /**
* Returns if the video is currently playing or not.
*
* This is not equivalent to the inverse of endOfVideo(). A video keeps
@@ -121,7 +141,7 @@ public:
* return true after calling start() and will continue to return true
* until stop() (or close()) is called.
*/
- bool isPlaying() const { return _isPlaying; }
+ bool isPlaying() const;
/**
* Returns if a video is rewindable or not. The default implementation
@@ -158,6 +178,14 @@ public:
virtual bool seek(const Audio::Timestamp &time);
/**
+ * Seek to a given frame.
+ *
+ * This only works when one video track is present, and that track
+ * supports getFrameTime(). This calls seek() internally.
+ */
+ bool seekToFrame(uint frame);
+
+ /**
* Pause or resume the video. This should stop/resume any audio playback
* and other stuff. The initial pause time is kept so that any timing
* variables can be updated appropriately.
@@ -325,6 +353,17 @@ public:
*/
void setDefaultHighColorFormat(const Graphics::PixelFormat &format) { _defaultHighColorFormat = format; }
+ /**
+ * Set the video to decode frames in reverse.
+ *
+ * By default, VideoDecoder will decode forward.
+ *
+ * @note This is used by setRate()
+ * @note This will not work if an audio track is present
+ * @param reverse true for reverse, false for forward
+ * @return true on success, false otherwise
+ */
+ bool setReverse(bool reverse);
/////////////////////////////////////////
// Audio Control
@@ -367,14 +406,10 @@ public:
*/
bool addStreamFileTrack(const Common::String &baseName);
-
- // Future API
- //void setRate(const Common::Rational &rate);
- //Common::Rational getRate() const;
-
protected:
/**
- * An abstract representation of a track in a movie.
+ * An abstract representation of a track in a movie. Since tracks here are designed
+ * to work independently, they should not reference any other track(s) in the video.
*/
class Track {
public:
@@ -519,6 +554,29 @@ protected:
* Does the palette currently in use by this track need to be updated?
*/
virtual bool hasDirtyPalette() const { return false; }
+
+ /**
+ * Get the time the given frame should be shown.
+ *
+ * By default, this returns a negative (invalid) value. This function
+ * should only be used by VideoDecoder::seekToFrame().
+ */
+ virtual Audio::Timestamp getFrameTime(uint frame) const;
+
+ /**
+ * 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) { return !reverse; }
+
+ /**
+ * Is the video track set to play in reverse?
+ */
+ virtual bool isReversed() const { return false; }
};
/**
@@ -533,12 +591,19 @@ protected:
uint32 getNextFrameStartTime() const;
virtual Audio::Timestamp getDuration() const;
+ Audio::Timestamp getFrameTime(uint frame) const;
protected:
/**
* Get the rate at which this track is played.
*/
virtual Common::Rational getFrameRate() const = 0;
+
+ /**
+ * Get the frame that should be displaying at the given time. This is
+ * helpful for someone implementing seek().
+ */
+ uint getFrameAtTime(const Audio::Timestamp &time) const;
};
/**
@@ -685,9 +750,9 @@ protected:
/**
* Decode enough data for the next frame and enough audio to last that long.
*
- * This function is used by the decodeNextFrame() function. A subclass
+ * This function is used by this class' decodeNextFrame() function. A subclass
* of a Track may decide to just have its decodeNextFrame() function read
- * and decode the frame.
+ * and decode the frame, but only if it is the only track in the video.
*/
virtual void readNextPacket() {}
@@ -733,16 +798,13 @@ protected:
Graphics::PixelFormat getDefaultHighColorFormat() const { return _defaultHighColorFormat; }
/**
- * Find the video track with the lowest start time for the next frame
+ * Set _nextVideoTrack to the video track with the lowest start time for the next frame.
+ *
+ * @return _nextVideoTrack
*/
VideoTrack *findNextVideoTrack();
/**
- * Find the video track with the lowest start time for the next frame
- */
- const VideoTrack *findNextVideoTrack() const;
-
- /**
* Typedef helpers for accessing tracks
*/
typedef Common::Array<Track *> TrackList;
@@ -763,9 +825,11 @@ private:
TrackList _tracks;
// Current playback status
- bool _isPlaying, _needsUpdate;
+ bool _needsUpdate;
Audio::Timestamp _lastTimeChange, _endTime;
bool _endTimeSet;
+ Common::Rational _playbackRate;
+ VideoTrack *_nextVideoTrack;
// Palette settings from individual tracks
mutable bool _dirtyPalette;
@@ -779,6 +843,7 @@ private:
void startAudio();
void startAudioLimit(const Audio::Timestamp &limit);
bool hasFramesLeft() const;
+ bool hasAudio() const;
int32 _startTime;
uint32 _pauseLevel;