diff options
author | Matthew Hoops | 2011-12-11 12:38:06 -0500 |
---|---|---|
committer | Matthew Hoops | 2011-12-12 12:28:48 -0500 |
commit | b367772b5f6966ef99ea0914b8e10644b18652ea (patch) | |
tree | a17272af1db610d4924d191ca630108afcbf4b8b | |
parent | 35a0fb089a12991be52a14e02977202263a7dbee (diff) | |
download | scummvm-rg350-b367772b5f6966ef99ea0914b8e10644b18652ea.tar.gz scummvm-rg350-b367772b5f6966ef99ea0914b8e10644b18652ea.tar.bz2 scummvm-rg350-b367772b5f6966ef99ea0914b8e10644b18652ea.zip |
VIDEO: Add support for QuickTime video track edit lists
-rw-r--r-- | audio/decoders/quicktime.cpp | 7 | ||||
-rw-r--r-- | common/quicktime.cpp | 19 | ||||
-rw-r--r-- | common/quicktime.h | 2 | ||||
-rw-r--r-- | video/qt_decoder.cpp | 798 | ||||
-rw-r--r-- | video/qt_decoder.h | 114 |
5 files changed, 639 insertions, 301 deletions
diff --git a/audio/decoders/quicktime.cpp b/audio/decoders/quicktime.cpp index 8cf0305e88..e737bf8e10 100644 --- a/audio/decoders/quicktime.cpp +++ b/audio/decoders/quicktime.cpp @@ -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); } }; diff --git a/common/quicktime.cpp b/common/quicktime.cpp index 9ea8c229ea..e16d3f2652 100644 --- a/common/quicktime.cpp +++ b/common/quicktime.cpp @@ -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() { diff --git a/common/quicktime.h b/common/quicktime.h index e4c821e209..d7e2691c2b 100644 --- a/common/quicktime.h +++ b/common/quicktime.h @@ -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; diff --git a/video/qt_decoder.cpp b/video/qt_decoder.cpp index 058dd6b489..4a57ac405d 100644 --- a/video/qt_decoder.cpp +++ b/video/qt_decoder.cpp @@ -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; - - return (Common::Rational(_tracks[_videoTrackIndex]->width) / getScaleFactorX()).toInt(); -} - -uint16 QuickTimeDecoder::getHeight() const { - if (_videoTrackIndex < 0) - return 0; - - 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; +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 (_scaleFactorY * _tracks[_videoTrackIndex]->scaleFactorY); -} + int32 frame = -1; -uint32 QuickTimeDecoder::getFrameDuration() { - 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; - 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); - - // Stop all audio (for now) - stopAudio(); - - // 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; +uint32 QuickTimeDecoder::getFrameCount() const { + uint32 count = 0; - 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++; - } - } + for (uint32 i = 0; i < _handlers.size(); i++) + if (_handlers[i]->getTrackType() == TrackHandler::kTrackTypeVideo) + count += ((VideoTrackHandler *)_handlers[i])->getFrameCount(); - 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; + + 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(); + + if (time < bestTime) { + bestTime = time; + bestTrack = track; + num = i; + } + } + } - return ((VideoSampleDesc *)_tracks[_videoTrackIndex]->sampleDescs[0])->_videoCodec; + return bestTrack; } const Graphics::Surface *QuickTimeDecoder::decodeNextFrame() { - if (_videoTrackIndex < 0 || _curFrame >= (int32)getFrameCount() - 1) + if (!_nextVideoTrack) 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); + const Graphics::Surface *frame = _nextVideoTrack->decodeNextFrame(); - 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 (!_setStartTime) { + _startTime = g_system->getMillis(); + _setStartTime = true; + } - if (!entry->_videoCodec) - return 0; + _nextVideoTrack = findNextVideoTrack(); + _needUpdate = false; - const Graphics::Surface *frame = entry->_videoCodec->decodeImage(frameData); - delete frameData; + // 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(); - // 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 (_scaledSurface) { + scaleSurface(frame, _scaledSurface, _scaleFactorX, _scaleFactorY); + return _scaledSurface; } - return scaleSurface(frame); + return frame; } -const Graphics::Surface *QuickTimeDecoder::scaleSurface(const Graphics::Surface *frame) { - if (getScaleFactorX() == 1 && getScaleFactorY() == 1) - return frame; - - assert(_scaledSurface); - - 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); +void QuickTimeDecoder::scaleSurface(const Graphics::Surface *src, Graphics::Surface *dst, Common::Rational scaleFactorX, Common::Rational scaleFactorY) { + assert(src && dst); - return _scaledSurface; + 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; + + // TODO: Add support for rate modification + + uint32 elapsedTime = getElapsedTime(); - return nextFrameStartTime - elapsedTime; + 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 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(); + + _handlers.push_back(new VideoTrackHandler(this, _tracks[i])); + } } - // Initialize video, if present - if (_videoTrackIndex >= 0) { - for (uint32 i = 0; i < _tracks[_videoTrackIndex]->sampleDescs.size(); i++) - ((VideoSampleDesc *)_tracks[_videoTrackIndex]->sampleDescs[i])->initCodec(); + // Prepare the first video track + _nextVideoTrack = findNextVideoTrack(); - if (getScaleFactorX() != 1 || getScaleFactorY() != 1) { + 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; - - // 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 < _tracks[_videoTrackIndex]->chunkCount; i++) { - if (sampleToChunkIndex < _tracks[_videoTrackIndex]->sampleToChunkCount && i >= _tracks[_videoTrackIndex]->sampleToChunk[sampleToChunkIndex].first) - sampleToChunkIndex++; - - totalSampleCount += _tracks[_videoTrackIndex]->sampleToChunk[sampleToChunkIndex - 1].count; - - 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]); +void QuickTimeDecoder::freeAllTrackHandlers() { + for (uint32 i = 0; i < _handlers.size(); i++) + delete _handlers[i]; - // 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]); - } + _handlers.clear(); +} - // 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()]); +void QuickTimeDecoder::seekToTime(Audio::Timestamp time) { + // Sets all tracks to this time + for (uint32 i = 0; i < _handlers.size(); i++) + _handlers[i]->seekToTime(time); - if (_tracks[_videoTrackIndex]->sampleSize != 0) - return _fd->readStream(_tracks[_videoTrackIndex]->sampleSize); + // Reset our start time + _startTime = g_system->getMillis() - time.msecs(); + _setStartTime = true; + resetPauseStartTime(); - return _fd->readStream(_tracks[_videoTrackIndex]->sampleSizes[getCurFrame()]); + // Reset the next video track too + _nextVideoTrack = findNextVideoTrack(); + _needUpdate = _nextVideoTrack != 0; } void QuickTimeDecoder::updateAudioBuffer() { - if (!_audStream) - return; - - uint32 numberOfChunksNeeded = 0; - - 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; - } + // 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(); +} - // Add a couple extra to ensure we don't underrun - numberOfChunksNeeded += 3; - } +Graphics::PixelFormat QuickTimeDecoder::getPixelFormat() const { + if (_nextVideoTrack) + return _nextVideoTrack->getPixelFormat(); - // 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 diff --git a/video/qt_decoder.h b/video/qt_decoder.h index b51fd043e7..b2d7153f42 100644 --- a/video/qt_decoder.h +++ b/video/qt_decoder.h @@ -31,14 +31,14 @@ #ifndef VIDEO_QT_DECODER_H #define VIDEO_QT_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" -#include "audio/mixer.h" -#include "audio/decoders/quicktime_intern.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 |