diff options
-rw-r--r-- | common/module.mk | 1 | ||||
-rw-r--r-- | common/quicktime.cpp | 774 | ||||
-rw-r--r-- | common/quicktime.h | 202 | ||||
-rw-r--r-- | video/qt_decoder.cpp | 952 | ||||
-rw-r--r-- | video/qt_decoder.h | 139 |
5 files changed, 1123 insertions, 945 deletions
diff --git a/common/module.mk b/common/module.mk index a57de6a4b8..5f6a529595 100644 --- a/common/module.mk +++ b/common/module.mk @@ -17,6 +17,7 @@ MODULE_OBJS := \ memorypool.o \ md5.o \ mutex.o \ + quicktime.o \ random.o \ rational.o \ str.o \ diff --git a/common/quicktime.cpp b/common/quicktime.cpp new file mode 100644 index 0000000000..d40f279f15 --- /dev/null +++ b/common/quicktime.cpp @@ -0,0 +1,774 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// +// Heavily based on ffmpeg code. +// +// Copyright (c) 2001 Fabrice Bellard. +// First version by Francois Revol revol@free.fr +// Seek function by Gael Chardon gael.dev@4now.net +// + +#include "common/debug.h" +#include "common/endian.h" +#include "common/macresman.h" +#include "common/memstream.h" +#include "common/quicktime.h" +#include "common/util.h" +#include "common/zlib.h" + +namespace Common { + +//////////////////////////////////////////// +// QuickTimeParser +//////////////////////////////////////////// + +QuickTimeParser::QuickTimeParser() { + _beginOffset = 0; + _numStreams = 0; + _fd = 0; + _scaleFactorX = 1; + _scaleFactorY = 1; + _resFork = new Common::MacResManager(); + + initParseTable(); +} + +QuickTimeParser::~QuickTimeParser() { + close(); + delete _resFork; +} + +bool QuickTimeParser::loadFile(const Common::String &filename) { + if (!_resFork->open(filename) || !_resFork->hasDataFork()) + return false; + + _foundMOOV = false; + _numStreams = 0; + + MOVatom atom = { 0, 0, 0xffffffff }; + + if (_resFork->hasResFork()) { + // Search for a 'moov' resource + Common::MacResIDArray idArray = _resFork->getResIDArray(MKID_BE('moov')); + + if (!idArray.empty()) + _fd = _resFork->getResource(MKID_BE('moov'), idArray[0]); + + if (_fd) { + atom.size = _fd->size(); + if (readDefault(atom) < 0 || !_foundMOOV) + return false; + } + delete _fd; + + atom.type = 0; + atom.offset = 0; + atom.size = 0xffffffff; + } + + _fd = _resFork->getDataFork(); + + if (readDefault(atom) < 0 || !_foundMOOV) + return false; + + init(); + return true; +} + +bool QuickTimeParser::loadStream(Common::SeekableReadStream *stream) { + _fd = stream; + _foundMOOV = false; + _numStreams = 0; + + MOVatom atom = { 0, 0, 0xffffffff }; + + if (readDefault(atom) < 0 || !_foundMOOV) { + _fd = 0; + return false; + } + + init(); + return true; +} + +void QuickTimeParser::init() { + // Remove unknown/unhandled streams + for (uint32 i = 0; i < _numStreams;) { + if (_streams[i]->codec_type == CODEC_TYPE_MOV_OTHER) { + delete _streams[i]; + for (uint32 j = i + 1; j < _numStreams; j++) + _streams[j - 1] = _streams[j]; + _numStreams--; + } else + i++; + } + + // Adjust time/duration + for (uint32 i = 0; i < _numStreams; i++) { + MOVStreamContext *sc = _streams[i]; + + if (!sc->time_rate) + sc->time_rate = 1; + + if (!sc->time_scale) + sc->time_scale = _timeScale; + + sc->duration /= sc->time_rate; + } +} + +void QuickTimeParser::initParseTable() { + static const ParseTable p[] = { + { &QuickTimeParser::readDefault, MKID_BE('dinf') }, + { &QuickTimeParser::readLeaf, MKID_BE('dref') }, + { &QuickTimeParser::readDefault, MKID_BE('edts') }, + { &QuickTimeParser::readELST, MKID_BE('elst') }, + { &QuickTimeParser::readHDLR, MKID_BE('hdlr') }, + { &QuickTimeParser::readDefault, MKID_BE('mdat') }, + { &QuickTimeParser::readMDHD, MKID_BE('mdhd') }, + { &QuickTimeParser::readDefault, MKID_BE('mdia') }, + { &QuickTimeParser::readDefault, MKID_BE('minf') }, + { &QuickTimeParser::readMOOV, MKID_BE('moov') }, + { &QuickTimeParser::readMVHD, MKID_BE('mvhd') }, + { &QuickTimeParser::readLeaf, MKID_BE('smhd') }, + { &QuickTimeParser::readDefault, MKID_BE('stbl') }, + { &QuickTimeParser::readSTCO, MKID_BE('stco') }, + { &QuickTimeParser::readSTSC, MKID_BE('stsc') }, + { &QuickTimeParser::readSTSD, MKID_BE('stsd') }, + { &QuickTimeParser::readSTSS, MKID_BE('stss') }, + { &QuickTimeParser::readSTSZ, MKID_BE('stsz') }, + { &QuickTimeParser::readSTTS, MKID_BE('stts') }, + { &QuickTimeParser::readTKHD, MKID_BE('tkhd') }, + { &QuickTimeParser::readTRAK, MKID_BE('trak') }, + { &QuickTimeParser::readLeaf, MKID_BE('udta') }, + { &QuickTimeParser::readLeaf, MKID_BE('vmhd') }, + { &QuickTimeParser::readCMOV, MKID_BE('cmov') }, + { &QuickTimeParser::readWAVE, MKID_BE('wave') }, + { 0, 0 } + }; + + _parseTable = p; +} + +int QuickTimeParser::readDefault(MOVatom atom) { + uint32 total_size = 0; + MOVatom a; + int err = 0; + + a.offset = atom.offset; + + while(((total_size + 8) < atom.size) && !_fd->eos() && _fd->pos() < _fd->size() && !err) { + a.size = atom.size; + a.type = 0; + + if (atom.size >= 8) { + a.size = _fd->readUint32BE(); + a.type = _fd->readUint32BE(); + + // Some QuickTime videos with resource forks have mdat chunks + // that are of size 0. Adjust it so it's the correct size. + if (a.type == MKID_BE('mdat') && a.size == 0) + a.size = _fd->size(); + } + + total_size += 8; + a.offset += 8; + debug(4, "type: %08x %.4s sz: %x %x %x", a.type, tag2str(a.type), a.size, atom.size, total_size); + + if (a.size == 1) { // 64 bit extended size + warning("64 bit extended size is not supported in QuickTime"); + return -1; + } + + if (a.size == 0) { + a.size = atom.size - total_size; + if (a.size <= 8) + break; + } + + uint32 i = 0; + + for (; _parseTable[i].type != 0 && _parseTable[i].type != a.type; i++) + ; // Empty + + if (a.size < 8) + break; + + a.size -= 8; + + if (_parseTable[i].type == 0) { // skip leaf atoms data + debug(0, ">>> Skipped [%s]", tag2str(a.type)); + + _fd->seek(a.size, SEEK_CUR); + } else { + uint32 start_pos = _fd->pos(); + err = (this->*_parseTable[i].func)(a); + + uint32 left = a.size - _fd->pos() + start_pos; + + if (left > 0) // skip garbage at atom end + _fd->seek(left, SEEK_CUR); + } + + a.offset += a.size; + total_size += a.size; + } + + if (!err && total_size < atom.size) + _fd->seek(atom.size - total_size, SEEK_SET); + + return err; +} + +int QuickTimeParser::readLeaf(MOVatom atom) { + if (atom.size > 1) + _fd->seek(atom.size, SEEK_SET); + + return 0; +} + +int QuickTimeParser::readMOOV(MOVatom atom) { + if (readDefault(atom) < 0) + return -1; + + // We parsed the 'moov' atom, so we don't need anything else + _foundMOOV = true; + return 1; +} + +int QuickTimeParser::readCMOV(MOVatom atom) { +#ifdef USE_ZLIB + // Read in the dcom atom + _fd->readUint32BE(); + if (_fd->readUint32BE() != MKID_BE('dcom')) + return -1; + if (_fd->readUint32BE() != MKID_BE('zlib')) { + warning("Unknown cmov compression type"); + return -1; + } + + // Read in the cmvd atom + uint32 compressedSize = _fd->readUint32BE() - 12; + if (_fd->readUint32BE() != MKID_BE('cmvd')) + return -1; + uint32 uncompressedSize = _fd->readUint32BE(); + + // Read in data + byte *compressedData = (byte *)malloc(compressedSize); + _fd->read(compressedData, compressedSize); + + // Create uncompressed stream + byte *uncompressedData = (byte *)malloc(uncompressedSize); + + // Uncompress the data + unsigned long dstLen = uncompressedSize; + if (!Common::uncompress(uncompressedData, &dstLen, compressedData, compressedSize)) { + warning ("Could not uncompress cmov chunk"); + free(compressedData); + free(uncompressedData); + return -1; + } + + // Load data into a new MemoryReadStream and assign _fd to be that + Common::SeekableReadStream *oldStream = _fd; + _fd = new Common::MemoryReadStream(uncompressedData, uncompressedSize, DisposeAfterUse::YES); + + // Read the contents of the uncompressed data + MOVatom a = { MKID_BE('moov'), 0, uncompressedSize }; + int err = readDefault(a); + + // Assign the file handle back to the original handle + free(compressedData); + delete _fd; + _fd = oldStream; + + return err; +#else + warning ("zlib not found, cannot read QuickTime cmov atom"); + return -1; +#endif +} + +int QuickTimeParser::readMVHD(MOVatom atom) { + byte version = _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + if (version == 1) { + warning("QuickTime version 1"); + _fd->readUint32BE(); _fd->readUint32BE(); + _fd->readUint32BE(); _fd->readUint32BE(); + } else { + _fd->readUint32BE(); // creation time + _fd->readUint32BE(); // modification time + } + + _timeScale = _fd->readUint32BE(); // time scale + debug(0, "time scale = %i\n", _timeScale); + + // duration + _duration = (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); + _fd->readUint32BE(); // preferred scale + + _fd->readUint16BE(); // preferred volume + + _fd->seek(10, SEEK_CUR); // reserved + + // We only need two values from the movie display matrix. Most of the values are just + // skipped. xMod and yMod are 16:16 fixed point numbers, the last part of the 3x3 matrix + // is 2:30. + uint32 xMod = _fd->readUint32BE(); + _fd->skip(12); + uint32 yMod = _fd->readUint32BE(); + _fd->skip(16); + + _scaleFactorX = Common::Rational(0x10000, xMod); + _scaleFactorY = Common::Rational(0x10000, yMod); + + _scaleFactorX.debugPrint(1, "readMVHD(): scaleFactorX ="); + _scaleFactorY.debugPrint(1, "readMVHD(): scaleFactorY ="); + + _fd->readUint32BE(); // preview time + _fd->readUint32BE(); // preview duration + _fd->readUint32BE(); // poster time + _fd->readUint32BE(); // selection time + _fd->readUint32BE(); // selection duration + _fd->readUint32BE(); // current time + _fd->readUint32BE(); // next track ID + + return 0; +} + +int QuickTimeParser::readTRAK(MOVatom atom) { + MOVStreamContext *sc = new MOVStreamContext(); + + if (!sc) + return -1; + + sc->codec_type = CODEC_TYPE_MOV_OTHER; + sc->start_time = 0; // XXX: check + _streams[_numStreams++] = sc; + + return readDefault(atom); +} + +int QuickTimeParser::readTKHD(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + byte version = _fd->readByte(); + + _fd->readByte(); _fd->readByte(); + _fd->readByte(); // flags + // + //MOV_TRACK_ENABLED 0x0001 + //MOV_TRACK_IN_MOVIE 0x0002 + //MOV_TRACK_IN_PREVIEW 0x0004 + //MOV_TRACK_IN_POSTER 0x0008 + // + + if (version == 1) { + _fd->readUint32BE(); _fd->readUint32BE(); + _fd->readUint32BE(); _fd->readUint32BE(); + } else { + _fd->readUint32BE(); // creation time + _fd->readUint32BE(); // modification time + } + + /* st->id = */_fd->readUint32BE(); // track id (NOT 0 !) + _fd->readUint32BE(); // reserved + //st->start_time = 0; // check + (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); // highlevel (considering edits) duration in movie timebase + _fd->readUint32BE(); // reserved + _fd->readUint32BE(); // reserved + + _fd->readUint16BE(); // layer + _fd->readUint16BE(); // alternate group + _fd->readUint16BE(); // volume + _fd->readUint16BE(); // reserved + + // We only need the two values from the displacement matrix for a track. + // See readMVHD() for more information. + uint32 xMod = _fd->readUint32BE(); + _fd->skip(12); + uint32 yMod = _fd->readUint32BE(); + _fd->skip(16); + + st->scaleFactorX = Common::Rational(0x10000, xMod); + st->scaleFactorY = Common::Rational(0x10000, yMod); + + st->scaleFactorX.debugPrint(1, "readTKHD(): scaleFactorX ="); + st->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 + + return 0; +} + +// edit list atom +int QuickTimeParser::readELST(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->editCount = _fd->readUint32BE(); + st->editList = new EditListEntry[st->editCount]; + + debug(2, "Track %d edit list count: %d", _numStreams - 1, st->editCount); + + for (uint32 i = 0; i < st->editCount; i++){ + st->editList[i].trackDuration = _fd->readUint32BE(); + st->editList[i].mediaTime = _fd->readSint32BE(); + st->editList[i].mediaRate = Common::Rational(_fd->readUint32BE(), 0x10000); + debugN(3, "\tDuration = %d, Media Time = %d, ", st->editList[i].trackDuration, st->editList[i].mediaTime); + st->editList[i].mediaRate.debugPrint(3, "Media Rate ="); + } + + if (st->editCount != 1) + warning("Multiple edit list entries. Things may go awry"); + + return 0; +} + +int QuickTimeParser::readHDLR(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + // component type + uint32 ctype = _fd->readUint32BE(); + uint32 type = _fd->readUint32BE(); // component subtype + + debug(0, "ctype= %s (0x%08lx)", tag2str(ctype), (long)ctype); + debug(0, "stype= %s", tag2str(type)); + + if (ctype == MKID_BE('mhlr')) // MOV + debug(0, "MOV detected"); + else if (ctype == 0) + debug(0, "MPEG-4 detected"); + + if (type == MKID_BE('vide')) + st->codec_type = CODEC_TYPE_VIDEO; + else if (type == MKID_BE('soun')) + st->codec_type = CODEC_TYPE_AUDIO; + + _fd->readUint32BE(); // component manufacture + _fd->readUint32BE(); // component flags + _fd->readUint32BE(); // component flags mask + + if (atom.size <= 24) + return 0; // nothing left to read + + // .mov: PASCAL string + byte len = _fd->readByte(); + _fd->seek(len, SEEK_CUR); + + _fd->seek(atom.size - (_fd->pos() - atom.offset), SEEK_CUR); + + return 0; +} + +int QuickTimeParser::readMDHD(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + byte version = _fd->readByte(); + + if (version > 1) + return 1; // unsupported + + _fd->readByte(); _fd->readByte(); + _fd->readByte(); // flags + + if (version == 1) { + _fd->readUint32BE(); _fd->readUint32BE(); + _fd->readUint32BE(); _fd->readUint32BE(); + } else { + _fd->readUint32BE(); // creation time + _fd->readUint32BE(); // modification time + } + + st->time_scale = _fd->readUint32BE(); + st->duration = (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); // duration + + _fd->readUint16BE(); // language + _fd->readUint16BE(); // quality + + return 0; +} + +int QuickTimeParser::readSTSD(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + uint32 entryCount = _fd->readUint32BE(); + st->sampleDescs.resize(entryCount); + + for (uint32 i = 0; i < entryCount; i++) { // Parsing Sample description table + MOVatom a = { 0, 0, 0 }; + uint32 start_pos = _fd->pos(); + int size = _fd->readUint32BE(); // size + uint32 format = _fd->readUint32BE(); // data format + + _fd->readUint32BE(); // reserved + _fd->readUint16BE(); // reserved + _fd->readUint16BE(); // index + + st->sampleDescs[i] = readSampleDesc(st, format); + + debug(0, "size=%d 4CC= %s codec_type=%d", size, tag2str(format), st->codec_type); + + if (!st->sampleDescs[i]) { + // other codec type, just skip (rtp, mp4s, tmcd ...) + _fd->seek(size - (_fd->pos() - start_pos), SEEK_CUR); + } + + // this will read extra atoms at the end (wave, alac, damr, avcC, SMI ...) + a.size = size - (_fd->pos() - start_pos); + if (a.size > 8) + readDefault(a); + else if (a.size > 0) + _fd->seek(a.size, SEEK_CUR); + } + + return 0; +} + +int QuickTimeParser::readSTSC(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->sample_to_chunk_sz = _fd->readUint32BE(); + + debug(0, "track[%i].stsc.entries = %i", _numStreams - 1, st->sample_to_chunk_sz); + + st->sample_to_chunk = new MOVstsc[st->sample_to_chunk_sz]; + + if (!st->sample_to_chunk) + return -1; + + for (uint32 i = 0; i < st->sample_to_chunk_sz; i++) { + st->sample_to_chunk[i].first = _fd->readUint32BE() - 1; + st->sample_to_chunk[i].count = _fd->readUint32BE(); + st->sample_to_chunk[i].id = _fd->readUint32BE(); + //warning("Sample to Chunk[%d]: First = %d, Count = %d", i, st->sample_to_chunk[i].first, st->sample_to_chunk[i].count); + } + + return 0; +} + +int QuickTimeParser::readSTSS(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->keyframe_count = _fd->readUint32BE(); + + debug(0, "keyframe_count = %d", st->keyframe_count); + + st->keyframes = new uint32[st->keyframe_count]; + + if (!st->keyframes) + return -1; + + for (uint32 i = 0; i < st->keyframe_count; i++) { + st->keyframes[i] = _fd->readUint32BE() - 1; // Adjust here, the frames are based on 1 + debug(6, "keyframes[%d] = %d", i, st->keyframes[i]); + + } + return 0; +} + +int QuickTimeParser::readSTSZ(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->sample_size = _fd->readUint32BE(); + st->sample_count = _fd->readUint32BE(); + + debug(5, "sample_size = %d sample_count = %d", st->sample_size, st->sample_count); + + if (st->sample_size) + return 0; // there isn't any table following + + st->sample_sizes = new uint32[st->sample_count]; + + if (!st->sample_sizes) + return -1; + + for(uint32 i = 0; i < st->sample_count; i++) { + st->sample_sizes[i] = _fd->readUint32BE(); + debug(6, "sample_sizes[%d] = %d", i, st->sample_sizes[i]); + } + + return 0; +} + +static uint32 ff_gcd(uint32 a, uint32 b) { + return b ? ff_gcd(b, a % b) : a; +} + +int QuickTimeParser::readSTTS(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + uint32 duration = 0; + uint32 total_sample_count = 0; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->stts_count = _fd->readUint32BE(); + st->stts_data = new MOVstts[st->stts_count]; + + debug(0, "track[%i].stts.entries = %i", _numStreams - 1, st->stts_count); + + st->time_rate = 0; + + for (int32 i = 0; i < st->stts_count; i++) { + int sample_duration; + int sample_count; + + sample_count = _fd->readUint32BE(); + sample_duration = _fd->readUint32BE(); + st->stts_data[i].count = sample_count; + st->stts_data[i].duration = sample_duration; + + st->time_rate = ff_gcd(st->time_rate, sample_duration); + + debug(0, "sample_count=%d, sample_duration=%d", sample_count, sample_duration); + + duration += sample_duration * sample_count; + total_sample_count += sample_count; + } + + st->nb_frames = total_sample_count; + + if (duration) + st->duration = duration; + + return 0; +} + +int QuickTimeParser::readSTCO(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->chunk_count = _fd->readUint32BE(); + st->chunk_offsets = new uint32[st->chunk_count]; + + if (!st->chunk_offsets) + return -1; + + for (uint32 i = 0; i < st->chunk_count; i++) { + // WORKAROUND/HACK: The offsets in Riven videos (ones inside the Mohawk archives themselves) + // have offsets relative to the archive and not the video. This is quite nasty. We subtract + // the initial offset of the stream to get the correct value inside of the stream. + st->chunk_offsets[i] = _fd->readUint32BE() - _beginOffset; + } + + return 0; +} + +int QuickTimeParser::readWAVE(MOVatom atom) { + if (_numStreams < 1) + return 0; + + MOVStreamContext *st = _streams[_numStreams - 1]; + + if (atom.size > (1 << 30)) + return -1; + + if (st->sampleDescs[0]->codecTag == MKID_BE('QDM2')) // Read extradata for QDM2 + st->extradata = _fd->readStream(atom.size - 8); + else if (atom.size > 8) + return readDefault(atom); + else + _fd->skip(atom.size); + + return 0; +} + +void QuickTimeParser::close() { + for (uint32 i = 0; i < _numStreams; i++) + delete _streams[i]; + + _numStreams = 0; + + delete _fd; + _fd = 0; +} + +QuickTimeParser::SampleDesc::SampleDesc() { + codecTag = 0; + bitsPerSample = 0; +} + +QuickTimeParser::MOVStreamContext::MOVStreamContext() { + chunk_count = 0; + chunk_offsets = 0; + stts_count = 0; + stts_data = 0; + sample_to_chunk_sz = 0; + sample_to_chunk = 0; + sample_size = 0; + sample_count = 0; + sample_sizes = 0; + keyframe_count = 0; + keyframes = 0; + time_scale = 0; + time_rate = 0; + width = 0; + height = 0; + codec_type = CODEC_TYPE_MOV_OTHER; + editCount = 0; + editList = 0; + extradata = 0; + nb_frames = 0; + duration = 0; + start_time = 0; +} + +QuickTimeParser::MOVStreamContext::~MOVStreamContext() { + delete[] chunk_offsets; + delete[] stts_data; + delete[] sample_to_chunk; + delete[] sample_sizes; + delete[] keyframes; + delete[] editList; + delete extradata; + + for (uint32 i = 0; i < sampleDescs.size(); i++) + delete sampleDescs[i]; +} + +} // End of namespace Video diff --git a/common/quicktime.h b/common/quicktime.h new file mode 100644 index 0000000000..7770860507 --- /dev/null +++ b/common/quicktime.h @@ -0,0 +1,202 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// +// Heavily based on ffmpeg code. +// +// Copyright (c) 2001 Fabrice Bellard. +// First version by Francois Revol revol@free.fr +// Seek function by Gael Chardon gael.dev@4now.net +// + +#ifndef COMMON_QUICKTIME_H +#define COMMON_QUICKTIME_H + +#include "common/array.h" +#include "common/scummsys.h" +#include "common/stream.h" +#include "common/rational.h" + +namespace Common { + class MacResManager; + +/** + * Parser for QuickTime files. + * + * File parser used in engines: + * - mohawk + * - sci + */ +class QuickTimeParser { +public: + QuickTimeParser(); + virtual ~QuickTimeParser(); + + /** + * Load a QuickTime file + * @param filename the filename to load + */ + bool loadFile(const Common::String &filename); + + /** + * Load a QuickTime file from a SeekableReadStream + * @param stream the stream to load + */ + bool loadStream(Common::SeekableReadStream *stream); + + /** + * Close a QuickTime file + */ + void close(); + + /** + * Set the beginning offset of the video so we can modify the offsets in the stco + * atom of videos inside the Mohawk archives + * @param the beginning offset of the video + */ + void setChunkBeginOffset(uint32 offset) { _beginOffset = offset; } + + bool isOpen() const { return _fd != 0; } + +protected: + // This is the file handle from which data is read from. It can be the actual file handle or a decompressed stream. + Common::SeekableReadStream *_fd; + + struct MOVatom { + uint32 type; + uint32 offset; + uint32 size; + }; + + struct ParseTable { + int (QuickTimeParser::*func)(MOVatom atom); + uint32 type; + }; + + struct MOVstts { + int count; + int duration; + }; + + struct MOVstsc { + uint32 first; + uint32 count; + uint32 id; + }; + + struct EditListEntry { + uint32 trackDuration; + int32 mediaTime; + Common::Rational mediaRate; + }; + + struct SampleDesc { + SampleDesc(); + virtual ~SampleDesc() {} + + uint32 codecTag; + uint16 bitsPerSample; + }; + + enum CodecType { + CODEC_TYPE_MOV_OTHER, + CODEC_TYPE_VIDEO, + CODEC_TYPE_AUDIO + }; + + struct MOVStreamContext { + MOVStreamContext(); + ~MOVStreamContext(); + + uint32 chunk_count; + uint32 *chunk_offsets; + int stts_count; + MOVstts *stts_data; + uint32 sample_to_chunk_sz; + MOVstsc *sample_to_chunk; + uint32 sample_size; + uint32 sample_count; + uint32 *sample_sizes; + uint32 keyframe_count; + uint32 *keyframes; + int32 time_scale; + int time_rate; + + uint16 width; + uint16 height; + CodecType codec_type; + + Common::Array<SampleDesc *> sampleDescs; + + uint32 editCount; + EditListEntry *editList; + + Common::SeekableReadStream *extradata; + + uint32 nb_frames; + uint32 duration; + uint32 start_time; + Common::Rational scaleFactorX; + Common::Rational scaleFactorY; + }; + + virtual SampleDesc *readSampleDesc(MOVStreamContext *st, uint32 format) = 0; + + const ParseTable *_parseTable; + bool _foundMOOV; + uint32 _timeScale; + uint32 _duration; + uint32 _numStreams; + Common::Rational _scaleFactorX; + Common::Rational _scaleFactorY; + MOVStreamContext *_streams[20]; + uint32 _beginOffset; + Common::MacResManager *_resFork; + + void initParseTable(); + void init(); + + int readDefault(MOVatom atom); + int readLeaf(MOVatom atom); + int readELST(MOVatom atom); + int readHDLR(MOVatom atom); + int readMDHD(MOVatom atom); + int readMOOV(MOVatom atom); + int readMVHD(MOVatom atom); + int readTKHD(MOVatom atom); + int readTRAK(MOVatom atom); + int readSTCO(MOVatom atom); + int readSTSC(MOVatom atom); + int readSTSD(MOVatom atom); + int readSTSS(MOVatom atom); + int readSTSZ(MOVatom atom); + int readSTTS(MOVatom atom); + int readCMOV(MOVatom atom); + int readWAVE(MOVatom atom); +}; + +} // End of namespace Common + +#endif diff --git a/video/qt_decoder.cpp b/video/qt_decoder.cpp index 4c2374f894..fc91fd1f44 100644 --- a/video/qt_decoder.cpp +++ b/video/qt_decoder.cpp @@ -35,10 +35,8 @@ #include "common/debug.h" #include "common/endian.h" -#include "common/macresman.h" #include "common/memstream.h" #include "common/util.h" -#include "common/zlib.h" // Audio codecs #include "audio/decoders/adpcm.h" @@ -61,25 +59,16 @@ namespace Video { QuickTimeDecoder::QuickTimeDecoder() { _audStream = NULL; - _beginOffset = 0; _curFrame = -1; _startTime = _nextFrameStartTime = 0; _audHandle = Audio::SoundHandle(); - _numStreams = 0; - _fd = 0; _scaledSurface = 0; - _scaleFactorX = 1; - _scaleFactorY = 1; _dirtyPalette = false; - _resFork = new Common::MacResManager(); _palette = 0; - - initParseTable(); } QuickTimeDecoder::~QuickTimeDecoder() { close(); - delete _resFork; } uint16 QuickTimeDecoder::getWidth() const { @@ -412,89 +401,35 @@ uint32 QuickTimeDecoder::getTimeToNextFrame() const { } bool QuickTimeDecoder::loadFile(const Common::String &filename) { - if (!_resFork->open(filename) || !_resFork->hasDataFork()) + if (!Common::QuickTimeParser::loadFile(filename)) return false; - _foundMOOV = false; - _numStreams = 0; _videoStreamIndex = _audioStreamIndex = -1; _startTime = 0; - - MOVatom atom = { 0, 0, 0xffffffff }; - - if (_resFork->hasResFork()) { - // Search for a 'moov' resource - Common::MacResIDArray idArray = _resFork->getResIDArray(MKID_BE('moov')); - - if (!idArray.empty()) - _fd = _resFork->getResource(MKID_BE('moov'), idArray[0]); - - if (_fd) { - atom.size = _fd->size(); - if (readDefault(atom) < 0 || !_foundMOOV) - return false; - } - delete _fd; - - atom.type = 0; - atom.offset = 0; - atom.size = 0xffffffff; - } - - _fd = _resFork->getDataFork(); - - if (readDefault(atom) < 0 || !_foundMOOV) - return false; - init(); + return true; } bool QuickTimeDecoder::loadStream(Common::SeekableReadStream *stream) { - _fd = stream; - _foundMOOV = false; - _numStreams = 0; - _videoStreamIndex = _audioStreamIndex = -1; - _startTime = 0; - - MOVatom atom = { 0, 0, 0xffffffff }; - - if (readDefault(atom) < 0 || !_foundMOOV) { - _fd = 0; + if (!Common::QuickTimeParser::loadStream(stream)) return false; - } + _videoStreamIndex = _audioStreamIndex = -1; + _startTime = 0; init(); + return true; } void QuickTimeDecoder::init() { - // Remove non-Video/Audio streams - for (uint32 i = 0; i < _numStreams;) { - if (_streams[i]->codec_type == CODEC_TYPE_MOV_OTHER) { - delete _streams[i]; - for (uint32 j = i + 1; j < _numStreams; j++) - _streams[j - 1] = _streams[j]; - _numStreams--; - } else - i++; - } + Common::QuickTimeParser::init(); - // Adjust time/duration + // Find audio/video streams for (uint32 i = 0; i < _numStreams; i++) { - MOVStreamContext *sc = _streams[i]; - - if (!sc->time_rate) - sc->time_rate = 1; - - if (!sc->time_scale) - sc->time_scale = _timeScale; - - sc->duration /= sc->time_rate; - - if (sc->codec_type == CODEC_TYPE_VIDEO && _videoStreamIndex < 0) + if (_streams[i]->codec_type == CODEC_TYPE_VIDEO && _videoStreamIndex < 0) _videoStreamIndex = i; - else if (sc->codec_type == CODEC_TYPE_AUDIO && _audioStreamIndex < 0) + else if (_streams[i]->codec_type == CODEC_TYPE_AUDIO && _audioStreamIndex < 0) _audioStreamIndex = i; } @@ -531,729 +466,154 @@ void QuickTimeDecoder::init() { } } -void QuickTimeDecoder::initParseTable() { - static const ParseTable p[] = { - { &QuickTimeDecoder::readDefault, MKID_BE('dinf') }, - { &QuickTimeDecoder::readLeaf, MKID_BE('dref') }, - { &QuickTimeDecoder::readDefault, MKID_BE('edts') }, - { &QuickTimeDecoder::readELST, MKID_BE('elst') }, - { &QuickTimeDecoder::readHDLR, MKID_BE('hdlr') }, - { &QuickTimeDecoder::readDefault, MKID_BE('mdat') }, - { &QuickTimeDecoder::readMDHD, MKID_BE('mdhd') }, - { &QuickTimeDecoder::readDefault, MKID_BE('mdia') }, - { &QuickTimeDecoder::readDefault, MKID_BE('minf') }, - { &QuickTimeDecoder::readMOOV, MKID_BE('moov') }, - { &QuickTimeDecoder::readMVHD, MKID_BE('mvhd') }, - { &QuickTimeDecoder::readLeaf, MKID_BE('smhd') }, - { &QuickTimeDecoder::readDefault, MKID_BE('stbl') }, - { &QuickTimeDecoder::readSTCO, MKID_BE('stco') }, - { &QuickTimeDecoder::readSTSC, MKID_BE('stsc') }, - { &QuickTimeDecoder::readSTSD, MKID_BE('stsd') }, - { &QuickTimeDecoder::readSTSS, MKID_BE('stss') }, - { &QuickTimeDecoder::readSTSZ, MKID_BE('stsz') }, - { &QuickTimeDecoder::readSTTS, MKID_BE('stts') }, - { &QuickTimeDecoder::readTKHD, MKID_BE('tkhd') }, - { &QuickTimeDecoder::readTRAK, MKID_BE('trak') }, - { &QuickTimeDecoder::readLeaf, MKID_BE('udta') }, - { &QuickTimeDecoder::readLeaf, MKID_BE('vmhd') }, - { &QuickTimeDecoder::readCMOV, MKID_BE('cmov') }, - { &QuickTimeDecoder::readWAVE, MKID_BE('wave') }, - { 0, 0 } - }; - - _parseTable = p; -} - -int QuickTimeDecoder::readDefault(MOVatom atom) { - uint32 total_size = 0; - MOVatom a; - int err = 0; - - a.offset = atom.offset; +Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(MOVStreamContext *st, uint32 format) { + if (st->codec_type == CODEC_TYPE_VIDEO) { + debug(0, "Video Codec FourCC: \'%s\'", tag2str(format)); - while(((total_size + 8) < atom.size) && !_fd->eos() && _fd->pos() < _fd->size() && !err) { - a.size = atom.size; - a.type = 0; + VideoSampleDesc *entry = new VideoSampleDesc(); + entry->codecTag = format; - if (atom.size >= 8) { - a.size = _fd->readUint32BE(); - a.type = _fd->readUint32BE(); + _fd->readUint16BE(); // version + _fd->readUint16BE(); // revision level + _fd->readUint32BE(); // vendor + _fd->readUint32BE(); // temporal quality + _fd->readUint32BE(); // spacial quality - // Some QuickTime videos with resource forks have mdat chunks - // that are of size 0. Adjust it so it's the correct size. - if (a.type == MKID_BE('mdat') && a.size == 0) - a.size = _fd->size(); - } - - total_size += 8; - a.offset += 8; - debug(4, "type: %08x %.4s sz: %x %x %x", a.type, tag2str(a.type), a.size, atom.size, total_size); - - if (a.size == 1) { // 64 bit extended size - warning("64 bit extended size is not supported in QuickTime"); - return -1; - } + uint16 width = _fd->readUint16BE(); // width + uint16 height = _fd->readUint16BE(); // height - if (a.size == 0) { - a.size = atom.size - total_size; - if (a.size <= 8) - break; - } + // The width is most likely invalid for entries after the first one + // so only set the overall width if it is not zero here. + if (width) + st->width = width; - uint32 i = 0; + if (height) + st->height = height; - for (; _parseTable[i].type != 0 && _parseTable[i].type != a.type; i++) - ; // Empty - - if (a.size < 8) - break; + _fd->readUint32BE(); // horiz resolution + _fd->readUint32BE(); // vert resolution + _fd->readUint32BE(); // data size, always 0 + _fd->readUint16BE(); // frames per samples - a.size -= 8; - - if (_parseTable[i].type == 0) { // skip leaf atoms data - debug(0, ">>> Skipped [%s]", tag2str(a.type)); - - _fd->seek(a.size, SEEK_CUR); - } else { - uint32 start_pos = _fd->pos(); - err = (this->*_parseTable[i].func)(a); - - uint32 left = a.size - _fd->pos() + start_pos; - - if (left > 0) // skip garbage at atom end - _fd->seek(left, SEEK_CUR); + byte codec_name[32]; + _fd->read(codec_name, 32); // codec name, pascal string (FIXME: true for mp4?) + if (codec_name[0] <= 31) { + memcpy(entry->codecName, &codec_name[1], codec_name[0]); + entry->codecName[codec_name[0]] = 0; } - a.offset += a.size; - total_size += a.size; - } - - if (!err && total_size < atom.size) - _fd->seek(atom.size - total_size, SEEK_SET); - - return err; -} - -int QuickTimeDecoder::readLeaf(MOVatom atom) { - if (atom.size > 1) - _fd->seek(atom.size, SEEK_SET); - - return 0; -} - -int QuickTimeDecoder::readMOOV(MOVatom atom) { - if (readDefault(atom) < 0) - return -1; - - // We parsed the 'moov' atom, so we don't need anything else - _foundMOOV = true; - return 1; -} - -int QuickTimeDecoder::readCMOV(MOVatom atom) { -#ifdef USE_ZLIB - // Read in the dcom atom - _fd->readUint32BE(); - if (_fd->readUint32BE() != MKID_BE('dcom')) - return -1; - if (_fd->readUint32BE() != MKID_BE('zlib')) { - warning("Unknown cmov compression type"); - return -1; - } - - // Read in the cmvd atom - uint32 compressedSize = _fd->readUint32BE() - 12; - if (_fd->readUint32BE() != MKID_BE('cmvd')) - return -1; - uint32 uncompressedSize = _fd->readUint32BE(); - - // Read in data - byte *compressedData = (byte *)malloc(compressedSize); - _fd->read(compressedData, compressedSize); - - // Create uncompressed stream - byte *uncompressedData = (byte *)malloc(uncompressedSize); - - // Uncompress the data - unsigned long dstLen = uncompressedSize; - if (!Common::uncompress(uncompressedData, &dstLen, compressedData, compressedSize)) { - warning ("Could not uncompress cmov chunk"); - free(compressedData); - free(uncompressedData); - return -1; - } - - // Load data into a new MemoryReadStream and assign _fd to be that - Common::SeekableReadStream *oldStream = _fd; - _fd = new Common::MemoryReadStream(uncompressedData, uncompressedSize, DisposeAfterUse::YES); - - // Read the contents of the uncompressed data - MOVatom a = { MKID_BE('moov'), 0, uncompressedSize }; - int err = readDefault(a); - - // Assign the file handle back to the original handle - free(compressedData); - delete _fd; - _fd = oldStream; - - return err; -#else - warning ("zlib not found, cannot read QuickTime cmov atom"); - return -1; -#endif -} - -int QuickTimeDecoder::readMVHD(MOVatom atom) { - byte version = _fd->readByte(); // version - _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags - - if (version == 1) { - warning("QuickTime version 1"); - _fd->readUint32BE(); _fd->readUint32BE(); - _fd->readUint32BE(); _fd->readUint32BE(); - } else { - _fd->readUint32BE(); // creation time - _fd->readUint32BE(); // modification time - } - - _timeScale = _fd->readUint32BE(); // time scale - debug(0, "time scale = %i\n", _timeScale); - - // duration - _duration = (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); - _fd->readUint32BE(); // preferred scale - - _fd->readUint16BE(); // preferred volume - - _fd->seek(10, SEEK_CUR); // reserved - - // We only need two values from the movie display matrix. Most of the values are just - // skipped. xMod and yMod are 16:16 fixed point numbers, the last part of the 3x3 matrix - // is 2:30. - uint32 xMod = _fd->readUint32BE(); - _fd->skip(12); - uint32 yMod = _fd->readUint32BE(); - _fd->skip(16); - - _scaleFactorX = Common::Rational(0x10000, xMod); - _scaleFactorY = Common::Rational(0x10000, yMod); - - _scaleFactorX.debugPrint(1, "readMVHD(): scaleFactorX ="); - _scaleFactorY.debugPrint(1, "readMVHD(): scaleFactorY ="); - - _fd->readUint32BE(); // preview time - _fd->readUint32BE(); // preview duration - _fd->readUint32BE(); // poster time - _fd->readUint32BE(); // selection time - _fd->readUint32BE(); // selection duration - _fd->readUint32BE(); // current time - _fd->readUint32BE(); // next track ID - - return 0; -} - -int QuickTimeDecoder::readTRAK(MOVatom atom) { - MOVStreamContext *sc = new MOVStreamContext(); - - if (!sc) - return -1; - - sc->codec_type = CODEC_TYPE_MOV_OTHER; - sc->start_time = 0; // XXX: check - _streams[_numStreams++] = sc; - - return readDefault(atom); -} - -int QuickTimeDecoder::readTKHD(MOVatom atom) { - MOVStreamContext *st = _streams[_numStreams - 1]; - byte version = _fd->readByte(); - - _fd->readByte(); _fd->readByte(); - _fd->readByte(); // flags - // - //MOV_TRACK_ENABLED 0x0001 - //MOV_TRACK_IN_MOVIE 0x0002 - //MOV_TRACK_IN_PREVIEW 0x0004 - //MOV_TRACK_IN_POSTER 0x0008 - // - - if (version == 1) { - _fd->readUint32BE(); _fd->readUint32BE(); - _fd->readUint32BE(); _fd->readUint32BE(); - } else { - _fd->readUint32BE(); // creation time - _fd->readUint32BE(); // modification time - } - - /* st->id = */_fd->readUint32BE(); // track id (NOT 0 !) - _fd->readUint32BE(); // reserved - //st->start_time = 0; // check - (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); // highlevel (considering edits) duration in movie timebase - _fd->readUint32BE(); // reserved - _fd->readUint32BE(); // reserved - - _fd->readUint16BE(); // layer - _fd->readUint16BE(); // alternate group - _fd->readUint16BE(); // volume - _fd->readUint16BE(); // reserved - - // We only need the two values from the displacement matrix for a track. - // See readMVHD() for more information. - uint32 xMod = _fd->readUint32BE(); - _fd->skip(12); - uint32 yMod = _fd->readUint32BE(); - _fd->skip(16); - - st->scaleFactorX = Common::Rational(0x10000, xMod); - st->scaleFactorY = Common::Rational(0x10000, yMod); - - st->scaleFactorX.debugPrint(1, "readTKHD(): scaleFactorX ="); - st->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 - - return 0; -} - -// edit list atom -int QuickTimeDecoder::readELST(MOVatom atom) { - MOVStreamContext *st = _streams[_numStreams - 1]; - - _fd->readByte(); // version - _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags - - st->editCount = _fd->readUint32BE(); - st->editList = new EditListEntry[st->editCount]; - - debug(2, "Track %d edit list count: %d", _numStreams - 1, st->editCount); - - for (uint32 i = 0; i < st->editCount; i++){ - st->editList[i].trackDuration = _fd->readUint32BE(); - st->editList[i].mediaTime = _fd->readSint32BE(); - st->editList[i].mediaRate = Common::Rational(_fd->readUint32BE(), 0x10000); - debugN(3, "\tDuration = %d, Media Time = %d, ", st->editList[i].trackDuration, st->editList[i].mediaTime); - st->editList[i].mediaRate.debugPrint(3, "Media Rate ="); - } - - if (st->editCount != 1) - warning("Multiple edit list entries. Things may go awry"); - - return 0; -} - -int QuickTimeDecoder::readHDLR(MOVatom atom) { - MOVStreamContext *st = _streams[_numStreams - 1]; - - _fd->readByte(); // version - _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags - - // component type - uint32 ctype = _fd->readUint32BE(); - uint32 type = _fd->readUint32BE(); // component subtype - - debug(0, "ctype= %s (0x%08lx)", tag2str(ctype), (long)ctype); - debug(0, "stype= %s", tag2str(type)); - - if (ctype == MKID_BE('mhlr')) // MOV - debug(0, "MOV detected"); - else if (ctype == 0) - debug(0, "MPEG-4 detected"); - - if (type == MKID_BE('vide')) - st->codec_type = CODEC_TYPE_VIDEO; - else if (type == MKID_BE('soun')) - st->codec_type = CODEC_TYPE_AUDIO; - - _fd->readUint32BE(); // component manufacture - _fd->readUint32BE(); // component flags - _fd->readUint32BE(); // component flags mask - - if (atom.size <= 24) - return 0; // nothing left to read - - // .mov: PASCAL string - byte len = _fd->readByte(); - _fd->seek(len, SEEK_CUR); - - _fd->seek(atom.size - (_fd->pos() - atom.offset), SEEK_CUR); - - return 0; -} - -int QuickTimeDecoder::readMDHD(MOVatom atom) { - MOVStreamContext *st = _streams[_numStreams - 1]; - byte version = _fd->readByte(); - - if (version > 1) - return 1; // unsupported - - _fd->readByte(); _fd->readByte(); - _fd->readByte(); // flags - - if (version == 1) { - _fd->readUint32BE(); _fd->readUint32BE(); - _fd->readUint32BE(); _fd->readUint32BE(); - } else { - _fd->readUint32BE(); // creation time - _fd->readUint32BE(); // modification time - } - - st->time_scale = _fd->readUint32BE(); - st->duration = (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); // duration - - _fd->readUint16BE(); // language - _fd->readUint16BE(); // quality - - return 0; -} - -int QuickTimeDecoder::readSTSD(MOVatom atom) { - MOVStreamContext *st = _streams[_numStreams - 1]; - - _fd->readByte(); // version - _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags - - uint32 entryCount = _fd->readUint32BE(); - st->sampleDescs.resize(entryCount); - - for (uint32 i = 0; i < entryCount; i++) { // Parsing Sample description table - MOVatom a = { 0, 0, 0 }; - uint32 start_pos = _fd->pos(); - int size = _fd->readUint32BE(); // size - uint32 format = _fd->readUint32BE(); // data format - - _fd->readUint32BE(); // reserved - _fd->readUint16BE(); // reserved - _fd->readUint16BE(); // index - - debug(0, "size=%d 4CC= %s codec_type=%d", size, tag2str(format), st->codec_type); - - if (st->codec_type == CODEC_TYPE_VIDEO) { - debug(0, "Video Codec FourCC: \'%s\'", tag2str(format)); - - VideoSampleDesc *entry = new VideoSampleDesc(); - st->sampleDescs[i] = entry; - - entry->codecTag = format; - - _fd->readUint16BE(); // version - _fd->readUint16BE(); // revision level - _fd->readUint32BE(); // vendor - _fd->readUint32BE(); // temporal quality - _fd->readUint32BE(); // spacial quality - - uint16 width = _fd->readUint16BE(); // width - uint16 height = _fd->readUint16BE(); // height - - // The width is most likely invalid for entries after the first one - // so only set the overall width if it is not zero here. - if (width) - st->width = width; - - if (height) - st->height = height; - - _fd->readUint32BE(); // horiz resolution - _fd->readUint32BE(); // vert resolution - _fd->readUint32BE(); // data size, always 0 - _fd->readUint16BE(); // frames per samples - - byte codec_name[32]; - _fd->read(codec_name, 32); // codec name, pascal string (FIXME: true for mp4?) - if (codec_name[0] <= 31) { - memcpy(entry->codecName, &codec_name[1], codec_name[0]); - entry->codecName[codec_name[0]] = 0; - } - - entry->bitsPerSample = _fd->readUint16BE(); // depth - entry->colorTableId = _fd->readUint16BE(); // colortable id - - // figure out the palette situation - byte colorDepth = entry->bitsPerSample & 0x1F; - bool colorGreyscale = (entry->bitsPerSample & 0x20) != 0; - - debug(0, "color depth: %d", colorDepth); - - // if the depth is 2, 4, or 8 bpp, file is palettized - if (colorDepth == 2 || colorDepth == 4 || colorDepth == 8) { - // Initialize the palette - entry->palette = new byte[256 * 3]; - memset(entry->palette, 0, 256 * 3); - - if (colorGreyscale) { - debug(0, "Greyscale palette"); - - // compute the greyscale palette - uint16 colorCount = 1 << colorDepth; - int16 colorIndex = 255; - byte colorDec = 256 / (colorCount - 1); - for (byte j = 0; j < colorCount; j++) { - entry->palette[j * 3] = entry->palette[j * 3 + 1] = entry->palette[j * 3 + 2] = colorIndex; - colorIndex -= colorDec; - if (colorIndex < 0) - colorIndex = 0; - } - } else if (entry->colorTableId & 0x08) { - // if flag bit 3 is set, use the default palette - //uint16 colorCount = 1 << colorDepth; - - warning("Predefined palette! %dbpp", colorDepth); - } else { - debug(0, "Palette from file"); - - // load the palette from the file - uint32 colorStart = _fd->readUint32BE(); - /* uint16 colorCount = */ _fd->readUint16BE(); - uint16 colorEnd = _fd->readUint16BE(); - for (uint32 j = colorStart; j <= colorEnd; j++) { - // each R, G, or B component is 16 bits; - // only use the top 8 bits; skip alpha bytes - // up front - _fd->readByte(); - _fd->readByte(); - entry->palette[j * 3] = _fd->readByte(); - _fd->readByte(); - entry->palette[j * 3 + 1] = _fd->readByte(); - _fd->readByte(); - entry->palette[j * 3 + 2] = _fd->readByte(); - _fd->readByte(); - } + entry->bitsPerSample = _fd->readUint16BE(); // depth + entry->colorTableId = _fd->readUint16BE(); // colortable id + + // figure out the palette situation + byte colorDepth = entry->bitsPerSample & 0x1F; + bool colorGreyscale = (entry->bitsPerSample & 0x20) != 0; + + debug(0, "color depth: %d", colorDepth); + + // if the depth is 2, 4, or 8 bpp, file is palettized + if (colorDepth == 2 || colorDepth == 4 || colorDepth == 8) { + // Initialize the palette + entry->palette = new byte[256 * 3]; + memset(entry->palette, 0, 256 * 3); + + if (colorGreyscale) { + debug(0, "Greyscale palette"); + + // compute the greyscale palette + uint16 colorCount = 1 << colorDepth; + int16 colorIndex = 255; + byte colorDec = 256 / (colorCount - 1); + for (byte j = 0; j < colorCount; j++) { + entry->palette[j * 3] = entry->palette[j * 3 + 1] = entry->palette[j * 3 + 2] = colorIndex; + colorIndex -= colorDec; + if (colorIndex < 0) + colorIndex = 0; } - } - } else if (st->codec_type == CODEC_TYPE_AUDIO) { - debug(0, "Audio Codec FourCC: \'%s\'", tag2str(format)); - - AudioSampleDesc *entry = new AudioSampleDesc(); - st->sampleDescs[i] = entry; - - entry->codecTag = format; - - uint16 stsdVersion = _fd->readUint16BE(); - _fd->readUint16BE(); // revision level - _fd->readUint32BE(); // vendor - - entry->channels = _fd->readUint16BE(); // channel count - entry->bitsPerSample = _fd->readUint16BE(); // sample size - - _fd->readUint16BE(); // compression id = 0 - _fd->readUint16BE(); // packet size = 0 - - entry->sampleRate = (_fd->readUint32BE() >> 16); - - debug(0, "stsd version =%d", stsdVersion); - if (stsdVersion == 0) { - // Not used, except in special cases. See below. - entry->samplesPerFrame = entry->bytesPerFrame = 0; - } else if (stsdVersion == 1) { - // Read QT version 1 fields. In version 0 these dont exist. - entry->samplesPerFrame = _fd->readUint32BE(); - debug(0, "stsd samples_per_frame =%d",entry->samplesPerFrame); - _fd->readUint32BE(); // bytes per packet - entry->bytesPerFrame = _fd->readUint32BE(); - debug(0, "stsd bytes_per_frame =%d", entry->bytesPerFrame); - _fd->readUint32BE(); // bytes per sample - } else { - warning("Unsupported QuickTime STSD audio version %d", stsdVersion); - return 1; - } + } else if (entry->colorTableId & 0x08) { + // if flag bit 3 is set, use the default palette + //uint16 colorCount = 1 << colorDepth; - // Version 0 videos (such as the Riven ones) don't have this set, - // but we need it later on. Add it in here. - if (format == MKID_BE('ima4')) { - entry->samplesPerFrame = 64; - entry->bytesPerFrame = 34 * entry->channels; + warning("Predefined palette! %dbpp", colorDepth); + } else { + debug(0, "Palette from file"); + + // load the palette from the file + uint32 colorStart = _fd->readUint32BE(); + /* uint16 colorCount = */ _fd->readUint16BE(); + uint16 colorEnd = _fd->readUint16BE(); + for (uint32 j = colorStart; j <= colorEnd; j++) { + // each R, G, or B component is 16 bits; + // only use the top 8 bits; skip alpha bytes + // up front + _fd->readByte(); + _fd->readByte(); + entry->palette[j * 3] = _fd->readByte(); + _fd->readByte(); + entry->palette[j * 3 + 1] = _fd->readByte(); + _fd->readByte(); + entry->palette[j * 3 + 2] = _fd->readByte(); + _fd->readByte(); + } } + } - if (entry->sampleRate == 0 && st->time_scale > 1) - entry->sampleRate = st->time_scale; + return entry; + } else if (st->codec_type == CODEC_TYPE_AUDIO) { + debug(0, "Audio Codec FourCC: \'%s\'", tag2str(format)); + + AudioSampleDesc *entry = new AudioSampleDesc(); + entry->codecTag = format; + + uint16 stsdVersion = _fd->readUint16BE(); + _fd->readUint16BE(); // revision level + _fd->readUint32BE(); // vendor + + entry->channels = _fd->readUint16BE(); // channel count + entry->bitsPerSample = _fd->readUint16BE(); // sample size + + _fd->readUint16BE(); // compression id = 0 + _fd->readUint16BE(); // packet size = 0 + + entry->sampleRate = (_fd->readUint32BE() >> 16); + + debug(0, "stsd version =%d", stsdVersion); + if (stsdVersion == 0) { + // Not used, except in special cases. See below. + entry->samplesPerFrame = entry->bytesPerFrame = 0; + } else if (stsdVersion == 1) { + // Read QT version 1 fields. In version 0 these dont exist. + entry->samplesPerFrame = _fd->readUint32BE(); + debug(0, "stsd samples_per_frame =%d",entry->samplesPerFrame); + _fd->readUint32BE(); // bytes per packet + entry->bytesPerFrame = _fd->readUint32BE(); + debug(0, "stsd bytes_per_frame =%d", entry->bytesPerFrame); + _fd->readUint32BE(); // bytes per sample } else { - // other codec type, just skip (rtp, mp4s, tmcd ...) - _fd->seek(size - (_fd->pos() - start_pos), SEEK_CUR); + warning("Unsupported QuickTime STSD audio version %d", stsdVersion); + delete entry; + return 0; } - // this will read extra atoms at the end (wave, alac, damr, avcC, SMI ...) - a.size = size - (_fd->pos() - start_pos); - if (a.size > 8) - readDefault(a); - else if (a.size > 0) - _fd->seek(a.size, SEEK_CUR); - } - - return 0; -} - -int QuickTimeDecoder::readSTSC(MOVatom atom) { - MOVStreamContext *st = _streams[_numStreams - 1]; - - _fd->readByte(); // version - _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags - - st->sample_to_chunk_sz = _fd->readUint32BE(); - - debug(0, "track[%i].stsc.entries = %i", _numStreams - 1, st->sample_to_chunk_sz); - - st->sample_to_chunk = new MOVstsc[st->sample_to_chunk_sz]; - - if (!st->sample_to_chunk) - return -1; - - for (uint32 i = 0; i < st->sample_to_chunk_sz; i++) { - st->sample_to_chunk[i].first = _fd->readUint32BE() - 1; - st->sample_to_chunk[i].count = _fd->readUint32BE(); - st->sample_to_chunk[i].id = _fd->readUint32BE(); - //warning("Sample to Chunk[%d]: First = %d, Count = %d", i, st->sample_to_chunk[i].first, st->sample_to_chunk[i].count); - } - - return 0; -} - -int QuickTimeDecoder::readSTSS(MOVatom atom) { - MOVStreamContext *st = _streams[_numStreams - 1]; - - _fd->readByte(); // version - _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags - - st->keyframe_count = _fd->readUint32BE(); - - debug(0, "keyframe_count = %d", st->keyframe_count); - - st->keyframes = new uint32[st->keyframe_count]; - - if (!st->keyframes) - return -1; - - for (uint32 i = 0; i < st->keyframe_count; i++) { - st->keyframes[i] = _fd->readUint32BE() - 1; // Adjust here, the frames are based on 1 - debug(6, "keyframes[%d] = %d", i, st->keyframes[i]); - - } - return 0; -} - -int QuickTimeDecoder::readSTSZ(MOVatom atom) { - MOVStreamContext *st = _streams[_numStreams - 1]; - - _fd->readByte(); // version - _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags - - st->sample_size = _fd->readUint32BE(); - st->sample_count = _fd->readUint32BE(); - - debug(5, "sample_size = %d sample_count = %d", st->sample_size, st->sample_count); - - if (st->sample_size) - return 0; // there isn't any table following - - st->sample_sizes = new uint32[st->sample_count]; - - if (!st->sample_sizes) - return -1; - - for(uint32 i = 0; i < st->sample_count; i++) { - st->sample_sizes[i] = _fd->readUint32BE(); - debug(6, "sample_sizes[%d] = %d", i, st->sample_sizes[i]); - } - - return 0; -} - -static uint32 ff_gcd(uint32 a, uint32 b) { - return b ? ff_gcd(b, a % b) : a; -} - -int QuickTimeDecoder::readSTTS(MOVatom atom) { - MOVStreamContext *st = _streams[_numStreams - 1]; - uint32 duration = 0; - uint32 total_sample_count = 0; - - _fd->readByte(); // version - _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags - - st->stts_count = _fd->readUint32BE(); - st->stts_data = new MOVstts[st->stts_count]; - - debug(0, "track[%i].stts.entries = %i", _numStreams - 1, st->stts_count); - - st->time_rate = 0; - - for (int32 i = 0; i < st->stts_count; i++) { - int sample_duration; - int sample_count; - - sample_count = _fd->readUint32BE(); - sample_duration = _fd->readUint32BE(); - st->stts_data[i].count = sample_count; - st->stts_data[i].duration = sample_duration; - - st->time_rate = ff_gcd(st->time_rate, sample_duration); - - debug(0, "sample_count=%d, sample_duration=%d", sample_count, sample_duration); - - duration += sample_duration * sample_count; - total_sample_count += sample_count; - } - - st->nb_frames = total_sample_count; - - if (duration) - st->duration = duration; - - return 0; -} - -int QuickTimeDecoder::readSTCO(MOVatom atom) { - MOVStreamContext *st = _streams[_numStreams - 1]; - - _fd->readByte(); // version - _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags - - st->chunk_count = _fd->readUint32BE(); - st->chunk_offsets = new uint32[st->chunk_count]; + // Version 0 videos (such as the Riven ones) don't have this set, + // but we need it later on. Add it in here. + if (format == MKID_BE('ima4')) { + entry->samplesPerFrame = 64; + entry->bytesPerFrame = 34 * entry->channels; + } - if (!st->chunk_offsets) - return -1; + if (entry->sampleRate == 0 && st->time_scale > 1) + entry->sampleRate = st->time_scale; - for (uint32 i = 0; i < st->chunk_count; i++) { - // WORKAROUND/HACK: The offsets in Riven videos (ones inside the Mohawk archives themselves) - // have offsets relative to the archive and not the video. This is quite nasty. We subtract - // the initial offset of the stream to get the correct value inside of the stream. - st->chunk_offsets[i] = _fd->readUint32BE() - _beginOffset; + return entry; } return 0; } -int QuickTimeDecoder::readWAVE(MOVatom atom) { - if (_numStreams < 1) - return 0; - - MOVStreamContext *st = _streams[_numStreams - 1]; - - if (atom.size > (1 << 30)) - return -1; - - if (st->sampleDescs[0]->codecTag == MKID_BE('QDM2')) // Read extradata for QDM2 - st->extradata = _fd->readStream(atom.size - 8); - else if (atom.size > 8) - return readDefault(atom); - else - _fd->skip(atom.size); - - return 0; -} - void QuickTimeDecoder::close() { stopAudio(); - for (uint32 i = 0; i < _numStreams; i++) - delete _streams[i]; - - delete _fd; - _fd = 0; - if (_scaledSurface) { _scaledSurface->free(); delete _scaledSurface; @@ -1263,6 +623,7 @@ void QuickTimeDecoder::close() { // The audio stream is deleted automatically _audStream = NULL; + Common::QuickTimeParser::close(); SeekableVideoDecoder::reset(); } @@ -1459,12 +820,7 @@ void QuickTimeDecoder::updateAudioBuffer() { readNextAudioChunk(); } -QuickTimeDecoder::SampleDesc::SampleDesc() { - codecTag = 0; - bitsPerSample = 0; -} - -QuickTimeDecoder::VideoSampleDesc::VideoSampleDesc() : SampleDesc() { +QuickTimeDecoder::VideoSampleDesc::VideoSampleDesc() : Common::QuickTimeParser::SampleDesc() { memset(codecName, 0, 32); colorTableId = 0; palette = 0; @@ -1476,49 +832,11 @@ QuickTimeDecoder::VideoSampleDesc::~VideoSampleDesc() { delete videoCodec; } -QuickTimeDecoder::AudioSampleDesc::AudioSampleDesc() : SampleDesc() { +QuickTimeDecoder::AudioSampleDesc::AudioSampleDesc() : Common::QuickTimeParser::SampleDesc() { channels = 0; sampleRate = 0; samplesPerFrame = 0; bytesPerFrame = 0; } -QuickTimeDecoder::MOVStreamContext::MOVStreamContext() { - chunk_count = 0; - chunk_offsets = 0; - stts_count = 0; - stts_data = 0; - sample_to_chunk_sz = 0; - sample_to_chunk = 0; - sample_size = 0; - sample_count = 0; - sample_sizes = 0; - keyframe_count = 0; - keyframes = 0; - time_scale = 0; - time_rate = 0; - width = 0; - height = 0; - codec_type = CODEC_TYPE_MOV_OTHER; - editCount = 0; - editList = 0; - extradata = 0; - nb_frames = 0; - duration = 0; - start_time = 0; -} - -QuickTimeDecoder::MOVStreamContext::~MOVStreamContext() { - delete[] chunk_offsets; - delete[] stts_data; - delete[] sample_to_chunk; - delete[] sample_sizes; - delete[] keyframes; - delete[] editList; - delete extradata; - - for (uint32 i = 0; i < sampleDescs.size(); i++) - delete sampleDescs[i]; -} - } // End of namespace Video diff --git a/video/qt_decoder.h b/video/qt_decoder.h index 6a0aa61b20..3da695e559 100644 --- a/video/qt_decoder.h +++ b/video/qt_decoder.h @@ -34,10 +34,8 @@ #ifndef VIDEO_QT_DECODER_H #define VIDEO_QT_DECODER_H -#include "common/array.h" +#include "common/quicktime.h" #include "common/scummsys.h" -#include "common/queue.h" -#include "common/rational.h" #include "video/video_decoder.h" #include "video/codecs/codec.h" @@ -46,7 +44,6 @@ #include "audio/mixer.h" namespace Common { - class File; class MacResManager; } @@ -59,7 +56,7 @@ namespace Video { * - mohawk * - sci */ -class QuickTimeDecoder : public SeekableVideoDecoder { +class QuickTimeDecoder : public SeekableVideoDecoder, public Common::QuickTimeParser { public: QuickTimeDecoder(); virtual ~QuickTimeDecoder(); @@ -106,14 +103,7 @@ public: const byte *getPalette() { _dirtyPalette = false; return _palette; } bool hasDirtyPalette() const { return _dirtyPalette; } - /** - * Set the beginning offset of the video so we can modify the offsets in the stco - * atom of videos inside the Mohawk archives - * @param the beginning offset of the video - */ - void setChunkBeginOffset(uint32 offset) { _beginOffset = offset; } - - bool isVideoLoaded() const { return _fd != 0; } + bool isVideoLoaded() const { return isOpen(); } const Graphics::Surface *decodeNextFrame(); bool endOfVideo() const; uint32 getElapsedTime() const; @@ -125,47 +115,8 @@ public: void seekToTime(Audio::Timestamp time); uint32 getDuration() const { return _duration * 1000 / _timeScale; } -private: - // This is the file handle from which data is read from. It can be the actual file handle or a decompressed stream. - Common::SeekableReadStream *_fd; - - struct MOVatom { - uint32 type; - uint32 offset; - uint32 size; - }; - - struct ParseTable { - int (QuickTimeDecoder::*func)(MOVatom atom); - uint32 type; - }; - - struct MOVstts { - int count; - int duration; - }; - - struct MOVstsc { - uint32 first; - uint32 count; - uint32 id; - }; - - struct EditListEntry { - uint32 trackDuration; - int32 mediaTime; - Common::Rational mediaRate; - }; - - struct SampleDesc { - SampleDesc(); - virtual ~SampleDesc() {} - - uint32 codecTag; - uint16 bitsPerSample; - }; - - struct VideoSampleDesc : public SampleDesc { +protected: + struct VideoSampleDesc : public Common::QuickTimeParser::SampleDesc { VideoSampleDesc(); ~VideoSampleDesc(); @@ -175,7 +126,7 @@ private: Codec *videoCodec; }; - struct AudioSampleDesc : public SampleDesc { + struct AudioSampleDesc : public Common::QuickTimeParser::SampleDesc { AudioSampleDesc(); uint16 channels; @@ -184,62 +135,9 @@ private: uint32 bytesPerFrame; }; - enum CodecType { - CODEC_TYPE_MOV_OTHER, - CODEC_TYPE_VIDEO, - CODEC_TYPE_AUDIO - }; - - struct MOVStreamContext { - MOVStreamContext(); - ~MOVStreamContext(); - - uint32 chunk_count; - uint32 *chunk_offsets; - int stts_count; - MOVstts *stts_data; - uint32 sample_to_chunk_sz; - MOVstsc *sample_to_chunk; - uint32 sample_size; - uint32 sample_count; - uint32 *sample_sizes; - uint32 keyframe_count; - uint32 *keyframes; - int32 time_scale; - int time_rate; - - uint16 width; - uint16 height; - CodecType codec_type; - - Common::Array<SampleDesc *> sampleDescs; - - uint32 editCount; - EditListEntry *editList; - - Common::SeekableReadStream *extradata; - - uint32 nb_frames; - uint32 duration; - uint32 start_time; - Common::Rational scaleFactorX; - Common::Rational scaleFactorY; - }; - - const ParseTable *_parseTable; - bool _foundMOOV; - uint32 _timeScale; - uint32 _duration; - uint32 _numStreams; - Common::Rational _scaleFactorX; - Common::Rational _scaleFactorY; - MOVStreamContext *_streams[20]; - const byte *_palette; - bool _dirtyPalette; - uint32 _beginOffset; - Common::MacResManager *_resFork; + Common::QuickTimeParser::SampleDesc *readSampleDesc(MOVStreamContext *st, uint32 format); - void initParseTable(); +private: Audio::AudioStream *createAudioStream(Common::SeekableReadStream *stream); bool checkAudioCodecSupport(uint32 tag); Common::SeekableReadStream *getNextFramePacket(uint32 &descId); @@ -263,30 +161,15 @@ private: int8 _videoStreamIndex; uint32 findKeyFrame(uint32 frame) const; + bool _dirtyPalette; + const byte *_palette; + Graphics::Surface *_scaledSurface; const Graphics::Surface *scaleSurface(const Graphics::Surface *frame); Common::Rational getScaleFactorX() const; Common::Rational getScaleFactorY() const; void pauseVideoIntern(bool pause); - - int readDefault(MOVatom atom); - int readLeaf(MOVatom atom); - int readELST(MOVatom atom); - int readHDLR(MOVatom atom); - int readMDHD(MOVatom atom); - int readMOOV(MOVatom atom); - int readMVHD(MOVatom atom); - int readTKHD(MOVatom atom); - int readTRAK(MOVatom atom); - int readSTCO(MOVatom atom); - int readSTSC(MOVatom atom); - int readSTSD(MOVatom atom); - int readSTSS(MOVatom atom); - int readSTSZ(MOVatom atom); - int readSTTS(MOVatom atom); - int readCMOV(MOVatom atom); - int readWAVE(MOVatom atom); }; } // End of namespace Video |