mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-17 23:44:22 +00:00
VIDEO: Add support for QuickTime video track edit lists
This commit is contained in:
parent
35a0fb089a
commit
b367772b5f
@ -87,6 +87,9 @@ void QuickTimeAudioDecoder::init() {
|
||||
|
||||
// Initialize the codec (if necessary)
|
||||
entry->initCodec();
|
||||
|
||||
if (_tracks[_audioTrackIndex]->editCount != 1)
|
||||
warning("Multiple edit list entries in an audio track. Things may go awry");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -414,7 +417,9 @@ public:
|
||||
}
|
||||
|
||||
Timestamp getLength() const {
|
||||
return Timestamp(0, _tracks[_audioTrackIndex]->duration, _tracks[_audioTrackIndex]->timeScale);
|
||||
// TODO: Switch to the other one when audio edits are supported
|
||||
//return Timestamp(0, _tracks[_audioTrackIndex]->duration, _timeScale);
|
||||
return Timestamp(0, _tracks[_audioTrackIndex]->mediaDuration, _tracks[_audioTrackIndex]->timeScale);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -386,8 +386,7 @@ int QuickTimeParser::readTKHD(Atom atom) {
|
||||
|
||||
/* track->id = */_fd->readUint32BE(); // track id (NOT 0 !)
|
||||
_fd->readUint32BE(); // reserved
|
||||
//track->startTime = 0; // check
|
||||
(version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); // highlevel (considering edits) duration in movie timebase
|
||||
track->duration = (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); // highlevel (considering edits) duration in movie timebase
|
||||
_fd->readUint32BE(); // reserved
|
||||
_fd->readUint32BE(); // reserved
|
||||
|
||||
@ -410,8 +409,8 @@ int QuickTimeParser::readTKHD(Atom atom) {
|
||||
track->scaleFactorY.debugPrint(1, "readTKHD(): scaleFactorY =");
|
||||
|
||||
// these are fixed-point, 16:16
|
||||
// uint32 tkWidth = _fd->readUint32BE() >> 16; // track width
|
||||
// uint32 tkHeight = _fd->readUint32BE() >> 16; // track height
|
||||
//_fd->readUint32BE() >> 16; // track width
|
||||
//_fd->readUint32BE() >> 16; // track height
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -428,17 +427,18 @@ int QuickTimeParser::readELST(Atom atom) {
|
||||
|
||||
debug(2, "Track %d edit list count: %d", _tracks.size() - 1, track->editCount);
|
||||
|
||||
uint32 offset = 0;
|
||||
|
||||
for (uint32 i = 0; i < track->editCount; i++){
|
||||
track->editList[i].trackDuration = _fd->readUint32BE();
|
||||
track->editList[i].mediaTime = _fd->readSint32BE();
|
||||
track->editList[i].mediaRate = Rational(_fd->readUint32BE(), 0x10000);
|
||||
debugN(3, "\tDuration = %d, Media Time = %d, ", track->editList[i].trackDuration, track->editList[i].mediaTime);
|
||||
track->editList[i].timeOffset = offset;
|
||||
debugN(3, "\tDuration = %d (Offset = %d), Media Time = %d, ", track->editList[i].trackDuration, offset, track->editList[i].mediaTime);
|
||||
track->editList[i].mediaRate.debugPrint(3, "Media Rate =");
|
||||
offset += track->editList[i].trackDuration;
|
||||
}
|
||||
|
||||
if (track->editCount != 1)
|
||||
warning("Multiple edit list entries. Things may go awry");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -500,7 +500,7 @@ int QuickTimeParser::readMDHD(Atom atom) {
|
||||
}
|
||||
|
||||
track->timeScale = _fd->readUint32BE();
|
||||
track->duration = (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); // duration
|
||||
track->mediaDuration = (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); // duration
|
||||
|
||||
_fd->readUint16BE(); // language
|
||||
_fd->readUint16BE(); // quality
|
||||
@ -793,6 +793,7 @@ QuickTimeParser::Track::Track() {
|
||||
duration = 0;
|
||||
startTime = 0;
|
||||
objectTypeMP4 = 0;
|
||||
mediaDuration = 0;
|
||||
}
|
||||
|
||||
QuickTimeParser::Track::~Track() {
|
||||
|
@ -109,6 +109,7 @@ protected:
|
||||
|
||||
struct EditListEntry {
|
||||
uint32 trackDuration;
|
||||
uint32 timeOffset;
|
||||
int32 mediaTime;
|
||||
Rational mediaRate;
|
||||
};
|
||||
@ -163,6 +164,7 @@ protected:
|
||||
|
||||
uint32 frameCount;
|
||||
uint32 duration;
|
||||
uint32 mediaDuration;
|
||||
uint32 startTime;
|
||||
Rational scaleFactorX;
|
||||
Rational scaleFactorY;
|
||||
|
@ -56,155 +56,42 @@ namespace Video {
|
||||
////////////////////////////////////////////
|
||||
|
||||
QuickTimeDecoder::QuickTimeDecoder() {
|
||||
_curFrame = -1;
|
||||
_startTime = _nextFrameStartTime = 0;
|
||||
_setStartTime = false;
|
||||
_audHandle = Audio::SoundHandle();
|
||||
_scaledSurface = 0;
|
||||
_dirtyPalette = false;
|
||||
_palette = 0;
|
||||
_width = _height = 0;
|
||||
_needUpdate = false;
|
||||
}
|
||||
|
||||
QuickTimeDecoder::~QuickTimeDecoder() {
|
||||
close();
|
||||
}
|
||||
|
||||
uint16 QuickTimeDecoder::getWidth() const {
|
||||
if (_videoTrackIndex < 0)
|
||||
return 0;
|
||||
int32 QuickTimeDecoder::getCurFrame() const {
|
||||
// TODO: This is rather simplistic and doesn't take edits that
|
||||
// repeat sections of the media into account. Doing that
|
||||
// over-complicates things and shouldn't be necessary, but
|
||||
// it would be nice to have in the future.
|
||||
|
||||
return (Common::Rational(_tracks[_videoTrackIndex]->width) / getScaleFactorX()).toInt();
|
||||
}
|
||||
int32 frame = -1;
|
||||
|
||||
uint16 QuickTimeDecoder::getHeight() const {
|
||||
if (_videoTrackIndex < 0)
|
||||
return 0;
|
||||
for (uint32 i = 0; i < _handlers.size(); i++)
|
||||
if (_handlers[i]->getTrackType() == TrackHandler::kTrackTypeVideo)
|
||||
frame += ((VideoTrackHandler *)_handlers[i])->getCurFrame() + 1;
|
||||
|
||||
return (Common::Rational(_tracks[_videoTrackIndex]->height) / getScaleFactorY()).toInt();
|
||||
}
|
||||
|
||||
uint32 QuickTimeDecoder::getFrameCount() const {
|
||||
if (_videoTrackIndex < 0)
|
||||
return 0;
|
||||
|
||||
return _tracks[_videoTrackIndex]->frameCount;
|
||||
}
|
||||
|
||||
Common::Rational QuickTimeDecoder::getScaleFactorX() const {
|
||||
if (_videoTrackIndex < 0)
|
||||
return 1;
|
||||
|
||||
return (_scaleFactorX * _tracks[_videoTrackIndex]->scaleFactorX);
|
||||
}
|
||||
|
||||
Common::Rational QuickTimeDecoder::getScaleFactorY() const {
|
||||
if (_videoTrackIndex < 0)
|
||||
return 1;
|
||||
|
||||
return (_scaleFactorY * _tracks[_videoTrackIndex]->scaleFactorY);
|
||||
}
|
||||
|
||||
uint32 QuickTimeDecoder::getFrameDuration() {
|
||||
if (_videoTrackIndex < 0)
|
||||
return 0;
|
||||
|
||||
uint32 curFrameIndex = 0;
|
||||
for (int32 i = 0; i < _tracks[_videoTrackIndex]->timeToSampleCount; i++) {
|
||||
curFrameIndex += _tracks[_videoTrackIndex]->timeToSample[i].count;
|
||||
if ((uint32)_curFrame < curFrameIndex) {
|
||||
// Ok, now we have what duration this frame has.
|
||||
return _tracks[_videoTrackIndex]->timeToSample[i].duration;
|
||||
}
|
||||
}
|
||||
|
||||
// This should never occur
|
||||
error ("Cannot find duration for frame %d", _curFrame);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Graphics::PixelFormat QuickTimeDecoder::getPixelFormat() const {
|
||||
Codec *codec = findDefaultVideoCodec();
|
||||
|
||||
if (!codec)
|
||||
return Graphics::PixelFormat::createFormatCLUT8();
|
||||
|
||||
return codec->getPixelFormat();
|
||||
}
|
||||
|
||||
uint32 QuickTimeDecoder::findKeyFrame(uint32 frame) const {
|
||||
for (int i = _tracks[_videoTrackIndex]->keyframeCount - 1; i >= 0; i--)
|
||||
if (_tracks[_videoTrackIndex]->keyframes[i] <= frame)
|
||||
return _tracks[_videoTrackIndex]->keyframes[i];
|
||||
|
||||
// If none found, we'll assume the requested frame is a key frame
|
||||
return frame;
|
||||
}
|
||||
|
||||
void QuickTimeDecoder::seekToFrame(uint32 frame) {
|
||||
assert(_videoTrackIndex >= 0);
|
||||
assert(frame < _tracks[_videoTrackIndex]->frameCount);
|
||||
uint32 QuickTimeDecoder::getFrameCount() const {
|
||||
uint32 count = 0;
|
||||
|
||||
// Stop all audio (for now)
|
||||
stopAudio();
|
||||
for (uint32 i = 0; i < _handlers.size(); i++)
|
||||
if (_handlers[i]->getTrackType() == TrackHandler::kTrackTypeVideo)
|
||||
count += ((VideoTrackHandler *)_handlers[i])->getFrameCount();
|
||||
|
||||
// Track down the keyframe
|
||||
_curFrame = findKeyFrame(frame) - 1;
|
||||
while (_curFrame < (int32)frame - 1)
|
||||
decodeNextFrame();
|
||||
|
||||
// Map out the starting point
|
||||
_nextFrameStartTime = 0;
|
||||
uint32 curFrame = 0;
|
||||
|
||||
for (int32 i = 0; i < _tracks[_videoTrackIndex]->timeToSampleCount && curFrame < frame; i++) {
|
||||
for (int32 j = 0; j < _tracks[_videoTrackIndex]->timeToSample[i].count && curFrame < frame; j++) {
|
||||
curFrame++;
|
||||
_nextFrameStartTime += _tracks[_videoTrackIndex]->timeToSample[i].duration;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust the video starting point
|
||||
const Audio::Timestamp curVideoTime(0, _nextFrameStartTime, _tracks[_videoTrackIndex]->timeScale);
|
||||
_startTime = g_system->getMillis() - curVideoTime.msecs();
|
||||
resetPauseStartTime();
|
||||
|
||||
// Adjust the audio starting point
|
||||
if (_audioTrackIndex >= 0) {
|
||||
_audioStartOffset = curVideoTime;
|
||||
|
||||
// Seek to the new audio location
|
||||
setAudioStreamPos(_audioStartOffset);
|
||||
|
||||
// Restart the audio
|
||||
startAudio();
|
||||
|
||||
// Pause the audio again if we're still paused
|
||||
if (isPaused() && _audStream)
|
||||
g_system->getMixer()->pauseHandle(_audHandle, true);
|
||||
}
|
||||
}
|
||||
|
||||
void QuickTimeDecoder::seekToTime(Audio::Timestamp time) {
|
||||
// Use makeQuickTimeStream() instead
|
||||
if (_videoTrackIndex < 0)
|
||||
error("Audio-only seeking not supported");
|
||||
|
||||
// Try to find the last frame that should have been decoded
|
||||
uint32 frame = 0;
|
||||
Audio::Timestamp totalDuration(0, _tracks[_videoTrackIndex]->timeScale);
|
||||
bool done = false;
|
||||
|
||||
for (int32 i = 0; i < _tracks[_videoTrackIndex]->timeToSampleCount && !done; i++) {
|
||||
for (int32 j = 0; j < _tracks[_videoTrackIndex]->timeToSample[i].count; j++) {
|
||||
totalDuration = totalDuration.addFrames(_tracks[_videoTrackIndex]->timeToSample[i].duration);
|
||||
if (totalDuration > time) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
frame++;
|
||||
}
|
||||
}
|
||||
|
||||
seekToFrame(frame);
|
||||
return count;
|
||||
}
|
||||
|
||||
void QuickTimeDecoder::startAudio() {
|
||||
@ -224,87 +111,83 @@ void QuickTimeDecoder::pauseVideoIntern(bool pause) {
|
||||
g_system->getMixer()->pauseHandle(_audHandle, pause);
|
||||
}
|
||||
|
||||
Codec *QuickTimeDecoder::findDefaultVideoCodec() const {
|
||||
if (_videoTrackIndex < 0 || _tracks[_videoTrackIndex]->sampleDescs.empty())
|
||||
return 0;
|
||||
QuickTimeDecoder::VideoTrackHandler *QuickTimeDecoder::findNextVideoTrack() const {
|
||||
VideoTrackHandler *bestTrack = 0;
|
||||
int32 num;
|
||||
uint32 bestTime = 0xffffffff;
|
||||
|
||||
return ((VideoSampleDesc *)_tracks[_videoTrackIndex]->sampleDescs[0])->_videoCodec;
|
||||
}
|
||||
for (uint32 i = 0; i < _handlers.size(); i++) {
|
||||
if (_handlers[i]->getTrackType() == TrackHandler::kTrackTypeVideo && !_handlers[i]->endOfTrack()) {
|
||||
VideoTrackHandler *track = (VideoTrackHandler *)_handlers[i];
|
||||
uint32 time = track->getNextFrameStartTime();
|
||||
|
||||
const Graphics::Surface *QuickTimeDecoder::decodeNextFrame() {
|
||||
if (_videoTrackIndex < 0 || _curFrame >= (int32)getFrameCount() - 1)
|
||||
return 0;
|
||||
|
||||
if (_startTime == 0)
|
||||
_startTime = g_system->getMillis();
|
||||
|
||||
_curFrame++;
|
||||
_nextFrameStartTime += getFrameDuration();
|
||||
|
||||
// Update the audio while we're at it
|
||||
updateAudioBuffer();
|
||||
|
||||
// Get the next packet
|
||||
uint32 descId;
|
||||
Common::SeekableReadStream *frameData = getNextFramePacket(descId);
|
||||
|
||||
if (!frameData || !descId || descId > _tracks[_videoTrackIndex]->sampleDescs.size())
|
||||
return 0;
|
||||
|
||||
// Find which video description entry we want
|
||||
VideoSampleDesc *entry = (VideoSampleDesc *)_tracks[_videoTrackIndex]->sampleDescs[descId - 1];
|
||||
|
||||
if (!entry->_videoCodec)
|
||||
return 0;
|
||||
|
||||
const Graphics::Surface *frame = entry->_videoCodec->decodeImage(frameData);
|
||||
delete frameData;
|
||||
|
||||
// Update the palette
|
||||
if (entry->_videoCodec->containsPalette()) {
|
||||
// The codec itself contains a palette
|
||||
if (entry->_videoCodec->hasDirtyPalette()) {
|
||||
_palette = entry->_videoCodec->getPalette();
|
||||
_dirtyPalette = true;
|
||||
}
|
||||
} else {
|
||||
// Check if the video description has been updated
|
||||
byte *palette = entry->_palette;
|
||||
|
||||
if (palette != _palette) {
|
||||
_palette = palette;
|
||||
_dirtyPalette = true;
|
||||
if (time < bestTime) {
|
||||
bestTime = time;
|
||||
bestTrack = track;
|
||||
num = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scaleSurface(frame);
|
||||
return bestTrack;
|
||||
}
|
||||
|
||||
const Graphics::Surface *QuickTimeDecoder::scaleSurface(const Graphics::Surface *frame) {
|
||||
if (getScaleFactorX() == 1 && getScaleFactorY() == 1)
|
||||
return frame;
|
||||
const Graphics::Surface *QuickTimeDecoder::decodeNextFrame() {
|
||||
if (!_nextVideoTrack)
|
||||
return 0;
|
||||
|
||||
assert(_scaledSurface);
|
||||
const Graphics::Surface *frame = _nextVideoTrack->decodeNextFrame();
|
||||
|
||||
for (int32 j = 0; j < _scaledSurface->h; j++)
|
||||
for (int32 k = 0; k < _scaledSurface->w; k++)
|
||||
memcpy(_scaledSurface->getBasePtr(k, j), frame->getBasePtr((k * getScaleFactorX()).toInt() , (j * getScaleFactorY()).toInt()), frame->format.bytesPerPixel);
|
||||
if (!_setStartTime) {
|
||||
_startTime = g_system->getMillis();
|
||||
_setStartTime = true;
|
||||
}
|
||||
|
||||
return _scaledSurface;
|
||||
_nextVideoTrack = findNextVideoTrack();
|
||||
_needUpdate = false;
|
||||
|
||||
// Update audio buffers too
|
||||
// (needs to be done after we find the next track)
|
||||
for (uint32 i = 0; i < _handlers.size(); i++)
|
||||
if (_handlers[i]->getTrackType() == TrackHandler::kTrackTypeAudio)
|
||||
((AudioTrackHandler *)_handlers[i])->updateBuffer();
|
||||
|
||||
if (_scaledSurface) {
|
||||
scaleSurface(frame, _scaledSurface, _scaleFactorX, _scaleFactorY);
|
||||
return _scaledSurface;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void QuickTimeDecoder::scaleSurface(const Graphics::Surface *src, Graphics::Surface *dst, Common::Rational scaleFactorX, Common::Rational scaleFactorY) {
|
||||
assert(src && dst);
|
||||
|
||||
for (int32 j = 0; j < dst->h; j++)
|
||||
for (int32 k = 0; k < dst->w; k++)
|
||||
memcpy(dst->getBasePtr(k, j), src->getBasePtr((k * scaleFactorX).toInt() , (j * scaleFactorY).toInt()), src->format.bytesPerPixel);
|
||||
}
|
||||
|
||||
bool QuickTimeDecoder::endOfVideo() const {
|
||||
return (!_audStream || _audStream->endOfData()) && (!findDefaultVideoCodec() || SeekableVideoDecoder::endOfVideo());
|
||||
if (!isVideoLoaded())
|
||||
return true;
|
||||
|
||||
for (uint32 i = 0; i < _handlers.size(); i++)
|
||||
if (!_handlers[i]->endOfTrack())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 QuickTimeDecoder::getElapsedTime() const {
|
||||
// TODO: Convert to multi-track
|
||||
if (_audStream) {
|
||||
// Use the audio time if present and the audio track's time is less than the
|
||||
// total length of the audio track. The audio track can end before the video
|
||||
// track, so we need to fall back on the getMillis() time tracking in that
|
||||
// case.
|
||||
uint32 time = g_system->getMixer()->getSoundElapsedTime(_audHandle) + _audioStartOffset.msecs();
|
||||
if (time < _tracks[_audioTrackIndex]->duration * 1000 / _tracks[_audioTrackIndex]->timeScale)
|
||||
if (time < _tracks[_audioTrackIndex]->mediaDuration * 1000 / _tracks[_audioTrackIndex]->timeScale)
|
||||
return time;
|
||||
}
|
||||
|
||||
@ -313,17 +196,24 @@ uint32 QuickTimeDecoder::getElapsedTime() const {
|
||||
}
|
||||
|
||||
uint32 QuickTimeDecoder::getTimeToNextFrame() const {
|
||||
if (endOfVideo() || _curFrame < 0)
|
||||
if (_needUpdate)
|
||||
return 0;
|
||||
|
||||
// Convert from the QuickTime rate base to 1000
|
||||
uint32 nextFrameStartTime = _nextFrameStartTime * 1000 / _tracks[_videoTrackIndex]->timeScale;
|
||||
uint32 elapsedTime = getElapsedTime();
|
||||
if (_nextVideoTrack) {
|
||||
uint32 nextFrameStartTime = _nextVideoTrack->getNextFrameStartTime();
|
||||
|
||||
if (nextFrameStartTime <= elapsedTime)
|
||||
return 0;
|
||||
if (nextFrameStartTime == 0)
|
||||
return 0;
|
||||
|
||||
return nextFrameStartTime - elapsedTime;
|
||||
// TODO: Add support for rate modification
|
||||
|
||||
uint32 elapsedTime = getElapsedTime();
|
||||
|
||||
if (elapsedTime < nextFrameStartTime)
|
||||
return nextFrameStartTime - elapsedTime;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool QuickTimeDecoder::loadFile(const Common::String &filename) {
|
||||
@ -345,30 +235,49 @@ bool QuickTimeDecoder::loadStream(Common::SeekableReadStream *stream) {
|
||||
void QuickTimeDecoder::init() {
|
||||
Audio::QuickTimeAudioDecoder::init();
|
||||
|
||||
_videoTrackIndex = -1;
|
||||
_startTime = 0;
|
||||
|
||||
// Find video streams
|
||||
for (uint32 i = 0; i < _tracks.size(); i++)
|
||||
if (_tracks[i]->codecType == CODEC_TYPE_VIDEO && _videoTrackIndex < 0)
|
||||
_videoTrackIndex = i;
|
||||
_setStartTime = false;
|
||||
|
||||
// Start the audio codec if we've got one that we can handle
|
||||
if (_audStream) {
|
||||
startAudio();
|
||||
_audioStartOffset = Audio::Timestamp(0);
|
||||
|
||||
// TODO: Support multiple audio tracks
|
||||
// For now, just push back a handler for the first audio track
|
||||
_handlers.push_back(new AudioTrackHandler(this, _tracks[_audioTrackIndex]));
|
||||
}
|
||||
|
||||
// Initialize video, if present
|
||||
if (_videoTrackIndex >= 0) {
|
||||
for (uint32 i = 0; i < _tracks[_videoTrackIndex]->sampleDescs.size(); i++)
|
||||
((VideoSampleDesc *)_tracks[_videoTrackIndex]->sampleDescs[i])->initCodec();
|
||||
// Initialize all the video tracks
|
||||
for (uint32 i = 0; i < _tracks.size(); i++) {
|
||||
if (_tracks[i]->codecType == CODEC_TYPE_VIDEO) {
|
||||
for (uint32 j = 0; j < _tracks[i]->sampleDescs.size(); j++)
|
||||
((VideoSampleDesc *)_tracks[i]->sampleDescs[j])->initCodec();
|
||||
|
||||
if (getScaleFactorX() != 1 || getScaleFactorY() != 1) {
|
||||
_handlers.push_back(new VideoTrackHandler(this, _tracks[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare the first video track
|
||||
_nextVideoTrack = findNextVideoTrack();
|
||||
|
||||
if (_nextVideoTrack) {
|
||||
// Initialize the scaled surface
|
||||
if (_scaleFactorX != 1 || _scaleFactorY != 1) {
|
||||
// We have to initialize the scaled surface
|
||||
_scaledSurface = new Graphics::Surface();
|
||||
_scaledSurface->create(getWidth(), getHeight(), getPixelFormat());
|
||||
_scaledSurface->create((_nextVideoTrack->getWidth() / _scaleFactorX).toInt(),
|
||||
(_nextVideoTrack->getHeight() / _scaleFactorY).toInt(), getPixelFormat());
|
||||
_width = _scaledSurface->w;
|
||||
_height = _scaledSurface->h;
|
||||
} else {
|
||||
_width = _nextVideoTrack->getWidth().toInt();
|
||||
_height = _nextVideoTrack->getHeight().toInt();
|
||||
}
|
||||
|
||||
_needUpdate = true;
|
||||
} else {
|
||||
_needUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -472,6 +381,7 @@ Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(Track *tra
|
||||
|
||||
void QuickTimeDecoder::close() {
|
||||
stopAudio();
|
||||
freeAllTrackHandlers();
|
||||
|
||||
if (_scaledSurface) {
|
||||
_scaledSurface->free();
|
||||
@ -479,93 +389,46 @@ void QuickTimeDecoder::close() {
|
||||
_scaledSurface = 0;
|
||||
}
|
||||
|
||||
_width = _height = 0;
|
||||
|
||||
Common::QuickTimeParser::close();
|
||||
SeekableVideoDecoder::reset();
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *QuickTimeDecoder::getNextFramePacket(uint32 &descId) {
|
||||
if (_videoTrackIndex < 0)
|
||||
return NULL;
|
||||
void QuickTimeDecoder::freeAllTrackHandlers() {
|
||||
for (uint32 i = 0; i < _handlers.size(); i++)
|
||||
delete _handlers[i];
|
||||
|
||||
// First, we have to track down which chunk holds the sample and which sample in the chunk contains the frame we are looking for.
|
||||
int32 totalSampleCount = 0;
|
||||
int32 sampleInChunk = 0;
|
||||
int32 actualChunk = -1;
|
||||
uint32 sampleToChunkIndex = 0;
|
||||
_handlers.clear();
|
||||
}
|
||||
|
||||
for (uint32 i = 0; i < _tracks[_videoTrackIndex]->chunkCount; i++) {
|
||||
if (sampleToChunkIndex < _tracks[_videoTrackIndex]->sampleToChunkCount && i >= _tracks[_videoTrackIndex]->sampleToChunk[sampleToChunkIndex].first)
|
||||
sampleToChunkIndex++;
|
||||
void QuickTimeDecoder::seekToTime(Audio::Timestamp time) {
|
||||
// Sets all tracks to this time
|
||||
for (uint32 i = 0; i < _handlers.size(); i++)
|
||||
_handlers[i]->seekToTime(time);
|
||||
|
||||
totalSampleCount += _tracks[_videoTrackIndex]->sampleToChunk[sampleToChunkIndex - 1].count;
|
||||
// Reset our start time
|
||||
_startTime = g_system->getMillis() - time.msecs();
|
||||
_setStartTime = true;
|
||||
resetPauseStartTime();
|
||||
|
||||
if (totalSampleCount > getCurFrame()) {
|
||||
actualChunk = i;
|
||||
descId = _tracks[_videoTrackIndex]->sampleToChunk[sampleToChunkIndex - 1].id;
|
||||
sampleInChunk = _tracks[_videoTrackIndex]->sampleToChunk[sampleToChunkIndex - 1].count - totalSampleCount + getCurFrame();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (actualChunk < 0) {
|
||||
warning ("Could not find data for frame %d", getCurFrame());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Next seek to that frame
|
||||
_fd->seek(_tracks[_videoTrackIndex]->chunkOffsets[actualChunk]);
|
||||
|
||||
// Then, if the chunk holds more than one frame, seek to where the frame we want is located
|
||||
for (int32 i = getCurFrame() - sampleInChunk; i < getCurFrame(); i++) {
|
||||
if (_tracks[_videoTrackIndex]->sampleSize != 0)
|
||||
_fd->skip(_tracks[_videoTrackIndex]->sampleSize);
|
||||
else
|
||||
_fd->skip(_tracks[_videoTrackIndex]->sampleSizes[i]);
|
||||
}
|
||||
|
||||
// Finally, read in the raw data for the frame
|
||||
//printf ("Frame Data[%d]: Offset = %d, Size = %d\n", getCurFrame(), _fd->pos(), _tracks[_videoTrackIndex]->sampleSizes[getCurFrame()]);
|
||||
|
||||
if (_tracks[_videoTrackIndex]->sampleSize != 0)
|
||||
return _fd->readStream(_tracks[_videoTrackIndex]->sampleSize);
|
||||
|
||||
return _fd->readStream(_tracks[_videoTrackIndex]->sampleSizes[getCurFrame()]);
|
||||
// Reset the next video track too
|
||||
_nextVideoTrack = findNextVideoTrack();
|
||||
_needUpdate = _nextVideoTrack != 0;
|
||||
}
|
||||
|
||||
void QuickTimeDecoder::updateAudioBuffer() {
|
||||
if (!_audStream)
|
||||
return;
|
||||
// Updates the audio buffers for all audio tracks
|
||||
for (uint32 i = 0; i < _handlers.size(); i++)
|
||||
if (_handlers[i]->getTrackType() == TrackHandler::kTrackTypeAudio)
|
||||
((AudioTrackHandler *)_handlers[i])->updateBuffer();
|
||||
}
|
||||
|
||||
uint32 numberOfChunksNeeded = 0;
|
||||
Graphics::PixelFormat QuickTimeDecoder::getPixelFormat() const {
|
||||
if (_nextVideoTrack)
|
||||
return _nextVideoTrack->getPixelFormat();
|
||||
|
||||
if (_videoTrackIndex < 0 || _curFrame == (int32)_tracks[_videoTrackIndex]->frameCount - 1) {
|
||||
// If we have no video, there's nothing to base our buffer against
|
||||
// However, one must ask why a QuickTimeDecoder is being used instead of the nice makeQuickTimeStream() function
|
||||
|
||||
// If we're on the last frame, make sure all audio remaining is buffered
|
||||
numberOfChunksNeeded = _tracks[_audioTrackIndex]->chunkCount;
|
||||
} else {
|
||||
Audio::QuickTimeAudioDecoder::AudioSampleDesc *entry = (Audio::QuickTimeAudioDecoder::AudioSampleDesc *)_tracks[_audioTrackIndex]->sampleDescs[0];
|
||||
|
||||
// Calculate the amount of chunks we need in memory until the next frame
|
||||
uint32 timeToNextFrame = getTimeToNextFrame();
|
||||
uint32 timeFilled = 0;
|
||||
uint32 curAudioChunk = _curAudioChunk - _audStream->numQueuedStreams();
|
||||
|
||||
for (; timeFilled < timeToNextFrame && curAudioChunk < _tracks[_audioTrackIndex]->chunkCount; numberOfChunksNeeded++, curAudioChunk++) {
|
||||
uint32 sampleCount = entry->getAudioChunkSampleCount(curAudioChunk);
|
||||
assert(sampleCount);
|
||||
|
||||
timeFilled += sampleCount * 1000 / entry->_sampleRate;
|
||||
}
|
||||
|
||||
// Add a couple extra to ensure we don't underrun
|
||||
numberOfChunksNeeded += 3;
|
||||
}
|
||||
|
||||
// Keep three streams in buffer so that if/when the first two end, it goes right into the next
|
||||
while (_audStream->numQueuedStreams() < numberOfChunksNeeded && _curAudioChunk < _tracks[_audioTrackIndex]->chunkCount)
|
||||
queueNextAudioChunk();
|
||||
return Graphics::PixelFormat();
|
||||
}
|
||||
|
||||
QuickTimeDecoder::VideoSampleDesc::VideoSampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag) : Common::QuickTimeParser::SampleDesc(parentTrack, codecTag) {
|
||||
@ -620,4 +483,383 @@ void QuickTimeDecoder::VideoSampleDesc::initCodec() {
|
||||
}
|
||||
}
|
||||
|
||||
bool QuickTimeDecoder::endOfVideoTracks() const {
|
||||
for (uint32 i = 0; i < _handlers.size(); i++)
|
||||
if (_handlers[i]->getTrackType() == TrackHandler::kTrackTypeVideo && !_handlers[i]->endOfTrack())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QuickTimeDecoder::TrackHandler::TrackHandler(QuickTimeDecoder *decoder, Track *parent) : _decoder(decoder), _parent(parent), _fd(_decoder->_fd) {
|
||||
_curEdit = 0;
|
||||
}
|
||||
|
||||
bool QuickTimeDecoder::TrackHandler::endOfTrack() {
|
||||
// A track is over when we've finished going through all edits
|
||||
return _curEdit == _parent->editCount;
|
||||
}
|
||||
|
||||
QuickTimeDecoder::AudioTrackHandler::AudioTrackHandler(QuickTimeDecoder *decoder, Track *parent) : TrackHandler(decoder, parent) {
|
||||
}
|
||||
|
||||
void QuickTimeDecoder::AudioTrackHandler::updateBuffer() {
|
||||
if (!_decoder->_audStream)
|
||||
return;
|
||||
|
||||
uint32 numberOfChunksNeeded = 0;
|
||||
|
||||
if (_decoder->endOfVideoTracks()) {
|
||||
// If we have no video left (or no video), there's nothing to base our buffer against
|
||||
numberOfChunksNeeded = _parent->chunkCount;
|
||||
} else {
|
||||
Audio::QuickTimeAudioDecoder::AudioSampleDesc *entry = (Audio::QuickTimeAudioDecoder::AudioSampleDesc *)_parent->sampleDescs[0];
|
||||
|
||||
// Calculate the amount of chunks we need in memory until the next frame
|
||||
uint32 timeToNextFrame = _decoder->getTimeToNextFrame();
|
||||
uint32 timeFilled = 0;
|
||||
uint32 curAudioChunk = _decoder->_curAudioChunk - _decoder->_audStream->numQueuedStreams();
|
||||
|
||||
for (; timeFilled < timeToNextFrame && curAudioChunk < _parent->chunkCount; numberOfChunksNeeded++, curAudioChunk++) {
|
||||
uint32 sampleCount = entry->getAudioChunkSampleCount(curAudioChunk);
|
||||
assert(sampleCount);
|
||||
|
||||
timeFilled += sampleCount * 1000 / entry->_sampleRate;
|
||||
}
|
||||
|
||||
// Add a couple extra to ensure we don't underrun
|
||||
numberOfChunksNeeded += 3;
|
||||
}
|
||||
|
||||
// Keep three streams in buffer so that if/when the first two end, it goes right into the next
|
||||
while (_decoder->_audStream->numQueuedStreams() < numberOfChunksNeeded && _decoder->_curAudioChunk < _parent->chunkCount)
|
||||
_decoder->queueNextAudioChunk();
|
||||
}
|
||||
|
||||
bool QuickTimeDecoder::AudioTrackHandler::endOfTrack() {
|
||||
// TODO: Handle edits
|
||||
return (_decoder->_curAudioChunk == _parent->chunkCount) && _decoder->_audStream->endOfData();
|
||||
}
|
||||
|
||||
void QuickTimeDecoder::AudioTrackHandler::seekToTime(Audio::Timestamp time) {
|
||||
if (_decoder->_audStream) {
|
||||
// Stop all audio
|
||||
_decoder->stopAudio();
|
||||
|
||||
_decoder->_audioStartOffset = time;
|
||||
|
||||
// Seek to the new audio location
|
||||
_decoder->setAudioStreamPos(_decoder->_audioStartOffset);
|
||||
|
||||
// Restart the audio
|
||||
_decoder->startAudio();
|
||||
|
||||
// Pause the audio again if we're still paused
|
||||
if (_decoder->isPaused() && _decoder->_audStream)
|
||||
g_system->getMixer()->pauseHandle(_decoder->_audHandle, true);
|
||||
}
|
||||
}
|
||||
|
||||
QuickTimeDecoder::VideoTrackHandler::VideoTrackHandler(QuickTimeDecoder *decoder, Common::QuickTimeParser::Track *parent) : TrackHandler(decoder, parent) {
|
||||
if (_parent->scaleFactorX != 1 || _parent->scaleFactorY != 1) {
|
||||
_scaledSurface = new Graphics::Surface();
|
||||
_scaledSurface->create(getWidth().toInt(), getHeight().toInt(), getPixelFormat());
|
||||
} else {
|
||||
_scaledSurface = 0;
|
||||
}
|
||||
|
||||
enterNewEditList(false);
|
||||
|
||||
_holdNextFrameStartTime = false;
|
||||
_curFrame = -1;
|
||||
_durationOverride = -1;
|
||||
|
||||
}
|
||||
|
||||
QuickTimeDecoder::VideoTrackHandler::~VideoTrackHandler() {
|
||||
if (_scaledSurface) {
|
||||
_scaledSurface->free();
|
||||
delete _scaledSurface;
|
||||
}
|
||||
}
|
||||
|
||||
const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::decodeNextFrame() {
|
||||
if (endOfTrack())
|
||||
return 0;
|
||||
|
||||
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;
|
||||
} 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++;
|
||||
|
||||
if (!endOfTrack())
|
||||
enterNewEditList(true);
|
||||
}
|
||||
|
||||
if (_scaledSurface) {
|
||||
_decoder->scaleSurface(frame, _scaledSurface, _parent->scaleFactorX, _parent->scaleFactorY);
|
||||
return _scaledSurface;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void QuickTimeDecoder::VideoTrackHandler::enterNewEditList(bool bufferFrames) {
|
||||
// Bypass all empty edit lists first
|
||||
while (!endOfTrack() && _parent->editList[_curEdit].mediaTime == -1)
|
||||
_curEdit++;
|
||||
|
||||
if (endOfTrack())
|
||||
return;
|
||||
|
||||
uint32 frameNum = 0;
|
||||
bool done = false;
|
||||
uint32 totalDuration = 0;
|
||||
uint32 prevDuration = 0;
|
||||
|
||||
// Track down where the mediaTime is in the media
|
||||
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) {
|
||||
done = true;
|
||||
prevDuration = totalDuration;
|
||||
break;
|
||||
} else if (totalDuration > (uint32)_parent->editList[_curEdit].mediaTime) {
|
||||
done = true;
|
||||
frameNum--;
|
||||
break;
|
||||
}
|
||||
|
||||
prevDuration = totalDuration;
|
||||
totalDuration += _parent->timeToSample[i].duration;
|
||||
frameNum++;
|
||||
}
|
||||
}
|
||||
|
||||
if (bufferFrames) {
|
||||
// Track down the keyframe
|
||||
_curFrame = findKeyFrame(frameNum) - 1;
|
||||
while (_curFrame < (int32)frameNum - 1)
|
||||
bufferNextFrame();
|
||||
} else {
|
||||
_curFrame = frameNum - 1;
|
||||
}
|
||||
|
||||
_nextFrameStartTime = getCurEditTimeOffset();
|
||||
|
||||
// Set an override for the duration since we came up in-between two frames
|
||||
if (prevDuration != totalDuration)
|
||||
_durationOverride = totalDuration - prevDuration;
|
||||
}
|
||||
|
||||
const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::bufferNextFrame() {
|
||||
_curFrame++;
|
||||
|
||||
// Get the next packet
|
||||
uint32 descId;
|
||||
Common::SeekableReadStream *frameData = getNextFramePacket(descId);
|
||||
|
||||
if (!frameData || !descId || descId > _parent->sampleDescs.size())
|
||||
return 0;
|
||||
|
||||
// Find which video description entry we want
|
||||
VideoSampleDesc *entry = (VideoSampleDesc *)_parent->sampleDescs[descId - 1];
|
||||
|
||||
if (!entry->_videoCodec)
|
||||
return 0;
|
||||
|
||||
const Graphics::Surface *frame = entry->_videoCodec->decodeImage(frameData);
|
||||
delete frameData;
|
||||
|
||||
// Update the palette
|
||||
if (entry->_videoCodec->containsPalette()) {
|
||||
// The codec itself contains a palette
|
||||
if (entry->_videoCodec->hasDirtyPalette()) {
|
||||
_decoder->_palette = entry->_videoCodec->getPalette();
|
||||
_decoder->_dirtyPalette = true;
|
||||
}
|
||||
} else {
|
||||
// Check if the video description has been updated
|
||||
byte *palette = entry->_palette;
|
||||
|
||||
if (palette !=_decoder-> _palette) {
|
||||
_decoder->_palette = palette;
|
||||
_decoder->_dirtyPalette = true;
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
uint32 QuickTimeDecoder::VideoTrackHandler::getNextFrameStartTime() {
|
||||
if (endOfTrack())
|
||||
return 0;
|
||||
|
||||
// Convert to milliseconds so the tracks can be compared
|
||||
return getRateAdjustedFrameTime() * 1000 / _parent->timeScale;
|
||||
}
|
||||
|
||||
uint32 QuickTimeDecoder::VideoTrackHandler::getFrameCount() {
|
||||
return _parent->frameCount;
|
||||
}
|
||||
|
||||
uint32 QuickTimeDecoder::VideoTrackHandler::getFrameDuration() {
|
||||
uint32 curFrameIndex = 0;
|
||||
for (int32 i = 0; i < _parent->timeToSampleCount; i++) {
|
||||
curFrameIndex += _parent->timeToSample[i].count;
|
||||
if ((uint32)_curFrame < curFrameIndex) {
|
||||
// Ok, now we have what duration this frame has.
|
||||
return _parent->timeToSample[i].duration;
|
||||
}
|
||||
}
|
||||
|
||||
// This should never occur
|
||||
error("Cannot find duration for frame %d", _curFrame);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *QuickTimeDecoder::VideoTrackHandler::getNextFramePacket(uint32 &descId) {
|
||||
// First, we have to track down which chunk holds the sample and which sample in the chunk contains the frame we are looking for.
|
||||
int32 totalSampleCount = 0;
|
||||
int32 sampleInChunk = 0;
|
||||
int32 actualChunk = -1;
|
||||
uint32 sampleToChunkIndex = 0;
|
||||
|
||||
for (uint32 i = 0; i < _parent->chunkCount; i++) {
|
||||
if (sampleToChunkIndex < _parent->sampleToChunkCount && i >= _parent->sampleToChunk[sampleToChunkIndex].first)
|
||||
sampleToChunkIndex++;
|
||||
|
||||
totalSampleCount += _parent->sampleToChunk[sampleToChunkIndex - 1].count;
|
||||
|
||||
if (totalSampleCount > _curFrame) {
|
||||
actualChunk = i;
|
||||
descId = _parent->sampleToChunk[sampleToChunkIndex - 1].id;
|
||||
sampleInChunk = _parent->sampleToChunk[sampleToChunkIndex - 1].count - totalSampleCount + _curFrame;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (actualChunk < 0) {
|
||||
warning("Could not find data for frame %d", _curFrame);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Next seek to that frame
|
||||
_fd->seek(_parent->chunkOffsets[actualChunk]);
|
||||
|
||||
// Then, if the chunk holds more than one frame, seek to where the frame we want is located
|
||||
for (int32 i = _curFrame - sampleInChunk; i < _curFrame; i++) {
|
||||
if (_parent->sampleSize != 0)
|
||||
_fd->skip(_parent->sampleSize);
|
||||
else
|
||||
_fd->skip(_parent->sampleSizes[i]);
|
||||
}
|
||||
|
||||
// Finally, read in the raw data for the frame
|
||||
//debug("Frame Data[%d]: Offset = %d, Size = %d", _curFrame, _fd->pos(), _parent->sampleSizes[_curFrame]);
|
||||
|
||||
if (_parent->sampleSize != 0)
|
||||
return _fd->readStream(_parent->sampleSize);
|
||||
|
||||
return _fd->readStream(_parent->sampleSizes[_curFrame]);
|
||||
}
|
||||
|
||||
uint32 QuickTimeDecoder::VideoTrackHandler::findKeyFrame(uint32 frame) const {
|
||||
for (int i = _parent->keyframeCount - 1; i >= 0; i--)
|
||||
if (_parent->keyframes[i] <= frame)
|
||||
return _parent->keyframes[i];
|
||||
|
||||
// If none found, we'll assume the requested frame is a key frame
|
||||
return frame;
|
||||
}
|
||||
|
||||
void QuickTimeDecoder::VideoTrackHandler::seekToTime(Audio::Timestamp time) {
|
||||
// First, figure out what edit we're in
|
||||
time = time.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())
|
||||
break;
|
||||
|
||||
// This track is done
|
||||
if (endOfTrack())
|
||||
return;
|
||||
|
||||
enterNewEditList(false);
|
||||
|
||||
// One extra check for the end of a track
|
||||
if (endOfTrack())
|
||||
return;
|
||||
|
||||
// Now we're in the edit and need to figure out what frame we need
|
||||
while (getRateAdjustedFrameTime() < (uint32)time.totalNumberOfFrames()) {
|
||||
_curFrame++;
|
||||
if (_durationOverride >= 0) {
|
||||
_nextFrameStartTime += _durationOverride;
|
||||
_durationOverride = -1;
|
||||
} else {
|
||||
_nextFrameStartTime += getFrameDuration();
|
||||
}
|
||||
}
|
||||
|
||||
// All that's left is to figure out what our starting time is going to be
|
||||
// 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 (_holdNextFrameStartTime)
|
||||
_curFrame--;
|
||||
|
||||
// Handle the keyframe here
|
||||
int32 destinationFrame = _curFrame + 1;
|
||||
|
||||
assert(destinationFrame < (int32)_parent->frameCount);
|
||||
_curFrame = findKeyFrame(destinationFrame) - 1;
|
||||
while (_curFrame < destinationFrame - 1)
|
||||
bufferNextFrame();
|
||||
}
|
||||
|
||||
Common::Rational QuickTimeDecoder::VideoTrackHandler::getWidth() const {
|
||||
return Common::Rational(_parent->width) / _parent->scaleFactorX;
|
||||
}
|
||||
|
||||
Common::Rational QuickTimeDecoder::VideoTrackHandler::getHeight() const {
|
||||
return Common::Rational(_parent->height) / _parent->scaleFactorY;
|
||||
}
|
||||
|
||||
Graphics::PixelFormat QuickTimeDecoder::VideoTrackHandler::getPixelFormat() const {
|
||||
return ((VideoSampleDesc *)_parent->sampleDescs[0])->_videoCodec->getPixelFormat();
|
||||
}
|
||||
|
||||
uint32 QuickTimeDecoder::VideoTrackHandler::getRateAdjustedFrameTime() const {
|
||||
// Figure out what time the next frame is at taking the edit list rate into account
|
||||
uint32 convertedTime = (Common::Rational(_nextFrameStartTime - getCurEditTimeOffset()) / _parent->editList[_curEdit].mediaRate).toInt();
|
||||
return convertedTime + getCurEditTimeOffset();
|
||||
}
|
||||
|
||||
uint32 QuickTimeDecoder::VideoTrackHandler::getCurEditTimeOffset() const {
|
||||
// Need to convert to the track scale
|
||||
return _parent->editList[_curEdit].timeOffset * _parent->timeScale / _decoder->_timeScale;
|
||||
}
|
||||
|
||||
uint32 QuickTimeDecoder::VideoTrackHandler::getCurEditTrackDuration() const {
|
||||
// Need to convert to the track scale
|
||||
return _parent->editList[_curEdit].trackDuration * _parent->timeScale / _decoder->_timeScale;
|
||||
}
|
||||
|
||||
} // End of namespace Video
|
||||
|
@ -31,13 +31,13 @@
|
||||
#ifndef VIDEO_QT_DECODER_H
|
||||
#define VIDEO_QT_DECODER_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/rational.h"
|
||||
|
||||
#include "video/video_decoder.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/decoders/quicktime_intern.h"
|
||||
#include "common/scummsys.h"
|
||||
#include "common/rational.h"
|
||||
#include "graphics/pixelformat.h"
|
||||
|
||||
#include "video/video_decoder.h"
|
||||
|
||||
namespace Common {
|
||||
class Rational;
|
||||
@ -63,13 +63,13 @@ public:
|
||||
* Returns the width of the video
|
||||
* @return the width of the video
|
||||
*/
|
||||
uint16 getWidth() const;
|
||||
uint16 getWidth() const { return _width; }
|
||||
|
||||
/**
|
||||
* Returns the height of the video
|
||||
* @return the height of the video
|
||||
*/
|
||||
uint16 getHeight() const;
|
||||
uint16 getHeight() const { return _height; }
|
||||
|
||||
/**
|
||||
* Returns the amount of frames in the video
|
||||
@ -101,6 +101,8 @@ public:
|
||||
const byte *getPalette() { _dirtyPalette = false; return _palette; }
|
||||
bool hasDirtyPalette() const { return _dirtyPalette; }
|
||||
|
||||
int32 getCurFrame() const;
|
||||
|
||||
bool isVideoLoaded() const { return isOpen(); }
|
||||
const Graphics::Surface *decodeNextFrame();
|
||||
bool endOfVideo() const;
|
||||
@ -132,8 +134,6 @@ protected:
|
||||
Common::QuickTimeParser::SampleDesc *readSampleDesc(Track *track, uint32 format);
|
||||
|
||||
private:
|
||||
Common::SeekableReadStream *getNextFramePacket(uint32 &descId);
|
||||
uint32 getFrameDuration();
|
||||
void init();
|
||||
|
||||
void startAudio();
|
||||
@ -144,20 +144,108 @@ private:
|
||||
Audio::Timestamp _audioStartOffset;
|
||||
|
||||
Codec *createCodec(uint32 codecTag, byte bitsPerPixel);
|
||||
Codec *findDefaultVideoCodec() const;
|
||||
uint32 _nextFrameStartTime;
|
||||
int _videoTrackIndex;
|
||||
uint32 findKeyFrame(uint32 frame) const;
|
||||
|
||||
bool _dirtyPalette;
|
||||
const byte *_palette;
|
||||
bool _setStartTime;
|
||||
bool _needUpdate;
|
||||
|
||||
uint16 _width, _height;
|
||||
|
||||
Graphics::Surface *_scaledSurface;
|
||||
const Graphics::Surface *scaleSurface(const Graphics::Surface *frame);
|
||||
Common::Rational getScaleFactorX() const;
|
||||
Common::Rational getScaleFactorY() const;
|
||||
void scaleSurface(const Graphics::Surface *src, Graphics::Surface *dst,
|
||||
Common::Rational scaleFactorX, Common::Rational scaleFactorY);
|
||||
|
||||
void pauseVideoIntern(bool pause);
|
||||
bool endOfVideoTracks() const;
|
||||
|
||||
// The TrackHandler is a class that wraps around a QuickTime Track
|
||||
// and handles playback in this decoder class.
|
||||
class TrackHandler {
|
||||
public:
|
||||
TrackHandler(QuickTimeDecoder *decoder, Track *parent);
|
||||
virtual ~TrackHandler() {}
|
||||
|
||||
enum TrackType {
|
||||
kTrackTypeAudio,
|
||||
kTrackTypeVideo
|
||||
};
|
||||
|
||||
virtual TrackType getTrackType() const = 0;
|
||||
|
||||
virtual void seekToTime(Audio::Timestamp time) = 0;
|
||||
|
||||
virtual bool endOfTrack();
|
||||
|
||||
protected:
|
||||
uint32 _curEdit;
|
||||
QuickTimeDecoder *_decoder;
|
||||
Common::SeekableReadStream *_fd;
|
||||
Track *_parent;
|
||||
};
|
||||
|
||||
// The AudioTrackHandler is currently just a wrapper around some
|
||||
// QuickTimeDecoder functions. Eventually this can be made to
|
||||
// handle multiple audio tracks, but I haven't seen a video with
|
||||
// that yet.
|
||||
class AudioTrackHandler : public TrackHandler {
|
||||
public:
|
||||
AudioTrackHandler(QuickTimeDecoder *decoder, Track *parent);
|
||||
TrackType getTrackType() const { return kTrackTypeAudio; }
|
||||
|
||||
void updateBuffer();
|
||||
void seekToTime(Audio::Timestamp time);
|
||||
bool endOfTrack();
|
||||
};
|
||||
|
||||
// The VideoTrackHandler is the bridge between the time of playback
|
||||
// and the media for the given track. It calculates when to start
|
||||
// tracks and at what rate to play the media using the edit list.
|
||||
class VideoTrackHandler : public TrackHandler {
|
||||
public:
|
||||
VideoTrackHandler(QuickTimeDecoder *decoder, Track *parent);
|
||||
~VideoTrackHandler();
|
||||
|
||||
TrackType getTrackType() const { return kTrackTypeVideo; }
|
||||
|
||||
const Graphics::Surface *decodeNextFrame();
|
||||
|
||||
uint32 getNextFrameStartTime();
|
||||
|
||||
uint32 getFrameCount();
|
||||
|
||||
int32 getCurFrame() { return _curFrame; }
|
||||
|
||||
Graphics::PixelFormat getPixelFormat() const;
|
||||
|
||||
void seekToTime(Audio::Timestamp time);
|
||||
|
||||
Common::Rational getWidth() const;
|
||||
Common::Rational getHeight() const;
|
||||
|
||||
private:
|
||||
int32 _curFrame;
|
||||
uint32 _nextFrameStartTime;
|
||||
Graphics::Surface *_scaledSurface;
|
||||
bool _holdNextFrameStartTime;
|
||||
int32 _durationOverride;
|
||||
|
||||
Common::SeekableReadStream *getNextFramePacket(uint32 &descId);
|
||||
uint32 getFrameDuration();
|
||||
uint32 findKeyFrame(uint32 frame) const;
|
||||
void enterNewEditList(bool bufferFrames);
|
||||
const Graphics::Surface *bufferNextFrame();
|
||||
uint32 getRateAdjustedFrameTime() const;
|
||||
uint32 getCurEditTimeOffset() const;
|
||||
uint32 getCurEditTrackDuration() const;
|
||||
};
|
||||
|
||||
Common::Array<TrackHandler *> _handlers;
|
||||
VideoTrackHandler *_nextVideoTrack;
|
||||
VideoTrackHandler *findNextVideoTrack() const;
|
||||
|
||||
void freeAllTrackHandlers();
|
||||
};
|
||||
|
||||
} // End of namespace Video
|
||||
|
Loading…
x
Reference in New Issue
Block a user