diff options
Diffstat (limited to 'video')
-rw-r--r-- | video/avi_decoder.cpp | 6 | ||||
-rw-r--r-- | video/avi_decoder.h | 4 | ||||
-rw-r--r-- | video/bink_decoder.cpp | 4 | ||||
-rw-r--r-- | video/bink_decoder.h | 2 | ||||
-rw-r--r-- | video/codecs/cdtoons.h | 6 | ||||
-rw-r--r-- | video/codecs/cinepak.cpp | 46 | ||||
-rw-r--r-- | video/codecs/cinepak.h | 13 | ||||
-rw-r--r-- | video/codecs/codec.h | 32 | ||||
-rw-r--r-- | video/codecs/indeo3.h | 7 | ||||
-rw-r--r-- | video/codecs/mjpeg.h | 10 | ||||
-rw-r--r-- | video/codecs/msrle.h | 6 | ||||
-rw-r--r-- | video/codecs/msvideo1.h | 6 | ||||
-rw-r--r-- | video/codecs/qtrle.h | 6 | ||||
-rw-r--r-- | video/codecs/rpza.cpp | 4 | ||||
-rw-r--r-- | video/codecs/rpza.h | 6 | ||||
-rw-r--r-- | video/codecs/smc.h | 6 | ||||
-rw-r--r-- | video/codecs/svq1.cpp | 4 | ||||
-rw-r--r-- | video/codecs/svq1.h | 6 | ||||
-rw-r--r-- | video/codecs/truemotion1.h | 6 | ||||
-rw-r--r-- | video/coktel_decoder.cpp | 2 | ||||
-rw-r--r-- | video/flic_decoder.cpp | 12 | ||||
-rw-r--r-- | video/flic_decoder.h | 1 | ||||
-rw-r--r-- | video/psx_decoder.cpp | 2 | ||||
-rw-r--r-- | video/qt_decoder.cpp | 214 | ||||
-rw-r--r-- | video/qt_decoder.h | 6 | ||||
-rw-r--r-- | video/smk_decoder.cpp | 4 | ||||
-rw-r--r-- | video/theora_decoder.h | 1 | ||||
-rw-r--r-- | video/video_decoder.cpp | 230 | ||||
-rw-r--r-- | video/video_decoder.h | 101 |
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; |