VIDEO: Add first draft of the new VideoDecoder API

It is currently named "AdvancedVideoDecoder" until all current VideoDecoders are converted to the new API.
This commit is contained in:
Matthew Hoops 2012-07-20 20:51:42 -04:00
parent f0304ee0bb
commit 818c16bdd0
2 changed files with 744 additions and 19 deletions

View File

@ -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;

View File

@ -23,10 +23,16 @@
#ifndef VIDEO_DECODER_H
#define VIDEO_DECODER_H
#include "audio/mixer.h"
#include "audio/timestamp.h" // TODO: Move this to common/ ?
#include "common/list.h"
#include "common/str.h"
#include "audio/timestamp.h" // TODO: Move this to common/ ?
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<Track *> 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: