From ce1268b73d3e8f17e6bb7e048849cb69372809cd Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sat, 15 Dec 2012 20:43:43 -0500 Subject: VIDEO: Allow for QuickTime movies to be played backwards Still doesn't handle videos with multiple edits --- video/qt_decoder.cpp | 163 +++++++++++++++++++++++++++++++++++++++++---------- video/qt_decoder.h | 4 ++ 2 files changed, 136 insertions(+), 31 deletions(-) diff --git a/video/qt_decoder.cpp b/video/qt_decoder.cpp index 93e57f96ea..9e26e96c7b 100644 --- a/video/qt_decoder.cpp +++ b/video/qt_decoder.cpp @@ -333,6 +333,7 @@ QuickTimeDecoder::VideoTrackHandler::VideoTrackHandler(QuickTimeDecoder *decoder _scaledSurface = 0; _curPalette = 0; _dirtyPalette = false; + _reversed = false; } QuickTimeDecoder::VideoTrackHandler::~VideoTrackHandler() { @@ -344,26 +345,26 @@ 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) { uint32 convertedFrames = requestedTime.convertToFramerate(_decoder->_timeScale).totalNumberOfFrames(); - for (_curEdit = 0; !endOfTrack(); _curEdit++) + for (_curEdit = 0; !atLastEdit(); _curEdit++) if (convertedFrames >= _parent->editList[_curEdit].timeOffset && convertedFrames < _parent->editList[_curEdit].timeOffset + _parent->editList[_curEdit].trackDuration) break; // If we did reach the end of the track, break out - if (endOfTrack()) + 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 (!endOfTrack() && _parent->editList[_curEdit].mediaTime == -1) + while (!atLastEdit() && _parent->editList[_curEdit].mediaTime == -1) _curEdit++; - if (!endOfTrack()) + if (!atLastEdit()) enterNewEditList(true); return true; @@ -372,7 +373,7 @@ bool QuickTimeDecoder::VideoTrackHandler::seek(const Audio::Timestamp &requested 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 @@ -391,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; } @@ -438,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)) { @@ -474,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; } @@ -555,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; @@ -675,4 +772,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 135da91f88..28314f2e63 100644 --- a/video/qt_decoder.h +++ b/video/qt_decoder.h @@ -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; }; }; -- cgit v1.2.3