diff options
author | clone2727 | 2011-06-01 07:04:05 -0700 |
---|---|---|
committer | clone2727 | 2011-06-01 07:04:05 -0700 |
commit | 49a8f7675f9685df984f5acd88ea4ac56b703f33 (patch) | |
tree | 650593c4254ba11c212f3f409b8c96929d4bc4ee | |
parent | 90f2cde9fa27b06fe7753d68d4166bc10c833942 (diff) | |
parent | dfb682288699e84321c14cc5d52cbd3fdaeba40c (diff) | |
download | scummvm-rg350-49a8f7675f9685df984f5acd88ea4ac56b703f33.tar.gz scummvm-rg350-49a8f7675f9685df984f5acd88ea4ac56b703f33.tar.bz2 scummvm-rg350-49a8f7675f9685df984f5acd88ea4ac56b703f33.zip |
Merge pull request #20 from scott-t/t7g-ios
T7G iOS support
33 files changed, 2285 insertions, 1219 deletions
diff --git a/audio/audiostream.cpp b/audio/audiostream.cpp index e6587a6543..547aa77526 100644 --- a/audio/audiostream.cpp +++ b/audio/audiostream.cpp @@ -30,6 +30,7 @@ #include "audio/audiostream.h" #include "audio/decoders/flac.h" #include "audio/decoders/mp3.h" +#include "audio/decoders/quicktime.h" #include "audio/decoders/raw.h" #include "audio/decoders/vorbis.h" @@ -48,7 +49,7 @@ struct StreamFileFormat { }; static const StreamFileFormat STREAM_FILEFORMATS[] = { - /* decoderName, fileExt, openStreamFuntion */ + /* decoderName, fileExt, openStreamFunction */ #ifdef USE_FLAC { "FLAC", ".flac", makeFLACStream }, { "FLAC", ".fla", makeFLACStream }, @@ -59,6 +60,7 @@ static const StreamFileFormat STREAM_FILEFORMATS[] = { #ifdef USE_MAD { "MPEG Layer 3", ".mp3", makeMP3Stream }, #endif + { "MPEG-4 Audio", ".m4a", makeQuickTimeStream }, { NULL, NULL, NULL } // Terminator }; diff --git a/audio/decoders/aac.cpp b/audio/decoders/aac.cpp new file mode 100644 index 0000000000..7949b5b561 --- /dev/null +++ b/audio/decoders/aac.cpp @@ -0,0 +1,177 @@ +/* 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$ + * + */ + +#include "audio/decoders/aac.h" + +#ifdef USE_FAAD + +#include "common/debug.h" +#include "common/stream.h" +#include "common/textconsole.h" +#include "common/util.h" + +#include "audio/audiostream.h" + +#include <neaacdec.h> + +namespace Audio { + +class AACStream : public AudioStream { +public: + AACStream(Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeStream, + Common::SeekableReadStream *extraData, + DisposeAfterUse::Flag disposeExtraData); + ~AACStream(); + + int readBuffer(int16 *buffer, const int numSamples); + + bool endOfData() const { return _inBufferPos >= _inBufferSize && !_remainingSamples; } + bool isStereo() const { return _channels == 2; } + int getRate() const { return _rate; } + +private: + NeAACDecHandle _handle; + byte _channels; + unsigned long _rate; + + byte *_inBuffer; + uint32 _inBufferSize; + uint32 _inBufferPos; + + int16 *_remainingSamples; + uint32 _remainingSamplesSize; + uint32 _remainingSamplesPos; + + void init(Common::SeekableReadStream *extraData); +}; + +AACStream::AACStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeStream, + Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) { + + _remainingSamples = 0; + _inBufferPos = 0; + + init(extraData); + + // Copy all the data to a pointer so it can be passed through + // (At least MPEG-4 chunks shouldn't be large) + _inBufferSize = stream->size(); + _inBuffer = new byte[_inBufferSize]; + stream->read(_inBuffer, _inBufferSize); + + if (disposeStream == DisposeAfterUse::YES) + delete stream; + + if (disposeExtraData == DisposeAfterUse::YES) + delete extraData; +} + +AACStream::~AACStream() { + NeAACDecClose(_handle); + delete[] _inBuffer; + delete[] _remainingSamples; +} + +void AACStream::init(Common::SeekableReadStream *extraData) { + // Open the library + _handle = NeAACDecOpen(); + + // Configure the library to our needs + NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration(_handle); + conf->outputFormat = FAAD_FMT_16BIT; // We only support 16bit audio + conf->downMatrix = 1; // Convert from 5.1 to stereo if required + NeAACDecSetConfiguration(_handle, conf); + + // Copy the extra data to a buffer + extraData->seek(0); + byte *extraDataBuf = new byte[extraData->size()]; + extraData->read(extraDataBuf, extraData->size()); + + // Initialize with our extra data + // NOTE: This code assumes the extra data is coming from an MPEG-4 file! + int err = NeAACDecInit2(_handle, extraDataBuf, extraData->size(), &_rate, &_channels); + delete[] extraDataBuf; + + if (err < 0) + error("Could not initialize AAC decoder: %s", NeAACDecGetErrorMessage(err)); +} + +int AACStream::readBuffer(int16 *buffer, const int numSamples) { + int samples = 0; + + assert((numSamples % _channels) == 0); + + // Dip into our remaining samples pool if it's available + if (_remainingSamples) { + samples = MIN<int>(numSamples, _remainingSamplesSize - _remainingSamplesPos); + + memcpy(buffer, _remainingSamples + _remainingSamplesPos, samples * 2); + _remainingSamplesPos += samples; + + if (_remainingSamplesPos == _remainingSamplesSize) { + delete[] _remainingSamples; + _remainingSamples = 0; + } + } + + // Decode until we have enough samples (or there's no more left) + while (samples < numSamples && !endOfData()) { + NeAACDecFrameInfo frameInfo; + uint16 *decodedSamples = (uint16 *)NeAACDecDecode(_handle, &frameInfo, _inBuffer + _inBufferPos, _inBufferSize - _inBufferPos); + + if (frameInfo.error != 0) + error("Failed to decode AAC frame: %s", NeAACDecGetErrorMessage(frameInfo.error)); + + int decodedSampleSize = frameInfo.samples; + int copySamples = (decodedSampleSize > (numSamples - samples)) ? (numSamples - samples) : decodedSampleSize; + + memcpy(buffer + samples, decodedSamples, copySamples * 2); + samples += copySamples; + + // Copy leftover samples for use in a later readBuffer() call + if (copySamples != decodedSampleSize) { + _remainingSamplesSize = decodedSampleSize - copySamples; + _remainingSamples = new int16[_remainingSamplesSize]; + _remainingSamplesPos = 0; + memcpy(_remainingSamples, decodedSamples + copySamples, _remainingSamplesSize * 2); + } + + _inBufferPos += frameInfo.bytesconsumed; + } + + return samples; +} + +// Factory function +AudioStream *makeAACStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeStream, + Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) { + + return new AACStream(stream, disposeStream, extraData, disposeExtraData); +} + +} // End of namespace Audio + +#endif // #ifdef USE_FAAD diff --git a/audio/decoders/aac.h b/audio/decoders/aac.h new file mode 100644 index 0000000000..f14fa9488b --- /dev/null +++ b/audio/decoders/aac.h @@ -0,0 +1,66 @@ +/* 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$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - groovie + */ + +#ifndef SOUND_AAC_H +#define SOUND_AAC_H + +#include "common/scummsys.h" +#include "common/types.h" + +#ifdef USE_FAAD + +namespace Common { + class SeekableReadStream; +} + +namespace Audio { + +class AudioStream; + +/** + * Create a new AudioStream from the AAC data in the given stream. + * + * @param stream the SeekableReadStream from which to read the AAC data + * @param disposeStream whether to delete the stream after use + * @param extraData the SeekableReadStream from which to read the AAC extra data + * @param disposeExtraData whether to delete the extra data stream after use + * @return a new AudioStream, or NULL, if an error occurred + */ +AudioStream *makeAACStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeStream, + Common::SeekableReadStream *extraData, + DisposeAfterUse::Flag disposeExtraData = DisposeAfterUse::NO); + +} // End of namespace Audio + +#endif // #ifdef USE_FAAD +#endif // #ifndef SOUND_AAC_H diff --git a/video/codecs/qdm2.cpp b/audio/decoders/qdm2.cpp index 10310ce2a0..a178c363b5 100644 --- a/video/codecs/qdm2.cpp +++ b/audio/decoders/qdm2.cpp @@ -23,19 +23,19 @@ // Based off ffmpeg's QDM2 decoder #include "common/scummsys.h" -#include "video/codecs/qdm2.h" +#include "audio/decoders/qdm2.h" -#ifdef VIDEO_CODECS_QDM2_H +#ifdef AUDIO_QDM2_H #include "audio/audiostream.h" -#include "video/codecs/qdm2data.h" +#include "audio/decoders/qdm2data.h" #include "common/array.h" #include "common/debug.h" #include "common/stream.h" #include "common/textconsole.h" -namespace Video { +namespace Audio { enum { SOFTCLIP_THRESHOLD = 27600, @@ -150,7 +150,7 @@ struct RDFTContext { FFTContext fft; }; -class QDM2Stream : public Audio::AudioStream { +class QDM2Stream : public AudioStream { public: QDM2Stream(Common::SeekableReadStream *stream, Common::SeekableReadStream *extraData); ~QDM2Stream(); @@ -3270,10 +3270,10 @@ int QDM2Stream::readBuffer(int16 *buffer, const int numSamples) { return decodedSamples; } -Audio::AudioStream *makeQDM2Stream(Common::SeekableReadStream *stream, Common::SeekableReadStream *extraData) { +AudioStream *makeQDM2Stream(Common::SeekableReadStream *stream, Common::SeekableReadStream *extraData) { return new QDM2Stream(stream, extraData); } -} // End of namespace Video +} // End of namespace Audio #endif diff --git a/video/codecs/qdm2.h b/audio/decoders/qdm2.h index bb9228550a..c0ec647bfd 100644 --- a/video/codecs/qdm2.h +++ b/audio/decoders/qdm2.h @@ -23,18 +23,16 @@ // Only compile if Mohawk is enabled or if we're building dynamic modules #if defined(ENABLE_MOHAWK) || defined(DYNAMIC_MODULES) -#ifndef VIDEO_CODECS_QDM2_H -#define VIDEO_CODECS_QDM2_H +#ifndef AUDIO_QDM2_H +#define AUDIO_QDM2_H namespace Common { class SeekableReadStream; } namespace Audio { -class AudioStream; -} -namespace Video { +class AudioStream; /** * Create a new AudioStream from the QDM2 data in the given stream. @@ -43,9 +41,9 @@ namespace Video { * @param extraData the QuickTime extra data stream * @return a new AudioStream, or NULL, if an error occurred */ -Audio::AudioStream *makeQDM2Stream(Common::SeekableReadStream *stream, Common::SeekableReadStream *extraData); +AudioStream *makeQDM2Stream(Common::SeekableReadStream *stream, Common::SeekableReadStream *extraData); -} // End of namespace Video +} // End of namespace Audio -#endif // VIDEO_CODECS_QDM2_H +#endif // AUDIO_QDM2_H #endif // Mohawk/Plugins guard diff --git a/video/codecs/qdm2data.h b/audio/decoders/qdm2data.h index 995873207f..d92bc0ff80 100644 --- a/video/codecs/qdm2data.h +++ b/audio/decoders/qdm2data.h @@ -20,12 +20,12 @@ * */ -#ifndef VIDEO_CODECS_QDM2DATA_H -#define VIDEO_CODECS_QDM2DATA_H +#ifndef AUDIO_QDM2DATA_H +#define AUDIO_QDM2DATA_H #include "common/scummsys.h" -namespace Video { +namespace Audio { /// VLC TABLES @@ -523,6 +523,6 @@ static const float type34_delta[10] = { // FIXME: covers 8 entries.. 0.138071194291115f,0.333333343267441f,0.60947573184967f,1.0f,0.0f, }; -} // End of namespace Video +} // End of namespace Audio #endif diff --git a/audio/decoders/quicktime.cpp b/audio/decoders/quicktime.cpp new file mode 100644 index 0000000000..bdde9db883 --- /dev/null +++ b/audio/decoders/quicktime.cpp @@ -0,0 +1,426 @@ +/* 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$ + * + */ + +#include "common/debug.h" +#include "common/util.h" +#include "common/memstream.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "audio/audiostream.h" +#include "audio/decoders/quicktime.h" +#include "audio/decoders/quicktime_intern.h" + +// Codecs +#include "audio/decoders/aac.h" +#include "audio/decoders/adpcm.h" +#include "audio/decoders/qdm2.h" +#include "audio/decoders/raw.h" + +namespace Audio { + +QuickTimeAudioDecoder::QuickTimeAudioDecoder() : Common::QuickTimeParser() { + _audStream = 0; +} + +QuickTimeAudioDecoder::~QuickTimeAudioDecoder() { + delete _audStream; +} + +bool QuickTimeAudioDecoder::loadAudioFile(const Common::String &filename) { + if (!Common::QuickTimeParser::parseFile(filename)) + return false; + + init(); + return true; +} + +bool QuickTimeAudioDecoder::loadAudioStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle) { + if (!Common::QuickTimeParser::parseStream(stream, disposeFileHandle)) + return false; + + init(); + return true; +} + +void QuickTimeAudioDecoder::init() { + Common::QuickTimeParser::init(); + + _audioStreamIndex = -1; + + // Find an audio stream + for (uint32 i = 0; i < _numStreams; i++) + if (_streams[i]->codec_type == CODEC_TYPE_AUDIO && _audioStreamIndex < 0) + _audioStreamIndex = i; + + // Initialize audio, if present + if (_audioStreamIndex >= 0) { + AudioSampleDesc *entry = (AudioSampleDesc *)_streams[_audioStreamIndex]->sampleDescs[0]; + + if (checkAudioCodecSupport(entry->codecTag, _streams[_audioStreamIndex]->objectTypeMP4)) { + _audStream = makeQueuingAudioStream(entry->sampleRate, entry->channels == 2); + _curAudioChunk = 0; + + // Make sure the bits per sample transfers to the sample size + if (entry->codecTag == MKTAG('r', 'a', 'w', ' ') || entry->codecTag == MKTAG('t', 'w', 'o', 's')) + _streams[_audioStreamIndex]->sample_size = (entry->bitsPerSample / 8) * entry->channels; + } + } +} + +Common::QuickTimeParser::SampleDesc *QuickTimeAudioDecoder::readSampleDesc(MOVStreamContext *st, uint32 format) { + 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 { + warning("Unsupported QuickTime STSD audio version %d", stsdVersion); + delete entry; + return 0; + } + + // 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 == MKTAG('i', 'm', 'a', '4')) { + entry->samplesPerFrame = 64; + entry->bytesPerFrame = 34 * entry->channels; + } + + if (entry->sampleRate == 0 && st->time_scale > 1) + entry->sampleRate = st->time_scale; + + return entry; + } + + return 0; +} + +bool QuickTimeAudioDecoder::checkAudioCodecSupport(uint32 tag, byte objectTypeMP4) { + // Check if the codec is a supported codec + if (tag == MKTAG('t', 'w', 'o', 's') || tag == MKTAG('r', 'a', 'w', ' ') || tag == MKTAG('i', 'm', 'a', '4')) + return true; + +#ifdef AUDIO_QDM2_H + if (tag == MKTAG('Q', 'D', 'M', '2')) + return true; +#endif + + if (tag == MKTAG('m', 'p', '4', 'a')) { + Common::String audioType; + switch (objectTypeMP4) { + case 0x40: // AAC +#ifdef USE_FAAD + return true; +#else + audioType = "AAC"; + break; +#endif + default: + audioType = "Unknown"; + break; + } + warning("No MPEG-4 audio (%s) support", audioType.c_str()); + } else + warning("Audio Codec Not Supported: \'%s\'", tag2str(tag)); + + return false; +} + +AudioStream *QuickTimeAudioDecoder::createAudioStream(Common::SeekableReadStream *stream) { + if (!stream || _audioStreamIndex < 0) + return NULL; + + AudioSampleDesc *entry = (AudioSampleDesc *)_streams[_audioStreamIndex]->sampleDescs[0]; + + if (entry->codecTag == MKTAG('t', 'w', 'o', 's') || entry->codecTag == MKTAG('r', 'a', 'w', ' ')) { + // Fortunately, most of the audio used in Myst videos is raw... + uint16 flags = 0; + if (entry->codecTag == MKTAG('r', 'a', 'w', ' ')) + flags |= FLAG_UNSIGNED; + if (entry->channels == 2) + flags |= FLAG_STEREO; + if (entry->bitsPerSample == 16) + flags |= FLAG_16BITS; + uint32 dataSize = stream->size(); + byte *data = (byte *)malloc(dataSize); + stream->read(data, dataSize); + delete stream; + return makeRawStream(data, dataSize, entry->sampleRate, flags); + } else if (entry->codecTag == MKTAG('i', 'm', 'a', '4')) { + // Riven uses this codec (as do some Myst ME videos) + return makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), kADPCMApple, entry->sampleRate, entry->channels, 34); + } else if (entry->codecTag == MKTAG('m', 'p', '4', 'a')) { + // The 7th Guest iOS uses an MPEG-4 codec +#ifdef USE_FAAD + if (_streams[_audioStreamIndex]->objectTypeMP4 == 0x40) + return makeAACStream(stream, DisposeAfterUse::YES, _streams[_audioStreamIndex]->extradata); +#endif +#ifdef AUDIO_QDM2_H + } else if (entry->codecTag == MKTAG('Q', 'D', 'M', '2')) { + // Myst ME uses this codec for many videos + return makeQDM2Stream(stream, _streams[_audioStreamIndex]->extradata); +#endif + } + + error("Unsupported audio codec"); + + return NULL; +} + +uint32 QuickTimeAudioDecoder::getAudioChunkSampleCount(uint chunk) { + if (_audioStreamIndex < 0) + return 0; + + uint32 sampleCount = 0; + + for (uint32 j = 0; j < _streams[_audioStreamIndex]->sample_to_chunk_sz; j++) + if (chunk >= _streams[_audioStreamIndex]->sample_to_chunk[j].first) + sampleCount = _streams[_audioStreamIndex]->sample_to_chunk[j].count; + + return sampleCount; +} + +bool QuickTimeAudioDecoder::isOldDemuxing() const { + assert(_audioStreamIndex >= 0); + return _streams[_audioStreamIndex]->stts_count == 1 && _streams[_audioStreamIndex]->stts_data[0].duration == 1; +} + +void QuickTimeAudioDecoder::queueNextAudioChunk() { + AudioSampleDesc *entry = (AudioSampleDesc *)_streams[_audioStreamIndex]->sampleDescs[0]; + Common::MemoryWriteStreamDynamic *wStream = new Common::MemoryWriteStreamDynamic(); + + _fd->seek(_streams[_audioStreamIndex]->chunk_offsets[_curAudioChunk]); + + // First, we have to get the sample count + uint32 sampleCount = getAudioChunkSampleCount(_curAudioChunk); + assert(sampleCount); + + if (isOldDemuxing()) { + // Old-style audio demuxing + + // Then calculate the right sizes + while (sampleCount > 0) { + uint32 samples = 0, size = 0; + + if (entry->samplesPerFrame >= 160) { + samples = entry->samplesPerFrame; + size = entry->bytesPerFrame; + } else if (entry->samplesPerFrame > 1) { + samples = MIN<uint32>((1024 / entry->samplesPerFrame) * entry->samplesPerFrame, sampleCount); + size = (samples / entry->samplesPerFrame) * entry->bytesPerFrame; + } else { + samples = MIN<uint32>(1024, sampleCount); + size = samples * _streams[_audioStreamIndex]->sample_size; + } + + // Now, we read in the data for this data and output it + byte *data = (byte *)malloc(size); + _fd->read(data, size); + wStream->write(data, size); + free(data); + sampleCount -= samples; + } + } else { + // New-style audio demuxing + + // Find our starting sample + uint32 startSample = 0; + for (uint32 i = 0; i < _curAudioChunk; i++) + startSample += getAudioChunkSampleCount(i); + + for (uint32 i = 0; i < sampleCount; i++) { + uint32 size = (_streams[_audioStreamIndex]->sample_size != 0) ? _streams[_audioStreamIndex]->sample_size : _streams[_audioStreamIndex]->sample_sizes[i + startSample]; + + // Now, we read in the data for this data and output it + byte *data = (byte *)malloc(size); + _fd->read(data, size); + wStream->write(data, size); + free(data); + } + } + + // Now queue the buffer + _audStream->queueAudioStream(createAudioStream(new Common::MemoryReadStream(wStream->getData(), wStream->size(), DisposeAfterUse::YES))); + delete wStream; + + _curAudioChunk++; +} + +void QuickTimeAudioDecoder::setAudioStreamPos(const Timestamp &where) { + if (!_audStream) + return; + + // Re-create the audio stream + delete _audStream; + Audio::QuickTimeAudioDecoder::AudioSampleDesc *entry = (Audio::QuickTimeAudioDecoder::AudioSampleDesc *)_streams[_audioStreamIndex]->sampleDescs[0]; + _audStream = Audio::makeQueuingAudioStream(entry->sampleRate, entry->channels == 2); + + // First, we need to track down what audio sample we need + Audio::Timestamp curAudioTime = where.convertToFramerate(_streams[_audioStreamIndex]->time_scale); + uint32 sample = curAudioTime.totalNumberOfFrames(); + uint32 seekSample = sample; + + if (!isOldDemuxing()) { + // We shouldn't have audio samples that are a different duration + // That would be quite bad! + if (_streams[_audioStreamIndex]->stts_count != 1) { + warning("Failed seeking"); + return; + } + + // Note that duration is in terms of *one* channel + // This eases calculation a bit + seekSample /= _streams[_audioStreamIndex]->stts_data[0].duration; + } + + // Now to track down what chunk it's in + uint32 totalSamples = 0; + _curAudioChunk = 0; + for (uint32 i = 0; i < _streams[_audioStreamIndex]->chunk_count; i++, _curAudioChunk++) { + uint32 chunkSampleCount = getAudioChunkSampleCount(i); + + if (seekSample < totalSamples + chunkSampleCount) + break; + + totalSamples += chunkSampleCount; + } + + // Reposition the audio stream + queueNextAudioChunk(); + if (sample != totalSamples) { + // HACK: Skip a certain amount of samples from the stream + // (There's got to be a better way to do this!) + int skipSamples = (sample - totalSamples) * entry->channels; + + int16 *tempBuffer = new int16[skipSamples]; + _audStream->readBuffer(tempBuffer, skipSamples); + delete[] tempBuffer; + } +} + +QuickTimeAudioDecoder::AudioSampleDesc::AudioSampleDesc() : Common::QuickTimeParser::SampleDesc() { + channels = 0; + sampleRate = 0; + samplesPerFrame = 0; + bytesPerFrame = 0; +} + +/** + * A wrapper around QuickTimeAudioDecoder that implements the RewindableAudioStream API + */ +class QuickTimeAudioStream : public SeekableAudioStream, public QuickTimeAudioDecoder { +public: + QuickTimeAudioStream() {} + ~QuickTimeAudioStream() {} + + bool openFromFile(const Common::String &filename) { + return QuickTimeAudioDecoder::loadAudioFile(filename) && _audioStreamIndex >= 0 && _audStream; + } + + bool openFromStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle) { + return QuickTimeAudioDecoder::loadAudioStream(stream, disposeFileHandle) && _audioStreamIndex >= 0 && _audStream; + } + + // AudioStream API + int readBuffer(int16 *buffer, const int numSamples) { + int samples = 0; + + while (samples < numSamples && !endOfData()) { + if (_audStream->numQueuedStreams() == 0) + queueNextAudioChunk(); + + samples += _audStream->readBuffer(buffer + samples, numSamples - samples); + } + + return samples; + } + + bool isStereo() const { return _audStream->isStereo(); } + int getRate() const { return _audStream->getRate(); } + bool endOfData() const { return _curAudioChunk >= _streams[_audioStreamIndex]->chunk_count && _audStream->endOfData(); } + + // SeekableAudioStream API + bool seek(const Timestamp &where) { + if (where > getLength()) + return false; + + setAudioStreamPos(where); + return true; + } + + Timestamp getLength() const { + return Timestamp(0, _streams[_audioStreamIndex]->duration, _streams[_audioStreamIndex]->time_scale); + } +}; + +SeekableAudioStream *makeQuickTimeStream(const Common::String &filename) { + QuickTimeAudioStream *audioStream = new QuickTimeAudioStream(); + + if (!audioStream->openFromFile(filename)) { + delete audioStream; + return 0; + } + + return audioStream; +} + +SeekableAudioStream *makeQuickTimeStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) { + QuickTimeAudioStream *audioStream = new QuickTimeAudioStream(); + + if (!audioStream->openFromStream(stream, disposeAfterUse)) { + delete audioStream; + return 0; + } + + return audioStream; +} + +} // End of namespace Audio diff --git a/audio/decoders/quicktime.h b/audio/decoders/quicktime.h new file mode 100644 index 0000000000..ff81ec9390 --- /dev/null +++ b/audio/decoders/quicktime.h @@ -0,0 +1,70 @@ +/* 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$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - groovie + * - mohawk + * - sci + */ + +#ifndef AUDIO_QUICKTIME_H +#define AUDIO_QUICKTIME_H + +#include "common/scummsys.h" +#include "common/types.h" + +namespace Common { + class SeekableReadStream; + class String; +} + +namespace Audio { + +class SeekableAudioStream; + +/** + * Try to load a QuickTime sound file from the given file name and create a SeekableAudioStream + * from that data. + * + * @param filename the filename of the file from which to read the data + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +SeekableAudioStream *makeQuickTimeStream(const Common::String &filename); + +/** + * Try to load a QuickTime sound file from the given seekable stream and create a SeekableAudioStream + * from that data. + * + * @param stream the SeekableReadStream from which to read the data + * @param disposeAfterUse whether to delete the stream after use + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +SeekableAudioStream *makeQuickTimeStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + +} // End of namespace Audio + +#endif diff --git a/audio/decoders/quicktime_intern.h b/audio/decoders/quicktime_intern.h new file mode 100644 index 0000000000..691ef7b58c --- /dev/null +++ b/audio/decoders/quicktime_intern.h @@ -0,0 +1,96 @@ +/* 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$ + * + */ + +/** + * Internal interface to the QuickTime audio decoder. + * + * This is available so that the QuickTimeVideoDecoder can use + * this directly. + */ + +#ifndef AUDIO_QUICKTIME_INTERN_H +#define AUDIO_QUICKTIME_INTERN_H + +#include "common/quicktime.h" +#include "common/scummsys.h" +#include "common/types.h" + +namespace Common { + class SeekableReadStream; + class String; +} + +namespace Audio { + +class AudioStream; +class QueuingAudioStream; + +class QuickTimeAudioDecoder : public Common::QuickTimeParser { +public: + QuickTimeAudioDecoder(); + virtual ~QuickTimeAudioDecoder(); + + /** + * Load a QuickTime audio file + * @param filename the filename to load + */ + bool loadAudioFile(const Common::String &filename); + + /** + * Load a QuickTime audio file from a SeekableReadStream + * @param stream the stream to load + */ + bool loadAudioStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle); + +protected: + struct AudioSampleDesc : public Common::QuickTimeParser::SampleDesc { + AudioSampleDesc(); + + uint16 channels; + uint32 sampleRate; + uint32 samplesPerFrame; + uint32 bytesPerFrame; + }; + + // Common::QuickTimeParser API + virtual Common::QuickTimeParser::SampleDesc *readSampleDesc(MOVStreamContext *st, uint32 format); + + AudioStream *createAudioStream(Common::SeekableReadStream *stream); + bool checkAudioCodecSupport(uint32 tag, byte objectTypeMP4); + void init(); + + void queueNextAudioChunk(); + uint32 getAudioChunkSampleCount(uint chunk); + int8 _audioStreamIndex; + uint _curAudioChunk; + QueuingAudioStream *_audStream; + + void setAudioStreamPos(const Timestamp &where); + bool isOldDemuxing() const; +}; + +} // End of namespace Audio + +#endif diff --git a/audio/module.mk b/audio/module.mk index 840b6d6692..46cb9944e1 100644 --- a/audio/module.mk +++ b/audio/module.mk @@ -13,12 +13,15 @@ MODULE_OBJS := \ musicplugin.o \ null.o \ timestamp.o \ + decoders/aac.o \ decoders/adpcm.o \ decoders/aiff.o \ decoders/flac.o \ decoders/iff_sound.o \ decoders/mac_snd.o \ decoders/mp3.o \ + decoders/qdm2.o \ + decoders/quicktime.o \ decoders/raw.o \ decoders/vag.o \ decoders/voc.o \ diff --git a/base/version.cpp b/base/version.cpp index 6a670c1a40..3083034714 100644 --- a/base/version.cpp +++ b/base/version.cpp @@ -112,5 +112,9 @@ const char *gScummVMFeatures = "" #ifdef USE_THEORADEC "Theora " #endif + +#ifdef USE_FAAD + "AAC " +#endif ; 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..cf50584cc6 --- /dev/null +++ b/common/quicktime.cpp @@ -0,0 +1,819 @@ +/* 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/textconsole.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(); + _disposeFileHandle = DisposeAfterUse::YES; + + initParseTable(); +} + +QuickTimeParser::~QuickTimeParser() { + close(); + delete _resFork; +} + +bool QuickTimeParser::parseFile(const Common::String &filename) { + if (!_resFork->open(filename) || !_resFork->hasDataFork()) + return false; + + _foundMOOV = false; + _numStreams = 0; + _disposeFileHandle = DisposeAfterUse::YES; + + MOVatom atom = { 0, 0, 0xffffffff }; + + if (_resFork->hasResFork()) { + // Search for a 'moov' resource + Common::MacResIDArray idArray = _resFork->getResIDArray(MKTAG('m', 'o', 'o', 'v')); + + if (!idArray.empty()) + _fd = _resFork->getResource(MKTAG('m', 'o', 'o', 'v'), 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::parseStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle) { + _fd = stream; + _foundMOOV = false; + _numStreams = 0; + _disposeFileHandle = disposeFileHandle; + + MOVatom atom = { 0, 0, 0xffffffff }; + + if (readDefault(atom) < 0 || !_foundMOOV) { + close(); + 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 scale + for (uint32 i = 0; i < _numStreams; i++) + if (!_streams[i]->time_scale) + _streams[i]->time_scale = _timeScale; +} + +void QuickTimeParser::initParseTable() { + static const ParseTable p[] = { + { &QuickTimeParser::readDefault, MKTAG('d', 'i', 'n', 'f') }, + { &QuickTimeParser::readLeaf, MKTAG('d', 'r', 'e', 'f') }, + { &QuickTimeParser::readDefault, MKTAG('e', 'd', 't', 's') }, + { &QuickTimeParser::readELST, MKTAG('e', 'l', 's', 't') }, + { &QuickTimeParser::readHDLR, MKTAG('h', 'd', 'l', 'r') }, + { &QuickTimeParser::readDefault, MKTAG('m', 'd', 'a', 't') }, + { &QuickTimeParser::readMDHD, MKTAG('m', 'd', 'h', 'd') }, + { &QuickTimeParser::readDefault, MKTAG('m', 'd', 'i', 'a') }, + { &QuickTimeParser::readDefault, MKTAG('m', 'i', 'n', 'f') }, + { &QuickTimeParser::readMOOV, MKTAG('m', 'o', 'o', 'v') }, + { &QuickTimeParser::readMVHD, MKTAG('m', 'v', 'h', 'd') }, + { &QuickTimeParser::readLeaf, MKTAG('s', 'm', 'h', 'd') }, + { &QuickTimeParser::readDefault, MKTAG('s', 't', 'b', 'l') }, + { &QuickTimeParser::readSTCO, MKTAG('s', 't', 'c', 'o') }, + { &QuickTimeParser::readSTSC, MKTAG('s', 't', 's', 'c') }, + { &QuickTimeParser::readSTSD, MKTAG('s', 't', 's', 'd') }, + { &QuickTimeParser::readSTSS, MKTAG('s', 't', 's', 's') }, + { &QuickTimeParser::readSTSZ, MKTAG('s', 't', 's', 'z') }, + { &QuickTimeParser::readSTTS, MKTAG('s', 't', 't', 's') }, + { &QuickTimeParser::readTKHD, MKTAG('t', 'k', 'h', 'd') }, + { &QuickTimeParser::readTRAK, MKTAG('t', 'r', 'a', 'k') }, + { &QuickTimeParser::readLeaf, MKTAG('u', 'd', 't', 'a') }, + { &QuickTimeParser::readLeaf, MKTAG('v', 'm', 'h', 'd') }, + { &QuickTimeParser::readCMOV, MKTAG('c', 'm', 'o', 'v') }, + { &QuickTimeParser::readWAVE, MKTAG('w', 'a', 'v', 'e') }, + { &QuickTimeParser::readESDS, MKTAG('e', 's', 'd', 's') }, + { 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 == MKTAG('m', 'd', 'a', 't') && 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() != MKTAG('d', 'c', 'o', 'm')) + return -1; + if (_fd->readUint32BE() != MKTAG('z', 'l', 'i', 'b')) { + warning("Unknown cmov compression type"); + return -1; + } + + // Read in the cmvd atom + uint32 compressedSize = _fd->readUint32BE() - 12; + if (_fd->readUint32BE() != MKTAG('c', 'm', 'v', 'd')) + 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 = { MKTAG('m', 'o', 'o', 'v'), 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 == MKTAG('m', 'h', 'l', 'r')) // MOV + debug(0, "MOV detected"); + else if (ctype == 0) + debug(0, "MPEG-4 detected"); + + if (type == MKTAG('v', 'i', 'd', 'e')) + st->codec_type = CODEC_TYPE_VIDEO; + else if (type == MKTAG('s', 'o', 'u', 'n')) + 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; +} + +int QuickTimeParser::readSTTS(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + uint32 totalSampleCount = 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[%d].stts.entries = %d", _numStreams - 1, st->stts_count); + + for (int32 i = 0; i < st->stts_count; i++) { + st->stts_data[i].count = _fd->readUint32BE(); + st->stts_data[i].duration = _fd->readUint32BE(); + + debug(1, "\tCount = %d, Duration = %d", st->stts_data[i].count, st->stts_data[i].duration); + + totalSampleCount += st->stts_data[i].count; + } + + st->nb_frames = totalSampleCount; + 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 == MKTAG('Q', 'D', 'M', '2')) // 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; +} + +enum { + kMP4IODescTag = 2, + kMP4ESDescTag = 3, + kMP4DecConfigDescTag = 4, + kMP4DecSpecificDescTag = 5 +}; + +static int readMP4DescLength(Common::SeekableReadStream *stream) { + int length = 0; + int count = 4; + + while (count--) { + byte c = stream->readByte(); + length = (length << 7) | (c & 0x7f); + + if (!(c & 0x80)) + break; + } + + return length; +} + +static void readMP4Desc(Common::SeekableReadStream *stream, byte &tag, int &length) { + tag = stream->readByte(); + length = readMP4DescLength(stream); +} + +int QuickTimeParser::readESDS(MOVatom atom) { + if (_numStreams < 1) + return 0; + + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readUint32BE(); // version + flags + + byte tag; + int length; + + readMP4Desc(_fd, tag, length); + _fd->readUint16BE(); // id + if (tag == kMP4ESDescTag) + _fd->readByte(); // priority + + // Check if we've got the Config MPEG-4 header + readMP4Desc(_fd, tag, length); + if (tag != kMP4DecConfigDescTag) + return 0; + + st->objectTypeMP4 = _fd->readByte(); + _fd->readByte(); // stream type + _fd->readUint16BE(); _fd->readByte(); // buffer size + _fd->readUint32BE(); // max bitrate + _fd->readUint32BE(); // avg bitrate + + // Check if we've got the Specific MPEG-4 header + readMP4Desc(_fd, tag, length); + if (tag != kMP4DecSpecificDescTag) + return 0; + + st->extradata = _fd->readStream(length); + + debug(0, "MPEG-4 object type = %02x", st->objectTypeMP4); + return 0; +} + +void QuickTimeParser::close() { + for (uint32 i = 0; i < _numStreams; i++) + delete _streams[i]; + + _numStreams = 0; + + if (_disposeFileHandle == DisposeAfterUse::YES) + 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; + width = 0; + height = 0; + codec_type = CODEC_TYPE_MOV_OTHER; + editCount = 0; + editList = 0; + extradata = 0; + nb_frames = 0; + duration = 0; + start_time = 0; + objectTypeMP4 = 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..a5903bc0f6 --- /dev/null +++ b/common/quicktime.h @@ -0,0 +1,208 @@ +/* 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/MPEG-4 files. + * + * File parser used in engines: + * - groovie + * - mohawk + * - sci + */ +class QuickTimeParser { +public: + QuickTimeParser(); + virtual ~QuickTimeParser(); + + /** + * Load a QuickTime file + * @param filename the filename to load + */ + bool parseFile(const Common::String &filename); + + /** + * Load a QuickTime file from a SeekableReadStream + * @param stream the stream to load + * @param disposeFileHandle whether to delete the stream after use + */ + bool parseStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle = DisposeAfterUse::YES); + + /** + * 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; + + DisposeAfterUse::Flag _disposeFileHandle; + + 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; + + 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; + + byte objectTypeMP4; + }; + + 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); + int readESDS(MOVatom atom); +}; + +} // End of namespace Common + +#endif diff --git a/common/util.cpp b/common/util.cpp index eed7009f90..a7ec1a9de7 100644 --- a/common/util.cpp +++ b/common/util.cpp @@ -211,6 +211,7 @@ const PlatformDescription g_platforms[] = { { "windows", "win", "win", "Windows", kPlatformWindows }, { "playstation", "psx", "psx", "Sony PlayStation", kPlatformPSX }, { "cdi", "cdi", "cdi", "Philips CD-i", kPlatformCDi }, + { "ios", "ios", "ios", "Apple iOS", kPlatformIOS }, { 0, 0, 0, "Default", kPlatformUnknown } }; diff --git a/common/util.h b/common/util.h index 5d965c4d31..5837c8beab 100644 --- a/common/util.h +++ b/common/util.h @@ -172,6 +172,7 @@ enum Platform { kPlatformWii, kPlatformPSX, kPlatformCDi, + kPlatformIOS, kPlatformUnknown = -1 }; @@ -133,6 +133,7 @@ _zlib=auto _mpeg2=no _png=auto _theoradec=auto +_faad=auto _fluidsynth=auto _opengl=auto _opengles=auto @@ -757,6 +758,9 @@ Optional Libraries: --with-theoradec-prefix=DIR Prefix where libtheoradec is installed (optional) --disable-theoradec disable Theora decoder [autodetect] + --with-faad-prefix=DIR Prefix where libfaad is installed (optional) + --disable-faad disable AAC decoder [autodetect] + --with-fluidsynth-prefix=DIR Prefix where libfluidsynth is installed (optional) --disable-fluidsynth disable fluidsynth MIDI driver [autodetect] @@ -813,6 +817,8 @@ for ac_option in $@; do --enable-png) _png=yes ;; --disable-theoradec) _theoradec=no ;; --enable-theoradec) _theoradec=yes ;; + --disable-faad) _faad=no ;; + --enable-faad) _faad=yes ;; --disable-fluidsynth) _fluidsynth=no ;; --enable-readline) _readline=yes ;; --disable-readline) _readline=no ;; @@ -881,6 +887,11 @@ for ac_option in $@; do THEORADEC_CFLAGS="-I$arg/include" THEORADEC_LIBS="-L$arg/lib" ;; + --with-faad-prefix=*) + arg=`echo $ac_option | cut -d '=' -f 2` + FAAD_CFLAGS="-I$arg/include" + FAAD_LIBS="-L$arg/lib" + ;; --with-zlib-prefix=*) arg=`echo $ac_option | cut -d '=' -f 2` ZLIB_CFLAGS="-I$arg/include" @@ -2786,6 +2797,25 @@ if test ! "$_theoradec" = notsupported ; then fi # +# Check for the AAC decoder +# +echocheck "libfaad" +if test "$_faad" = auto ; then + _faad=no + cat > $TMPC << EOF +#include <neaacdec.h> +int main(void) { NeAACDecGetCapabilities(); return 0; } +EOF + cc_check $FAAD_CFLAGS $FAAD_LIBS -lfaad && _faad=yes +fi +if test "$_faad" = yes ; then + LIBS="$LIBS $FAAD_LIBS -lfaad" + INCLUDES="$INCLUDES $FAAD_CFLAGS" +fi +define_in_config_if_yes "$_faad" 'USE_FAAD' +echo "$_faad" + +# # Check for SEQ MIDI # echocheck "SEQ MIDI" diff --git a/engines/groovie/detection.cpp b/engines/groovie/detection.cpp index 78ecac8dbb..0dd510abca 100644 --- a/engines/groovie/detection.cpp +++ b/engines/groovie/detection.cpp @@ -111,6 +111,20 @@ static const GroovieGameDescription gameDescriptions[] = { kGroovieT7G, 0 }, + { + { + "t7g", "", + { + { "script.grv", 0, "d1b8033b40aa67c076039881eccce90d", 16659}, + { "SeventhGuest", 0, NULL, -1}, + { NULL, 0, NULL, 0} + }, + Common::EN_ANY, Common::kPlatformIOS, ADGF_NO_FLAGS, + Common::GUIO_NOMIDI + }, + kGroovieT7G, 0 + }, + #ifdef ENABLE_GROOVIE2 // The 11th Hour DOS English { diff --git a/engines/groovie/groovie.cpp b/engines/groovie/groovie.cpp index 508049e1a0..8b6ee2ca42 100644 --- a/engines/groovie/groovie.cpp +++ b/engines/groovie/groovie.cpp @@ -54,6 +54,15 @@ GroovieEngine::GroovieEngine(OSystem *syst, const GroovieGameDescription *gd) : SearchMan.addSubDirectoryMatching(gameDataDir, "media"); SearchMan.addSubDirectoryMatching(gameDataDir, "system"); + _modeSpeed = kGroovieSpeedNormal; + if (ConfMan.hasKey("t7g_speed")) { + Common::String speed = ConfMan.get("t7g_speed"); + if (speed.equals("im_an_ios")) + _modeSpeed = kGroovieSpeediOS; + else if (speed.equals("tweaked")) + _modeSpeed = kGroovieSpeedTweaked; + } + // Initialize the custom debug levels DebugMan.addDebugChannel(kGroovieDebugAll, "All", "Debug everything"); DebugMan.addDebugChannel(kGroovieDebugVideo, "Video", "Debug video and audio playback"); @@ -141,10 +150,20 @@ Common::Error GroovieEngine::run() { } // Create the music player - if (getPlatform() == Common::kPlatformMacintosh) + switch (getPlatform()) { + case Common::kPlatformMacintosh: + // TODO: The 11th Hour Mac uses QuickTime MIDI files + // Right now, since the XMIDI are present and it is still detected as + // the DOS version, we don't have to do anything here. _musicPlayer = new MusicPlayerMac(this); - else + break; + case Common::kPlatformIOS: + _musicPlayer = new MusicPlayerIOS(this); + break; + default: _musicPlayer = new MusicPlayerXMI(this, _gameDescription->version == kGroovieT7G ? "fat" : "sample"); + break; + } // Load volume levels syncSoundSettings(); @@ -207,17 +226,19 @@ Common::Error GroovieEngine::run() { _script->directGameLoad(slot); } - // Check that the game files and the audio tracks aren't together run from - // the same cd - checkCD(); - // Game timer counter uint16 tmr = 0; - // Initialize the CD - int cd_num = ConfMan.getInt("cdrom"); - if (cd_num >= 0) - _system->getAudioCDManager()->openCD(cd_num); + // Check that the game files and the audio tracks aren't together run from + // the same cd + if (getPlatform() != Common::kPlatformIOS) { + checkCD(); + + // Initialize the CD + int cd_num = ConfMan.getInt("cdrom"); + if (cd_num >= 0) + _system->getAudioCDManager()->openCD(cd_num); + } while (!shouldQuit()) { // Give the debugger a chance to act diff --git a/engines/groovie/groovie.h b/engines/groovie/groovie.h index 0ac9c4b956..eb64cfc60a 100644 --- a/engines/groovie/groovie.h +++ b/engines/groovie/groovie.h @@ -72,6 +72,12 @@ enum DebugLevels { // the current limitation is 32 debug levels (1 << 31 is the last one) }; +enum GameSpeed { + kGroovieSpeedNormal, + kGroovieSpeediOS, + kGroovieSpeedTweaked +}; + struct GroovieGameDescription; class GroovieEngine : public Engine { @@ -110,6 +116,8 @@ public: Common::MacResManager *_macResFork; + GameSpeed _modeSpeed; + private: const GroovieGameDescription *_gameDescription; Debugger *_debugger; diff --git a/engines/groovie/music.cpp b/engines/groovie/music.cpp index b8a78d9f56..9a3903e9dc 100644 --- a/engines/groovie/music.cpp +++ b/engines/groovie/music.cpp @@ -31,6 +31,7 @@ #include "common/macresman.h" #include "common/memstream.h" #include "common/textconsole.h" +#include "audio/audiostream.h" #include "audio/midiparser.h" namespace Groovie { @@ -92,6 +93,7 @@ void MusicPlayer::playCD(uint8 track) { } else if ((track == 98) && (_prevCDtrack == 3)) { // Track 98 is used as a hack to stop the credits song g_system->getAudioCDManager()->stop(); + stopCreditsIOS(); return; } @@ -124,6 +126,8 @@ void MusicPlayer::playCD(uint8 track) { playSong((19 << 10) | 36); // XMI.GJD, file 36 } else if (track == 3) { // TODO: Credits MIDI fallback + if (_vm->getPlatform() == Common::kPlatformIOS) + playCreditsIOS(); } } } @@ -224,6 +228,20 @@ void MusicPlayer::unload() { _isPlaying = false; } +void MusicPlayer::playCreditsIOS() { + Audio::AudioStream *stream = Audio::SeekableAudioStream::openStreamFile("7th_Guest_Dolls_from_Hell_OC_ReMix"); + + if (!stream) { + warning("Could not find '7th_Guest_Dolls_from_Hell_OC_ReMix' audio file"); + return; + } + + _vm->_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, &_handleCreditsIOS, stream); +} + +void MusicPlayer::stopCreditsIOS() { + _vm->_system->getMixer()->stopHandle(_handleCreditsIOS); +} // MusicPlayerMidi @@ -747,4 +765,89 @@ Common::SeekableReadStream *MusicPlayerMac::decompressMidi(Common::SeekableReadS return new Common::MemoryReadStream(output, size, DisposeAfterUse::YES); } +MusicPlayerIOS::MusicPlayerIOS(GroovieEngine *vm) : MusicPlayer(vm) { + vm->getTimerManager()->installTimerProc(&onTimer, 50 * 1000, this); +} + +MusicPlayerIOS::~MusicPlayerIOS() { + _vm->getTimerManager()->removeTimerProc(&onTimer); +} + +void MusicPlayerIOS::updateVolume() { + // Just set the mixer volume for the music sound type + _vm->_system->getMixer()->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, _userVolume * _gameVolume / 100); +} + +void MusicPlayerIOS::unload() { + MusicPlayer::unload(); + + _vm->_system->getMixer()->stopHandle(_handle); +} + +bool MusicPlayerIOS::load(uint32 fileref, bool loop) { + // Find correct filename + ResInfo info; + _vm->_resMan->getResInfo(fileref, info); + uint len = info.filename.size(); + if (len < 4) + return false; // This shouldn't actually occur + /* + 19462 door + 19463 ?? + 19464 ?? + 19465 puzzle? + 19466 cake + 19467 maze + 19468 ambient (but not 69, amb b. odd) + 19470 puzzle + 19471 + 19473 + 19475 coffins or blood pump + 19476 blood pump or coffins + 19493 + 19499 chapel + 19509 downstair ambient + 19510 bedroom 'skip 3 and 5' puzzle (should loop from partway?) + 19514 + 19515 bathroom drain teeth + */ + if ((fileref >= 19462 && fileref <= 19468) || + fileref == 19470 || fileref == 19471 || + fileref == 19473 || fileref == 19475 || + fileref == 19476 || fileref == 19493 || + fileref == 19499 || fileref == 19509 || + fileref == 19510 || fileref == 19514 || + fileref == 19515) + loop = true; // XMIs for these refs self-loop + + // iOS port provides alternative intro sequence music + if (info.filename == "gu39.xmi") { + info.filename = "intro"; + } else if (info.filename == "gu32.xmi") { + info.filename = "foyer"; + } else { + // Remove the extension + info.filename.deleteLastChar(); + info.filename.deleteLastChar(); + info.filename.deleteLastChar(); + info.filename.deleteLastChar(); + } + + // Create the audio stream + Audio::AudioStream *audStream = Audio::SeekableAudioStream::openStreamFile(info.filename); + + if (!audStream) { + warning("Could not play audio file '%s'", info.filename.c_str()); + return false; + } + + // Loop if requested + if (loop) + audStream = Audio::makeLoopingAudioStream((Audio::RewindableAudioStream *)audStream, 0); + + // Play! + _vm->_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, &_handle, audStream); + return true; +} + } // End of Groovie namespace diff --git a/engines/groovie/music.h b/engines/groovie/music.h index fa4150a83b..7af482e45d 100644 --- a/engines/groovie/music.h +++ b/engines/groovie/music.h @@ -26,6 +26,7 @@ #include "common/array.h" #include "common/mutex.h" #include "audio/mididrv.h" +#include "audio/mixer.h" class MidiParser; @@ -59,6 +60,11 @@ private: uint16 _backgroundDelay; + // T7G iOS credits mp3 stream + void playCreditsIOS(); + void stopCreditsIOS(); + Audio::SoundHandle _handleCreditsIOS; + // Volume fading uint32 _fadingStartTime; uint16 _fadingStartVolume; @@ -157,6 +163,20 @@ private: Common::SeekableReadStream *decompressMidi(Common::SeekableReadStream *stream); }; +class MusicPlayerIOS : public MusicPlayer { +public: + MusicPlayerIOS(GroovieEngine *vm); + ~MusicPlayerIOS(); + +protected: + void updateVolume(); + bool load(uint32 fileref, bool loop); + void unload(); + +private: + Audio::SoundHandle _handle; +}; + } // End of Groovie namespace #endif // GROOVIE_MUSIC_H diff --git a/engines/groovie/player.cpp b/engines/groovie/player.cpp index 11318d2e94..e2a1ff3d56 100644 --- a/engines/groovie/player.cpp +++ b/engines/groovie/player.cpp @@ -28,18 +28,19 @@ namespace Groovie { VideoPlayer::VideoPlayer(GroovieEngine *vm) : - _vm(vm), _syst(vm->_system), _file(NULL), _audioStream(NULL) { + _vm(vm), _syst(vm->_system), _file(NULL), _audioStream(NULL), _fps(0), _overrideSpeed(false) { } bool VideoPlayer::load(Common::SeekableReadStream *file, uint16 flags) { _file = file; _flags = flags; + _overrideSpeed = false; _audioStream = NULL; - uint16 fps = loadInternal(); + _fps = loadInternal(); - if (fps != 0) { - _millisBetweenFrames = 1000 / fps; + if (_fps != 0) { + setOverrideSpeed(_overrideSpeed); _begunPlaying = false; return true; } else { @@ -48,6 +49,16 @@ bool VideoPlayer::load(Common::SeekableReadStream *file, uint16 flags) { } } +void VideoPlayer::setOverrideSpeed(bool isOverride) { + _overrideSpeed = isOverride; + if (_fps != 0) { + if (isOverride) + _millisBetweenFrames = 1000 / 26; + else + _millisBetweenFrames = 1000 / _fps; + } +} + bool VideoPlayer::playFrame() { bool end = true; diff --git a/engines/groovie/player.h b/engines/groovie/player.h index c6d927f2c0..d8135a99b2 100644 --- a/engines/groovie/player.h +++ b/engines/groovie/player.h @@ -45,15 +45,21 @@ protected: virtual uint16 loadInternal() = 0; virtual bool playFrameInternal() = 0; + void setOverrideSpeed(bool isOverride); + bool getOverrideSpeed() const { return _overrideSpeed; } + GroovieEngine *_vm; OSystem *_syst; Common::SeekableReadStream *_file; uint16 _flags; Audio::QueuingAudioStream *_audioStream; - + + private: // Synchronization stuff bool _begunPlaying; + bool _overrideSpeed; + uint16 _fps; uint16 _millisBetweenFrames; uint32 _lastFrameTime; diff --git a/engines/groovie/resource.cpp b/engines/groovie/resource.cpp index 05359342f8..10cf24a589 100644 --- a/engines/groovie/resource.cpp +++ b/engines/groovie/resource.cpp @@ -173,6 +173,7 @@ bool ResMan_t7g::getResInfo(uint32 fileRef, ResInfo &resInfo) { char resname[12]; rlFile->read(resname, 12); debugC(2, kGroovieDebugResource | kGroovieDebugAll, "Groovie::Resource: Resource name: %12s", resname); + resInfo.filename = resname; // Read the resource information resInfo.offset = rlFile->readUint32LE(); @@ -281,6 +282,7 @@ bool ResMan_v2::getResInfo(uint32 fileRef, ResInfo &resInfo) { char resname[12]; rlFile.read(resname, 12); debugC(2, kGroovieDebugResource | kGroovieDebugAll, "Groovie::Resource: Resource name: %12s", resname); + resInfo.filename = resname; // 6 padding bytes? (it looks like they're always 0) diff --git a/engines/groovie/resource.h b/engines/groovie/resource.h index 2c215917cc..33e15e6b98 100644 --- a/engines/groovie/resource.h +++ b/engines/groovie/resource.h @@ -33,6 +33,7 @@ struct ResInfo { uint16 gjd; uint32 offset; uint32 size; + Common::String filename; }; class ResMan { @@ -40,11 +41,12 @@ public: virtual ~ResMan() {} Common::SeekableReadStream *open(uint32 fileRef); + virtual uint32 getRef(Common::String name, Common::String scriptname = "") = 0; + virtual bool getResInfo(uint32 fileRef, ResInfo &resInfo) = 0; protected: Common::Array<Common::String> _gjds; - virtual bool getResInfo(uint32 fileRef, ResInfo &resInfo) = 0; uint16 _lastGjd; }; diff --git a/engines/groovie/script.cpp b/engines/groovie/script.cpp index 3bd90a042e..b52a8723fc 100644 --- a/engines/groovie/script.cpp +++ b/engines/groovie/script.cpp @@ -65,8 +65,9 @@ static void debugScript(int level, bool nl, const char *s, ...) { Script::Script(GroovieEngine *vm, EngineVersion version) : _code(NULL), _savedCode(NULL), _stacktop(0), _debugger(NULL), _vm(vm), - _videoFile(NULL), _videoRef(0), _staufsMove(NULL), - _random("GroovieScripts") { + _videoFile(NULL), _videoRef(0), _staufsMove(NULL), _lastCursor(0xff), + _version(version), _random("GroovieScripts") { + // Initialize the opcode set depending on the engine version switch (version) { case kGroovieT7G: @@ -385,6 +386,7 @@ bool Script::hotspot(Common::Rect rect, uint16 address, uint8 cursor) { // If clicked with the mouse, jump to the specified address if (_mouseClicked) { + _lastCursor = cursor; _inputAction = address; } } @@ -584,6 +586,10 @@ bool Script::playvideofromref(uint32 fileref) { if (_videoFile) { _videoRef = fileref; + // If teeth cursor, and in main script, mark video prefer low-speed + // filename check as sometimes teeth used for puzzle movements (bishops) + if (_version == kGroovieT7G && _lastCursor == 7 && _scriptFile == "script.grv") + _bitflags |= (1 << 15); _vm->_videoPlayer->load(_videoFile, _bitflags); } else { error("Couldn't open file"); diff --git a/engines/groovie/script.h b/engines/groovie/script.h index 95da96487e..8cd790af5e 100644 --- a/engines/groovie/script.h +++ b/engines/groovie/script.h @@ -72,6 +72,9 @@ private: Common::RandomSource _random; bool _firstbit; + uint8 _lastCursor; + + EngineVersion _version; // Script filename (for debugging purposes) Common::String _scriptFile; diff --git a/engines/groovie/vdx.cpp b/engines/groovie/vdx.cpp index ae1e9eebb3..b3fcf462b2 100644 --- a/engines/groovie/vdx.cpp +++ b/engines/groovie/vdx.cpp @@ -86,6 +86,11 @@ uint16 VDXPlayer::loadInternal() { _flagEight = ((_flags & (1 << 8)) != 0); _flagNine = ((_flags & (1 << 9)) != 0); + // Enable highspeed if we're not obeying fps, and not marked as special + // This will be disabled in chunk audio if we're actually an audio vdx + if ( _vm->_modeSpeed == kGroovieSpeediOS || (_vm->_modeSpeed == kGroovieSpeedTweaked && ((_flags & (1 << 15)) == 0))) + setOverrideSpeed(true); + if (_flagOnePrev && !_flagOne && !_flagEight) { _flagSeven = true; } @@ -522,6 +527,9 @@ void VDXPlayer::decodeBlockDelta(uint32 offset, byte *colors, uint16 imageWidth) } void VDXPlayer::chunkSound(Common::ReadStream *in) { + if (getOverrideSpeed()) + setOverrideSpeed(false); + if (!_audioStream) { _audioStream = Audio::makeQueuingAudioStream(22050, false); Audio::SoundHandle sound_handle; @@ -104,6 +104,10 @@ ifdef USE_THEORADEC OSX_STATIC_LIBS += $(STATICLIBPATH)/lib/libtheoradec.a endif +ifdef USE_FAAD +OSX_STATIC_LIBS += $(STATICLIBPATH)/lib/libfaad.a +endif + ifdef USE_ZLIB OSX_ZLIB ?= -lz endif diff --git a/video/module.mk b/video/module.mk index 0172482dfa..308b344a75 100644 --- a/video/module.mk +++ b/video/module.mk @@ -15,7 +15,6 @@ MODULE_OBJS := \ codecs/mjpeg.o \ codecs/msrle.o \ codecs/msvideo1.o \ - codecs/qdm2.o \ codecs/qtrle.o \ codecs/rpza.o \ codecs/smc.o \ diff --git a/video/qt_decoder.cpp b/video/qt_decoder.cpp index 2d29bcf97b..328c3fb21b 100644 --- a/video/qt_decoder.cpp +++ b/video/qt_decoder.cpp @@ -21,7 +21,7 @@ */ // -// Heavily based on ffmpeg code. +// Partially based on ffmpeg code. // // Copyright (c) 2001 Fabrice Bellard. // First version by Francois Revol revol@free.fr @@ -30,32 +30,19 @@ #include "video/qt_decoder.h" +#include "audio/audiostream.h" + #include "common/debug.h" #include "common/endian.h" -#include "common/macresman.h" #include "common/memstream.h" -#include "common/util.h" -#include "common/zlib.h" -#include "common/stream.h" #include "common/system.h" #include "common/textconsole.h" -#include "common/types.h" - -#include "graphics/pixelformat.h" -#include "graphics/surface.h" - - -#include "audio/audiostream.h" - -// Audio codecs -#include "audio/decoders/adpcm.h" -#include "audio/decoders/raw.h" +#include "common/util.h" // Video codecs #include "video/codecs/codec.h" #include "video/codecs/cinepak.h" #include "video/codecs/mjpeg.h" -#include "video/codecs/qdm2.h" #include "video/codecs/qtrle.h" #include "video/codecs/rpza.h" #include "video/codecs/smc.h" @@ -69,26 +56,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 { @@ -194,65 +171,16 @@ void QuickTimeDecoder::seekToFrame(uint32 frame) { if (_audioStreamIndex >= 0) { _audioStartOffset = curVideoTime; - // Re-create the audio stream - STSDEntry *entry = &_streams[_audioStreamIndex]->stsdEntries[0]; - _audStream = Audio::makeQueuingAudioStream(entry->sampleRate, entry->channels == 2); - - // First, we need to track down what audio sample we need - Audio::Timestamp curAudioTime(0, _streams[_audioStreamIndex]->time_scale); - uint sample = 0; - bool done = false; - for (int32 i = 0; i < _streams[_audioStreamIndex]->stts_count && !done; i++) { - for (int32 j = 0; j < _streams[_audioStreamIndex]->stts_data[i].count; j++) { - curAudioTime = curAudioTime.addFrames(_streams[_audioStreamIndex]->stts_data[i].duration); - - if (curAudioTime > curVideoTime) { - done = true; - break; - } - - sample++; - } - } - - // Now to track down what chunk it's in - _curAudioChunk = 0; - uint32 totalSamples = 0; - for (uint32 i = 0; i < _streams[_audioStreamIndex]->chunk_count; i++, _curAudioChunk++) { - int sampleToChunkIndex = -1; + // Seek to the new audio location + setAudioStreamPos(_audioStartOffset); - for (uint32 j = 0; j < _streams[_audioStreamIndex]->sample_to_chunk_sz; j++) - if (i >= _streams[_audioStreamIndex]->sample_to_chunk[j].first) - sampleToChunkIndex = j; - - assert(sampleToChunkIndex >= 0); - - totalSamples += _streams[_audioStreamIndex]->sample_to_chunk[sampleToChunkIndex].count; - - if (sample < totalSamples) { - totalSamples -= _streams[_audioStreamIndex]->sample_to_chunk[sampleToChunkIndex].count; - break; - } - } - - // Reposition the audio stream - readNextAudioChunk(); - if (sample != totalSamples) { - // HACK: Skip a certain amount of samples from the stream - // (There's got to be a better way to do this!) - int16 *tempBuffer = new int16[sample - totalSamples]; - _audStream->readBuffer(tempBuffer, sample - totalSamples); - delete[] tempBuffer; - debug(3, "Skipping %d audio samples", sample - totalSamples); - } - // Restart the audio startAudio(); } } void QuickTimeDecoder::seekToTime(Audio::Timestamp time) { - // TODO: Audio-only seeking (or really, have QuickTime sounds) + // Use makeQuickTimeStream() instead if (_videoStreamIndex < 0) error("Audio-only seeking not supported"); @@ -308,17 +236,15 @@ Codec *QuickTimeDecoder::createCodec(uint32 codecTag, byte bitsPerPixel) { } void QuickTimeDecoder::startAudio() { - if (_audStream) { // No audio/audio not supported + if (_audStream) { updateAudioBuffer(); - g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_audHandle, _audStream); - } + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_audHandle, _audStream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO); + } // else no audio or the audio compression is not supported } void QuickTimeDecoder::stopAudio() { - if (_audStream) { + if (_audStream) g_system->getMixer()->stopHandle(_audHandle); - _audStream = NULL; // the mixer automatically frees the stream - } } void QuickTimeDecoder::pauseVideoIntern(bool pause) { @@ -327,10 +253,10 @@ void QuickTimeDecoder::pauseVideoIntern(bool pause) { } Codec *QuickTimeDecoder::findDefaultVideoCodec() const { - if (_videoStreamIndex < 0 || !_streams[_videoStreamIndex]->stsdEntryCount) + if (_videoStreamIndex < 0 || _streams[_videoStreamIndex]->sampleDescs.empty()) return 0; - return _streams[_videoStreamIndex]->stsdEntries[0].videoCodec; + return ((VideoSampleDesc *)_streams[_videoStreamIndex]->sampleDescs[0])->videoCodec; } const Graphics::Surface *QuickTimeDecoder::decodeNextFrame() { @@ -350,11 +276,11 @@ const Graphics::Surface *QuickTimeDecoder::decodeNextFrame() { uint32 descId; Common::SeekableReadStream *frameData = getNextFramePacket(descId); - if (!frameData || !descId || descId > _streams[_videoStreamIndex]->stsdEntryCount) + if (!frameData || !descId || descId > _streams[_videoStreamIndex]->sampleDescs.size()) return 0; // Find which video description entry we want - STSDEntry *entry = &_streams[_videoStreamIndex]->stsdEntries[descId - 1]; + VideoSampleDesc *entry = (VideoSampleDesc *)_streams[_videoStreamIndex]->sampleDescs[descId - 1]; if (!entry->videoCodec) return 0; @@ -421,38 +347,7 @@ uint32 QuickTimeDecoder::getTimeToNextFrame() const { } bool QuickTimeDecoder::loadFile(const Common::String &filename) { - if (!_resFork->open(filename) || !_resFork->hasDataFork()) - 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(MKTAG('m','o','o','v')); - - if (!idArray.empty()) - _fd = _resFork->getResource(MKTAG('m','o','o','v'), 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) + if (!Common::QuickTimeParser::parseFile(filename)) return false; init(); @@ -460,75 +355,34 @@ bool QuickTimeDecoder::loadFile(const Common::String &filename) { } 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::parseStream(stream)) return false; - } 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++; - } - - // 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; + Audio::QuickTimeAudioDecoder::init(); - sc->duration /= sc->time_rate; + _videoStreamIndex = -1; + _startTime = 0; - if (sc->codec_type == CODEC_TYPE_VIDEO && _videoStreamIndex < 0) + // Find video streams + for (uint32 i = 0; i < _numStreams; i++) + if (_streams[i]->codec_type == CODEC_TYPE_VIDEO && _videoStreamIndex < 0) _videoStreamIndex = i; - else if (sc->codec_type == CODEC_TYPE_AUDIO && _audioStreamIndex < 0) - _audioStreamIndex = i; - } - - // Initialize audio, if present - if (_audioStreamIndex >= 0) { - STSDEntry *entry = &_streams[_audioStreamIndex]->stsdEntries[0]; - - if (checkAudioCodecSupport(entry->codecTag)) { - _audStream = Audio::makeQueuingAudioStream(entry->sampleRate, entry->channels == 2); - _curAudioChunk = 0; - - // Make sure the bits per sample transfers to the sample size - if (entry->codecTag == MKTAG('r','a','w',' ') || entry->codecTag == MKTAG('t','w','o','s')) - _streams[_audioStreamIndex]->sample_size = (entry->bitsPerSample / 8) * entry->channels; - - startAudio(); - } + // Start the audio codec if we've got one that we can handle + if (_audStream) { + startAudio(); _audioStartOffset = Audio::Timestamp(0); } // Initialize video, if present if (_videoStreamIndex >= 0) { - for (uint32 i = 0; i < _streams[_videoStreamIndex]->stsdEntryCount; i++) { - STSDEntry *entry = &_streams[_videoStreamIndex]->stsdEntries[i]; + for (uint32 i = 0; i < _streams[_videoStreamIndex]->sampleDescs.size(); i++) { + VideoSampleDesc *entry = (VideoSampleDesc *)_streams[_videoStreamIndex]->sampleDescs[i]; entry->videoCodec = createCodec(entry->codecTag, entry->bitsPerSample & 0x1F); } @@ -540,734 +394,115 @@ void QuickTimeDecoder::init() { } } -void QuickTimeDecoder::initParseTable() { - static const ParseTable p[] = { - { &QuickTimeDecoder::readDefault, MKTAG('d','i','n','f') }, - { &QuickTimeDecoder::readLeaf, MKTAG('d','r','e','f') }, - { &QuickTimeDecoder::readDefault, MKTAG('e','d','t','s') }, - { &QuickTimeDecoder::readELST, MKTAG('e','l','s','t') }, - { &QuickTimeDecoder::readHDLR, MKTAG('h','d','l','r') }, - { &QuickTimeDecoder::readDefault, MKTAG('m','d','a','t') }, - { &QuickTimeDecoder::readMDHD, MKTAG('m','d','h','d') }, - { &QuickTimeDecoder::readDefault, MKTAG('m','d','i','a') }, - { &QuickTimeDecoder::readDefault, MKTAG('m','i','n','f') }, - { &QuickTimeDecoder::readMOOV, MKTAG('m','o','o','v') }, - { &QuickTimeDecoder::readMVHD, MKTAG('m','v','h','d') }, - { &QuickTimeDecoder::readLeaf, MKTAG('s','m','h','d') }, - { &QuickTimeDecoder::readDefault, MKTAG('s','t','b','l') }, - { &QuickTimeDecoder::readSTCO, MKTAG('s','t','c','o') }, - { &QuickTimeDecoder::readSTSC, MKTAG('s','t','s','c') }, - { &QuickTimeDecoder::readSTSD, MKTAG('s','t','s','d') }, - { &QuickTimeDecoder::readSTSS, MKTAG('s','t','s','s') }, - { &QuickTimeDecoder::readSTSZ, MKTAG('s','t','s','z') }, - { &QuickTimeDecoder::readSTTS, MKTAG('s','t','t','s') }, - { &QuickTimeDecoder::readTKHD, MKTAG('t','k','h','d') }, - { &QuickTimeDecoder::readTRAK, MKTAG('t','r','a','k') }, - { &QuickTimeDecoder::readLeaf, MKTAG('u','d','t','a') }, - { &QuickTimeDecoder::readLeaf, MKTAG('v','m','h','d') }, - { &QuickTimeDecoder::readCMOV, MKTAG('c','m','o','v') }, - { &QuickTimeDecoder::readWAVE, MKTAG('w','a','v','e') }, - { 0, 0 } - }; - - _parseTable = p; -} - -int QuickTimeDecoder::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 == MKTAG('m','d','a','t') && 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 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() != MKTAG('d','c','o','m')) - return -1; - if (_fd->readUint32BE() != MKTAG('z','l','i','b')) { - warning("Unknown cmov compression type"); - return -1; - } - - // Read in the cmvd atom - uint32 compressedSize = _fd->readUint32BE() - 12; - if (_fd->readUint32BE() != MKTAG('c','m','v','d')) - 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 = { MKTAG('m','o','o','v'), 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->readUint32LE(); - uint32 type = _fd->readUint32BE(); // component subtype - - debug(0, "ctype= %s (0x%08lx)", tag2str(ctype), (long)ctype); - debug(0, "stype= %s", tag2str(type)); - - if(ctype == MKTAG('m','h','l','r')) // MOV - debug(0, "MOV detected"); - else if(ctype == 0) { - warning("MP4 streams are not supported"); - return -1; - } - - if (type == MKTAG('v','i','d','e')) - st->codec_type = CODEC_TYPE_VIDEO; - else if (type == MKTAG('s','o','u','n')) - 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 - - st->stsdEntryCount = _fd->readUint32BE(); - st->stsdEntries = new STSDEntry[st->stsdEntryCount]; - - for (uint32 i = 0; i < st->stsdEntryCount; i++) { // Parsing Sample description table - STSDEntry *entry = &st->stsdEntries[i]; - - 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); +Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(MOVStreamContext *st, uint32 format) { + if (st->codec_type == CODEC_TYPE_VIDEO) { + debug(0, "Video Codec FourCC: \'%s\'", tag2str(format)); + VideoSampleDesc *entry = new VideoSampleDesc(); entry->codecTag = format; - if (st->codec_type == CODEC_TYPE_VIDEO) { - debug(0, "Video Codec FourCC: \'%s\'", tag2str(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; - } + _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)); - - 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 == MKTAG('i','m','a','4')) { - 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; - } else { - // 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 entry; } - 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]; - - 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 QuickTimeDecoder::readWAVE(MOVatom atom) { - if (_numStreams < 1) - return 0; - - MOVStreamContext *st = _streams[_numStreams - 1]; - - if (atom.size > (1 << 30)) - return -1; - - if (st->stsdEntries[0].codecTag == MKTAG('Q','D','M','2')) // 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; + // Pass it on up + return Audio::QuickTimeAudioDecoder::readSampleDesc(st, format); } void QuickTimeDecoder::close() { stopAudio(); - for (uint32 i = 0; i < _numStreams; i++) - delete _streams[i]; - - delete _fd; - _fd = 0; - if (_scaledSurface) { _scaledSurface->free(); delete _scaledSurface; _scaledSurface = 0; } - // The audio stream is deleted automatically - _audStream = NULL; - + Common::QuickTimeParser::close(); SeekableVideoDecoder::reset(); } @@ -1325,120 +560,20 @@ Common::SeekableReadStream *QuickTimeDecoder::getNextFramePacket(uint32 &descId) return _fd->readStream(_streams[_videoStreamIndex]->sample_sizes[getCurFrame()]); } -bool QuickTimeDecoder::checkAudioCodecSupport(uint32 tag) { - // Check if the codec is a supported codec - if (tag == MKTAG('t','w','o','s') || tag == MKTAG('r','a','w',' ') || tag == MKTAG('i','m','a','4')) - return true; - -#ifdef VIDEO_CODECS_QDM2_H - if (tag == MKTAG('Q','D','M','2')) - return true; -#endif - - warning("Audio Codec Not Supported: \'%s\'", tag2str(tag)); - - return false; -} - -Audio::AudioStream *QuickTimeDecoder::createAudioStream(Common::SeekableReadStream *stream) { - if (!stream || _audioStreamIndex < 0) - return NULL; - - STSDEntry *entry = &_streams[_audioStreamIndex]->stsdEntries[0]; - - if (entry->codecTag == MKTAG('t','w','o','s') || entry->codecTag == MKTAG('r','a','w',' ')) { - // Fortunately, most of the audio used in Myst videos is raw... - uint16 flags = 0; - if (entry->codecTag == MKTAG('r','a','w',' ')) - flags |= Audio::FLAG_UNSIGNED; - if (entry->channels == 2) - flags |= Audio::FLAG_STEREO; - if (entry->bitsPerSample == 16) - flags |= Audio::FLAG_16BITS; - uint32 dataSize = stream->size(); - byte *data = (byte *)malloc(dataSize); - stream->read(data, dataSize); - delete stream; - return Audio::makeRawStream(data, dataSize, entry->sampleRate, flags); - } else if (entry->codecTag == MKTAG('i','m','a','4')) { - // Riven uses this codec (as do some Myst ME videos) - return Audio::makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), Audio::kADPCMApple, entry->sampleRate, entry->channels, 34); -#ifdef VIDEO_CODECS_QDM2_H - } else if (entry->codecTag == MKTAG('Q','D','M','2')) { - // Several Myst ME videos use this codec - return makeQDM2Stream(stream, _streams[_audioStreamIndex]->extradata); -#endif - } - - error("Unsupported audio codec"); - - return NULL; -} - -uint32 QuickTimeDecoder::getAudioChunkSampleCount(uint chunk) { - if (_audioStreamIndex < 0) - return 0; - - uint32 sampleCount = 0; - - for (uint32 j = 0; j < _streams[_audioStreamIndex]->sample_to_chunk_sz; j++) - if (chunk >= _streams[_audioStreamIndex]->sample_to_chunk[j].first) - sampleCount = _streams[_audioStreamIndex]->sample_to_chunk[j].count; - - return sampleCount; -} - -void QuickTimeDecoder::readNextAudioChunk() { - STSDEntry *entry = &_streams[_audioStreamIndex]->stsdEntries[0]; - Common::MemoryWriteStreamDynamic *wStream = new Common::MemoryWriteStreamDynamic(); - - _fd->seek(_streams[_audioStreamIndex]->chunk_offsets[_curAudioChunk]); - - // First, we have to get the sample count - uint32 sampleCount = getAudioChunkSampleCount(_curAudioChunk); - assert(sampleCount); - - // Then calculate the right sizes - while (sampleCount > 0) { - uint32 samples = 0, size = 0; - - if (entry->samplesPerFrame >= 160) { - samples = entry->samplesPerFrame; - size = entry->bytesPerFrame; - } else if (entry->samplesPerFrame > 1) { - samples = MIN<uint32>((1024 / entry->samplesPerFrame) * entry->samplesPerFrame, sampleCount); - size = (samples / entry->samplesPerFrame) * entry->bytesPerFrame; - } else { - samples = MIN<uint32>(1024, sampleCount); - size = samples * _streams[_audioStreamIndex]->sample_size; - } - - // Now, we read in the data for this data and output it - byte *data = (byte *)malloc(size); - _fd->read(data, size); - wStream->write(data, size); - free(data); - sampleCount -= samples; - } - - // Now queue the buffer - _audStream->queueAudioStream(createAudioStream(new Common::MemoryReadStream(wStream->getData(), wStream->size(), DisposeAfterUse::YES))); - delete wStream; - - _curAudioChunk++; -} - void QuickTimeDecoder::updateAudioBuffer() { if (!_audStream) return; uint32 numberOfChunksNeeded = 0; - if (_curFrame == (int32)_streams[_videoStreamIndex]->nb_frames - 1) { + if (_videoStreamIndex < 0 || _curFrame == (int32)_streams[_videoStreamIndex]->nb_frames - 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 = _streams[_audioStreamIndex]->chunk_count; } else { - STSDEntry *entry = &_streams[_audioStreamIndex]->stsdEntries[0]; + Audio::QuickTimeAudioDecoder::AudioSampleDesc *entry = (Audio::QuickTimeAudioDecoder::AudioSampleDesc *)_streams[_audioStreamIndex]->sampleDescs[0]; // Calculate the amount of chunks we need in memory until the next frame uint32 timeToNextFrame = getTimeToNextFrame(); @@ -1458,63 +593,19 @@ void QuickTimeDecoder::updateAudioBuffer() { // Keep three streams in buffer so that if/when the first two end, it goes right into the next while (_audStream->numQueuedStreams() < numberOfChunksNeeded && _curAudioChunk < _streams[_audioStreamIndex]->chunk_count) - readNextAudioChunk(); + queueNextAudioChunk(); } -QuickTimeDecoder::STSDEntry::STSDEntry() { - codecTag = 0; - bitsPerSample = 0; +QuickTimeDecoder::VideoSampleDesc::VideoSampleDesc() : Common::QuickTimeParser::SampleDesc() { memset(codecName, 0, 32); colorTableId = 0; palette = 0; videoCodec = 0; - channels = 0; - sampleRate = 0; - samplesPerFrame = 0; - bytesPerFrame = 0; } -QuickTimeDecoder::STSDEntry::~STSDEntry() { +QuickTimeDecoder::VideoSampleDesc::~VideoSampleDesc() { delete[] palette; delete videoCodec; } -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; - stsdEntryCount = 0; - stsdEntries = 0; - 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[] stsdEntries; - delete[] editList; - delete extradata; -} - } // End of namespace Video diff --git a/video/qt_decoder.h b/video/qt_decoder.h index cf08349d91..d8beda0f83 100644 --- a/video/qt_decoder.h +++ b/video/qt_decoder.h @@ -21,7 +21,7 @@ */ // -// Heavily based on ffmpeg code. +// Partially based on ffmpeg code. // // Copyright (c) 2001 Fabrice Bellard. // First version by Francois Revol revol@free.fr @@ -37,21 +37,10 @@ #include "video/video_decoder.h" #include "audio/mixer.h" -#include "audio/timestamp.h" +#include "audio/decoders/quicktime_intern.h" namespace Common { -class MacResManager; -class SeekableReadStream; -} - -namespace Audio { -class AudioStream; -class QueuingAudioStream; -} - -namespace Graphics { -struct PixelFormat; -struct Surface; + class Rational; } namespace Video { @@ -65,7 +54,7 @@ class Codec; * - mohawk * - sci */ -class QuickTimeDecoder : public SeekableVideoDecoder { +class QuickTimeDecoder : public SeekableVideoDecoder, public Audio::QuickTimeAudioDecoder { public: QuickTimeDecoder(); virtual ~QuickTimeDecoder(); @@ -112,14 +101,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; @@ -131,129 +113,28 @@ 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 STSDEntry { - STSDEntry(); - ~STSDEntry(); +protected: + struct VideoSampleDesc : public Common::QuickTimeParser::SampleDesc { + VideoSampleDesc(); + ~VideoSampleDesc(); - uint32 codecTag; - uint16 bitsPerSample; - - // Video char codecName[32]; uint16 colorTableId; byte *palette; Codec *videoCodec; - - // Audio - uint16 channels; - uint32 sampleRate; - uint32 samplesPerFrame; - 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; + Common::QuickTimeParser::SampleDesc *readSampleDesc(MOVStreamContext *st, uint32 format); - uint16 width; - uint16 height; - CodecType codec_type; - - uint32 stsdEntryCount; - STSDEntry *stsdEntries; - - 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; - - void initParseTable(); - Audio::AudioStream *createAudioStream(Common::SeekableReadStream *stream); - bool checkAudioCodecSupport(uint32 tag); +private: Common::SeekableReadStream *getNextFramePacket(uint32 &descId); uint32 getFrameDuration(); void init(); - Audio::QueuingAudioStream *_audStream; void startAudio(); void stopAudio(); void updateAudioBuffer(); void readNextAudioChunk(); - uint32 getAudioChunkSampleCount(uint chunk); - int8 _audioStreamIndex; - uint _curAudioChunk; Audio::SoundHandle _audHandle; Audio::Timestamp _audioStartOffset; @@ -263,30 +144,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 |