VIDEO: Add support for QuickTime video track edit lists

This commit is contained in:
Matthew Hoops 2011-12-11 12:38:06 -05:00
parent 35a0fb089a
commit b367772b5f
5 changed files with 646 additions and 308 deletions

View File

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

View File

@ -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() {

View File

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

View File

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

View File

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