From 818c16bdd09f4aa92c4a46de5256f28331c35cbb Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Fri, 20 Jul 2012 20:51:42 -0400 Subject: VIDEO: Add first draft of the new VideoDecoder API It is currently named "AdvancedVideoDecoder" until all current VideoDecoders are converted to the new API. --- video/video_decoder.cpp | 405 ++++++++++++++++++++++++++++++++++++++++++++++-- video/video_decoder.h | 358 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 744 insertions(+), 19 deletions(-) diff --git a/video/video_decoder.cpp b/video/video_decoder.cpp index 44d7917652..ef2aeae94f 100644 --- a/video/video_decoder.cpp +++ b/video/video_decoder.cpp @@ -22,6 +22,7 @@ #include "video/video_decoder.h" +#include "audio/audiostream.h" #include "audio/mixer.h" // for kMaxChannelVolume #include "common/rational.h" @@ -51,26 +52,10 @@ uint32 VideoDecoder::getTime() const { return g_system->getMillis() - _startTime; } -void VideoDecoder::setSystemPalette() { - g_system->getPaletteManager()->setPalette(getPalette(), 0, 256); -} - bool VideoDecoder::needsUpdate() const { return !endOfVideo() && getTimeToNextFrame() == 0; } -void VideoDecoder::reset() { - _curFrame = -1; - _startTime = 0; - _pauseLevel = 0; - _audioVolume = Audio::Mixer::kMaxChannelVolume; - _audioBalance = 0; -} - -bool VideoDecoder::endOfVideo() const { - return !isVideoLoaded() || (getCurFrame() >= (int32)getFrameCount() - 1); -} - void VideoDecoder::pauseVideo(bool pause) { if (pause) { _pauseLevel++; @@ -108,6 +93,394 @@ void VideoDecoder::setBalance(int8 balance) { updateBalance(); } +AdvancedVideoDecoder::AdvancedVideoDecoder() { + _needsRewind = false; + _dirtyPalette = false; + _palette = 0; + _isPlaying = false; +} + +void AdvancedVideoDecoder::close() { + if (_isPlaying) + stop(); + + for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) + delete *it; + + _tracks.clear(); + _needsRewind = false; + _dirtyPalette = false; + _palette = 0; + _startTime = 0; + reset(); +} + +bool AdvancedVideoDecoder::isVideoLoaded() const { + return !_tracks.empty(); +} + +const Graphics::Surface *AdvancedVideoDecoder::decodeNextFrame() { + readNextPacket(); + VideoTrack *track = findNextVideoTrack(); + + if (!track) + return 0; + + const Graphics::Surface *frame = track->decodeNextFrame(); + + if (track->hasDirtyPalette()) { + _palette = track->getPalette(); + _dirtyPalette = true; + } + + return frame; +} + +const byte *AdvancedVideoDecoder::getPalette() { + _dirtyPalette = false; + return _palette; +} + +int AdvancedVideoDecoder::getCurFrame() const { + int32 frame = -1; + + for (TrackList::const_iterator it = _tracks.begin(); it != _tracks.end(); it++) + if ((*it)->getTrackType() == Track::kTrackTypeVideo) + frame += ((VideoTrack *)*it)->getCurFrame() + 1; + + return frame; +} + +uint32 AdvancedVideoDecoder::getFrameCount() const { + int count = 0; + + for (TrackList::const_iterator it = _tracks.begin(); it != _tracks.end(); it++) + if ((*it)->getTrackType() == Track::kTrackTypeVideo) + count += ((VideoTrack *)*it)->getFrameCount(); + + return count; +} + +uint32 AdvancedVideoDecoder::getTime() const { + if (isPaused()) + return _pauseStartTime - _startTime; + + for (TrackList::const_iterator it = _tracks.begin(); it != _tracks.end(); it++) { + if ((*it)->getTrackType() == Track::kTrackTypeAudio) { + uint32 time = ((const AudioTrack *)*it)->getRunningTime(); + + if (time != 0) + return time + _audioStartOffset.msecs(); + } + } + + return g_system->getMillis() - _startTime; +} + +uint32 AdvancedVideoDecoder::getTimeToNextFrame() const { + if (endOfVideo()) + return 0; + + const VideoTrack *track = findNextVideoTrack(); + + if (!track) + return 0; + + uint32 elapsedTime = getTime(); + uint32 nextFrameStartTime = track->getNextFrameStartTime(); + + if (nextFrameStartTime <= elapsedTime) + return 0; + + return nextFrameStartTime - elapsedTime; +} + +bool AdvancedVideoDecoder::endOfVideo() const { + // TODO: Bring _isPlaying into account? + + if (!isVideoLoaded()) + return true; + + for (TrackList::const_iterator it = _tracks.begin(); it != _tracks.end(); it++) + if (!(*it)->endOfTrack()) + return false; + + return true; +} + +bool AdvancedVideoDecoder::isRewindable() const { + if (_tracks.empty()) + return false; + + for (TrackList::const_iterator it = _tracks.begin(); it != _tracks.end(); it++) + if (!(*it)->isRewindable()) + return false; + + return true; +} + +bool AdvancedVideoDecoder::rewind() { + if (!isRewindable()) + return false; + + _needsRewind = false; + + // TODO: Pause status + + for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) + if (!(*it)->rewind()) + return false; + + _audioStartOffset = 0; + return true; +} + +bool AdvancedVideoDecoder::isSeekable() const { + if (_tracks.empty()) + return false; + + for (TrackList::const_iterator it = _tracks.begin(); it != _tracks.end(); it++) + if (!(*it)->isSeekable()) + return false; + + return true; +} + +bool AdvancedVideoDecoder::seek(const Audio::Timestamp &time) { + if (!isSeekable()) + return false; + + _needsRewind = false; + + // TODO: Pause status + + for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) + if (!(*it)->seek(time)) + return false; + + _audioStartOffset = time; + return true; +} + +void AdvancedVideoDecoder::start() { + if (_isPlaying || !isVideoLoaded()) + return; + + _isPlaying = true; + _startTime = g_system->getMillis(); + _audioStartOffset = 0; + + // If someone previously called stop(), we'll rewind it. + if (_needsRewind) + rewind(); + + for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) + (*it)->start(); +} + +void AdvancedVideoDecoder::stop() { + if (!_isPlaying) + return; + + _isPlaying = false; + _startTime = 0; + _audioStartOffset = 0; + _palette = 0; + _dirtyPalette = false; + + for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) + (*it)->stop(); + + // Also reset the pause state. + _pauseLevel = 0; + + // If this is a rewindable video, don't close it too. We'll just rewind() the video + // the next time someone calls start(). Otherwise, since it can't be rewound, we + // just close it. + if (isRewindable()) + _needsRewind = true; + else + close(); +} + +Audio::Timestamp AdvancedVideoDecoder::getDuration() const { + return Audio::Timestamp(0, 1000); +} + +void AdvancedVideoDecoder::pauseVideoIntern(bool pause) { + for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) + (*it)->pause(pause); +} + +void AdvancedVideoDecoder::updateVolume() { + // For API compatibility only + for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) + if ((*it)->getTrackType() == Track::kTrackTypeAudio) + ((AudioTrack *)*it)->setVolume(_audioVolume); +} + +void AdvancedVideoDecoder::updateBalance() { + // For API compatibility only + for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) + if ((*it)->getTrackType() == Track::kTrackTypeAudio) + ((AudioTrack *)*it)->setBalance(_audioBalance); +} + +AdvancedVideoDecoder::Track::Track() { + _paused = false; +} + +bool AdvancedVideoDecoder::Track::isRewindable() const { + return isSeekable(); +} + +bool AdvancedVideoDecoder::Track::rewind() { + return seek(Audio::Timestamp(0, 1000)); +} + +uint32 AdvancedVideoDecoder::FixedRateVideoTrack::getNextFrameStartTime() const { + if (endOfTrack() || getCurFrame() < 0) + return 0; + + Common::Rational time = (getCurFrame() + 1) * 1000; + time /= getFrameRate(); + return time.toInt(); +} + +bool AdvancedVideoDecoder::FixedLengthVideoTrack::endOfTrack() const { + return getCurFrame() >= (getFrameCount() - 1); +} + +bool AdvancedVideoDecoder::AudioTrack::endOfTrack() const { + Audio::AudioStream *stream = getAudioStream(); + return !stream || (!g_system->getMixer()->isSoundHandleActive(_handle) && stream->endOfData()); +} + +void AdvancedVideoDecoder::AudioTrack::setVolume(byte volume) { + _volume = volume; + + if (g_system->getMixer()->isSoundHandleActive(_handle)) + g_system->getMixer()->setChannelVolume(_handle, _volume); +} + +void AdvancedVideoDecoder::AudioTrack::setBalance(int8 balance) { + _balance = balance; + + if (g_system->getMixer()->isSoundHandleActive(_handle)) + g_system->getMixer()->setChannelBalance(_handle, _balance); +} + +void AdvancedVideoDecoder::AudioTrack::start() { + stop(); + + Audio::AudioStream *stream = getAudioStream(); + assert(stream); + + g_system->getMixer()->playStream(getSoundType(), &_handle, stream, -1, getVolume(), getBalance(), DisposeAfterUse::NO); + + // Pause the audio again if we're still paused + if (isPaused()) + g_system->getMixer()->pauseHandle(_handle, true); +} + +void AdvancedVideoDecoder::AudioTrack::stop() { + g_system->getMixer()->stopHandle(_handle); +} + +uint32 AdvancedVideoDecoder::AudioTrack::getRunningTime() const { + if (g_system->getMixer()->isSoundHandleActive(_handle)) + return g_system->getMixer()->getSoundElapsedTime(_handle); + + return 0; +} + +void AdvancedVideoDecoder::AudioTrack::pauseIntern(bool shouldPause) { + if (g_system->getMixer()->isSoundHandleActive(_handle)) + g_system->getMixer()->pauseHandle(_handle, shouldPause); +} + +Audio::AudioStream *AdvancedVideoDecoder::RewindableAudioTrack::getAudioStream() const { + return getRewindableAudioStream(); +} + +bool AdvancedVideoDecoder::RewindableAudioTrack::rewind() { + Audio::RewindableAudioStream *stream = getRewindableAudioStream(); + assert(stream); + return stream->rewind(); +} + +Audio::AudioStream *AdvancedVideoDecoder::SeekableAudioTrack::getAudioStream() const { + return getSeekableAudioStream(); +} + +bool AdvancedVideoDecoder::SeekableAudioTrack::seek(const Audio::Timestamp &time) { + Audio::SeekableAudioStream *stream = getSeekableAudioStream(); + assert(stream); + return stream->seek(time); +} + +void AdvancedVideoDecoder::addTrack(Track *track) { + _tracks.push_back(track); +} + +AdvancedVideoDecoder::VideoTrack *AdvancedVideoDecoder::findNextVideoTrack() { + VideoTrack *bestTrack = 0; + uint32 bestTime = 0xFFFFFFFF; + + for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) { + if ((*it)->getTrackType() == Track::kTrackTypeVideo) { + VideoTrack *track = (VideoTrack *)*it; + uint32 time = track->getNextFrameStartTime(); + + if (time < bestTime) { + bestTime = time; + bestTrack = track; + } + } + } + + return bestTrack; +} + +const AdvancedVideoDecoder::VideoTrack *AdvancedVideoDecoder::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) { + const VideoTrack *track = (const VideoTrack *)*it; + uint32 time = track->getNextFrameStartTime(); + + if (time < bestTime) { + bestTime = time; + bestTrack = track; + } + } + } + + return bestTrack; +} + +////////////////////////////////////////////// +///////////////// DEPRECATED ///////////////// +////////////////////////////////////////////// + +void VideoDecoder::reset() { + _curFrame = -1; + _startTime = 0; + _pauseLevel = 0; + _audioVolume = Audio::Mixer::kMaxChannelVolume; + _audioBalance = 0; +} + +bool VideoDecoder::endOfVideo() const { + return !isVideoLoaded() || (getCurFrame() >= (int32)getFrameCount() - 1); +} + +void VideoDecoder::setSystemPalette() { + g_system->getPaletteManager()->setPalette(getPalette(), 0, 256); +} + uint32 FixedRateVideoDecoder::getTimeToNextFrame() const { if (endOfVideo() || _curFrame < 0) return 0; diff --git a/video/video_decoder.h b/video/video_decoder.h index 3bb75ade09..1c359591b3 100644 --- a/video/video_decoder.h +++ b/video/video_decoder.h @@ -23,10 +23,16 @@ #ifndef VIDEO_DECODER_H #define VIDEO_DECODER_H -#include "common/str.h" - +#include "audio/mixer.h" #include "audio/timestamp.h" // TODO: Move this to common/ ? +#include "common/list.h" +#include "common/str.h" +namespace Audio { +class AudioStream; +class RewindableAudioStream; +class SeekableAudioStream; +} namespace Common { class Rational; @@ -42,6 +48,7 @@ namespace Video { /** * Generic interface for video decoder classes. + * @note This class is now deprecated in favor of AdvancedVideoDecoder. */ class VideoDecoder { public: @@ -109,6 +116,7 @@ public: /** * Set the system palette to the palette returned by getPalette. * @see getPalette + * @note This function is now deprecated. There is no replacement. */ void setSystemPalette(); @@ -222,47 +230,389 @@ public: protected: /** * Resets _curFrame and _startTime. Should be called from every close() function. + * @note This function is now deprecated. There is no replacement. */ void reset(); /** * Actual implementation of pause by subclasses. See pause() * for details. + * @note This function is now deprecated. There is no replacement. */ virtual void pauseVideoIntern(bool pause) {} /** * Add the time the video has been paused to maintain sync + * @note This function is now deprecated. There is no replacement. */ virtual void addPauseTime(uint32 ms) { _startTime += ms; } /** * Reset the pause start time (which should be called when seeking) + * @note This function is now deprecated. There is no replacement. */ void resetPauseStartTime(); /** * Update currently playing audio tracks with the new volume setting + * @note This function is now deprecated. There is no replacement. */ virtual void updateVolume() {} /** * Update currently playing audio tracks with the new balance setting + * @note This function is now deprecated. There is no replacement. */ virtual void updateBalance() {} int32 _curFrame; int32 _startTime; -private: +// FIXME: These are protected until the new API takes over this one +//private: uint32 _pauseLevel; uint32 _pauseStartTime; byte _audioVolume; int8 _audioBalance; }; +/** + * Improved interface for video decoder classes. + */ +class AdvancedVideoDecoder : public VideoDecoder { +public: + AdvancedVideoDecoder(); + virtual ~AdvancedVideoDecoder() {} + + // Old API Non-changing + // loadFile() + // loadStream() + // getWidth() + // getHeight() + // needsUpdate() + + // Old API Changing + virtual void close(); + bool isVideoLoaded() const; + virtual const Graphics::Surface *decodeNextFrame(); + const byte *getPalette(); + bool hasDirtyPalette() const { return _dirtyPalette; } + int getCurFrame() const; + uint32 getFrameCount() const; + uint32 getTime() const; + uint32 getTimeToNextFrame() const; + bool endOfVideo() const; + + // New API + /** + * Returns if a video is rewindable or not. + */ + bool isRewindable() const; + + /** + * Rewind a video to its beginning. + * + * If the video is playing, it will continue to play. + * + * @return true on success, false otherwise + */ + bool rewind(); + + /** + * Returns if a video is seekable or not. + */ + bool isSeekable() const; + + /** + * Seek to a given time in the video. + * + * If the video is playing, it will continue to play. + * + * @param time The time to seek to + * @return true on success, false otherwise + */ + bool seek(const Audio::Timestamp &time); + + /** + * Begin playback of the video. + * + * @note This has no effect is the video is already playing. + */ + void start(); + + /** + * Stop playback of the video. + * + * @note This will close() the video if it is not rewindable. + */ + void stop(); + + /** + * Returns if the video is currently playing or not. + * @todo Differentiate this function from endOfVideo() + */ + bool isPlaying() const { return _isPlaying; } + + /** + * Get the duration of the video. + * + * If the duration is unknown, this will return 0. + */ + virtual Audio::Timestamp getDuration() const; + + // Future API + //void setRate(const Common::Rational &rate); + //Common::Rational getRate() const; + //void setStartTime(const Audio::Timestamp &startTime); + //Audio::Timestamp getStartTime() const; + //void setStopTime(const Audio::Timestamp &stopTime); + //Audio::Timestamp getStopTime() const; + //void setSegment(const Audio::Timestamp &startTime, const Audio::Timestamp &stopTime); + +protected: + // Old API + void pauseVideoIntern(bool pause); + void updateVolume(); + void updateBalance(); + + // New API + + /** + * An abstract representation of a track in a movie. + */ + class Track { + public: + Track(); + virtual ~Track() {} + + /** + * The types of tracks this class can be. + */ + enum TrackType { + kTrackTypeNone, + kTrackTypeVideo, + kTrackTypeAudio + }; + + /** + * Get the type of track. + */ + virtual TrackType getTrackType() const = 0; + + /** + * Return if the track has finished. + */ + virtual bool endOfTrack() const = 0; + + /** + * Return if the track is rewindable. + */ + virtual bool isRewindable() const; + + /** + * Rewind the video to the beginning. + * @return true on success, false otherwise. + */ + virtual bool rewind(); + + /** + * Return if the track is seekable. + */ + virtual bool isSeekable() const { return false; } + + /** + * Seek to the given time. + * @param time The time to seek to. + * @return true on success, false otherwise. + */ + virtual bool seek(const Audio::Timestamp &time) { return false; } + + /** + * Start playback of the track. + */ + virtual void start() {} + + /** + * Stop playback of the track. + */ + virtual void stop() {} + + /** + * Set the pause status of the track. + */ + void pause(bool shouldPause) {} + + /** + * Return if the track is paused. + */ + bool isPaused() const { return _paused; } + + protected: + /** + * Function called by pause() for subclasses to implement. + */ + void pauseIntern(bool pause); + + private: + bool _paused; + }; + + /** + * An abstract representation of a video track. + */ + class VideoTrack : public Track { + public: + VideoTrack() {} + virtual ~VideoTrack() {} + + TrackType getTrackType() const { return kTrackTypeVideo; } + + // TODO: Document + virtual int getCurFrame() const = 0; + virtual int getFrameCount() const { return 0; } + virtual uint32 getNextFrameStartTime() const = 0; + virtual const Graphics::Surface *decodeNextFrame() = 0; + virtual const byte *getPalette() const { return 0; } + virtual bool hasDirtyPalette() const { return false; } + }; + + /** + * A VideoTrack that is played at a constant rate. + */ + class FixedRateVideoTrack : public virtual VideoTrack { + public: + FixedRateVideoTrack() {} + virtual ~FixedRateVideoTrack() {} + + uint32 getNextFrameStartTime() const; + + protected: + /** + * Get the rate at which this track is played. + */ + virtual Common::Rational getFrameRate() const = 0; + }; + + /** + * A VideoTrack with a known frame count that can be reliably + * used to figure out if the track has finished. + */ + class FixedLengthVideoTrack : public virtual VideoTrack { + public: + FixedLengthVideoTrack() {} + virtual ~FixedLengthVideoTrack() {} + + bool endOfTrack() const; + }; + + /** + * An abstract representation of an audio track. + */ + class AudioTrack : public Track { + public: + AudioTrack() {} + virtual ~AudioTrack() {} + + TrackType getTrackType() const { return kTrackTypeAudio; } + + virtual bool endOfTrack() const; + void start(); + void stop(); + + // TODO: Document + byte getVolume() const { return _volume; } + void setVolume(byte volume); + int8 getBalance() const { return _balance; } + void setBalance(int8 balance); + uint32 getRunningTime() const; + + virtual Audio::Mixer::SoundType getSoundType() const { return Audio::Mixer::kPlainSoundType; } + + protected: + void pauseIntern(bool pause); + + // TODO: Document + virtual Audio::AudioStream *getAudioStream() const = 0; + + private: + Audio::SoundHandle _handle; + byte _volume; + int8 _balance; + }; + + /** + * An AudioTrack that implements isRewindable() and rewind() using + * the RewindableAudioStream API. + */ + class RewindableAudioTrack : public AudioTrack { + public: + RewindableAudioTrack() {} + virtual ~RewindableAudioTrack() {} + + bool isRewindable() const { return true; } + bool rewind(); + + protected: + Audio::AudioStream *getAudioStream() const; + + // TODO: Document + virtual Audio::RewindableAudioStream *getRewindableAudioStream() const = 0; + }; + + /** + * An AudioTrack that implements isSeekable() and seek() using + * the SeekableAudioStream API. + */ + class SeekableAudioTrack : public AudioTrack { + public: + SeekableAudioTrack() {} + virtual ~SeekableAudioTrack() {} + + bool isSeekable() const { return true; } + bool seek(const Audio::Timestamp &time); + + protected: + Audio::AudioStream *getAudioStream() const; + + // TODO: Document + virtual Audio::SeekableAudioStream *getSeekableAudioStream() const = 0; + }; + + /** + * Decode enough data for the next frame and enough audio to last that long. + * + * This function is used by the default decodeNextFrame() function. A subclass + * of a Track may decide to just have its decodeNextFrame() function read + * and decode the frame. + */ + virtual void readNextPacket() {} + + /** + * Define a track to be used by this class. + * + * The pointer is then owned by this base class. + */ + void addTrack(Track *track); + +private: + // Tracks owned by this AdvancedVideoDecoder + typedef Common::List TrackList; + TrackList _tracks; + VideoTrack *findNextVideoTrack(); + const VideoTrack *findNextVideoTrack() const; + + // Current playback status + bool _isPlaying, _needsRewind; + Audio::Timestamp _audioStartOffset; + + // Palette settings from individual tracks + mutable bool _dirtyPalette; + const byte *_palette; +}; + /** * A VideoDecoder wrapper that implements getTimeToNextFrame() based on getFrameRate(). + * @note This class is now deprecated. Use AdvancedVideoDecoder instead. */ class FixedRateVideoDecoder : public virtual VideoDecoder { public: @@ -282,6 +632,7 @@ private: /** * A VideoDecoder that can be rewound back to the beginning. + * @note This class is now deprecated. Use AdvancedVideoDecoder instead. */ class RewindableVideoDecoder : public virtual VideoDecoder { public: @@ -293,6 +644,7 @@ public: /** * A VideoDecoder that can seek to a frame or point in time. + * @note This class is now deprecated. Use AdvancedVideoDecoder instead. */ class SeekableVideoDecoder : public virtual RewindableVideoDecoder { public: -- cgit v1.2.3