diff options
author | Martin Kiewitz | 2015-06-04 15:53:54 +0200 |
---|---|---|
committer | Martin Kiewitz | 2015-06-04 15:53:54 +0200 |
commit | 4dec07bf2df4a15ef353ec95b72e76c3432751fb (patch) | |
tree | 233b3135ed3cbcc65038551864ef094e3efe55a4 | |
parent | 9d67c9535940fb76b12b721325c92745611a4b96 (diff) | |
download | scummvm-rg350-4dec07bf2df4a15ef353ec95b72e76c3432751fb.tar.gz scummvm-rg350-4dec07bf2df4a15ef353ec95b72e76c3432751fb.tar.bz2 scummvm-rg350-4dec07bf2df4a15ef353ec95b72e76c3432751fb.zip |
SHERLOCK: improve 3DO movie player, add SDX2 codec
- queue up to 0.5 seconds of audio to avoid buffer underruns
- support for SDX2 codec
- put both audio codecs into audio/decoders/3do.cpp
- made movie player capable of playing EA logo movie
-rw-r--r-- | audio/decoders/3do.cpp | 275 | ||||
-rw-r--r-- | audio/decoders/3do.h | 96 | ||||
-rw-r--r-- | audio/module.mk | 1 | ||||
-rw-r--r-- | engines/sherlock/scalpel/3do/movie_decoder.cpp | 257 | ||||
-rw-r--r-- | engines/sherlock/scalpel/3do/movie_decoder.h | 20 |
5 files changed, 532 insertions, 117 deletions
diff --git a/audio/decoders/3do.cpp b/audio/decoders/3do.cpp new file mode 100644 index 0000000000..ab98aa2ba2 --- /dev/null +++ b/audio/decoders/3do.cpp @@ -0,0 +1,275 @@ +/* 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. + * + */ + +#include "common/textconsole.h" +#include "common/stream.h" +#include "common/util.h" + +#include "audio/decoders/3do.h" +#include "audio/decoders/raw.h" + +namespace Audio { + +#define AUDIO_3DO_ADP4_STEPSIZETABLE_MAX 88 + +static int16 audio_3DO_ADP4_stepSizeTable[AUDIO_3DO_ADP4_STEPSIZETABLE_MAX + 1] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 +}; + +static int16 audio_3DO_ADP4_stepSizeIndex[] = { + -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 +}; + +int16 audio_3DO_ADP4_DecodeSample(uint8 dataNibble, int16 &decoderLastSample, int16 &decoderStepIndex) { + int16 currentStep = audio_3DO_ADP4_stepSizeTable[decoderStepIndex]; + int32 decodedSample = decoderLastSample; + int16 delta = currentStep >> 3; + + if (dataNibble & 1) + delta += currentStep >> 2; + + if (dataNibble & 2) + delta += currentStep >> 1; + + if (dataNibble & 4) + delta += currentStep; + + if (dataNibble & 8) { + decodedSample -= delta; + } else { + decodedSample += delta; + } + + decoderLastSample = CLIP<int32>(decodedSample, -32768, 32767); + + decoderStepIndex += audio_3DO_ADP4_stepSizeIndex[dataNibble & 0x07]; + decoderStepIndex = CLIP<int16>(decoderStepIndex, 0, AUDIO_3DO_ADP4_STEPSIZETABLE_MAX); + + return decoderLastSample; +} + +SeekableAudioStream *make3DO_ADP4Stream(Common::SeekableReadStream *stream, uint32 size, uint16 sampleRate, byte audioFlags, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace) { + int32 streamPos = 0; + int32 compressedSize = size; + int32 decompressedSize = 0; + int32 decompressedPos = 0; + byte compressedByte = 0; + + audio_3DO_ADP4_PersistentSpace decoderData; + + assert(compressedSize <= stream->size()); + + if (audioFlags & Audio::FLAG_UNSIGNED) { + // Unsigned data is not allowed + warning("make3DO_ADP4Stream(): sample data result is expected to be signed"); + return 0; + } + if (!(audioFlags & Audio::FLAG_16BITS)) { + // 8-bit sample data is not allowed + warning("make3DO_ADP4Stream(): sample data result is expected to be 16-bit"); + return 0; + } + if (audioFlags & Audio::FLAG_LITTLE_ENDIAN) { + // LE sample data is not allowed + warning("make3DO_ADP4Stream(): sample data result is expected to be Big Endian"); + return 0; + } + if (audioFlags & Audio::FLAG_STEREO) { + warning("make3DO_ADP4Stream(): stereo currently not supported"); + return 0; + } + + if (persistentSpace) { + memcpy(&decoderData, persistentSpace, sizeof(decoderData)); + } else { + memset(&decoderData, 0, sizeof(decoderData)); + } + + assert(compressedSize < 0x40000000); // safety check + + decompressedSize = compressedSize * 4; // 4 bits == 1 16-bit sample + byte *decompressedData = (byte *)malloc(decompressedSize); + assert(decompressedData); + + if (!(audioFlags & Audio::FLAG_STEREO)) { + // Mono + for (streamPos = 0; streamPos < compressedSize; streamPos++) { + compressedByte = stream->readByte(); + + WRITE_BE_UINT16(decompressedData + decompressedPos, audio_3DO_ADP4_DecodeSample(compressedByte >> 4, decoderData.lastSample, decoderData.stepIndex)); + decompressedPos += 2; + WRITE_BE_UINT16(decompressedData + decompressedPos, audio_3DO_ADP4_DecodeSample(compressedByte & 0x0F, decoderData.lastSample, decoderData.stepIndex)); + decompressedPos += 2; + } + } + + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + + if (persistentSpace) { + memcpy(persistentSpace, &decoderData, sizeof(decoderData)); + } + + // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES. + return makeRawStream(decompressedData, decompressedSize, sampleRate, audioFlags); +} + +static int16 audio_3DO_SDX2_SquareTable[256] = { +-32768,-32258,-31752,-31250,-30752,-30258,-29768,-29282,-28800,-28322, +-27848,-27378,-26912,-26450,-25992,-25538,-25088,-24642,-24200,-23762, +-23328,-22898,-22472,-22050,-21632,-21218,-20808,-20402,-20000,-19602, +-19208,-18818,-18432,-18050,-17672,-17298,-16928,-16562,-16200,-15842, +-15488,-15138,-14792,-14450,-14112,-13778,-13448,-13122,-12800,-12482, +-12168,-11858,-11552,-11250,-10952,-10658,-10368,-10082, -9800, -9522, + -9248, -8978, -8712, -8450, -8192, -7938, -7688, -7442, -7200, -6962, + -6728, -6498, -6272, -6050, -5832, -5618, -5408, -5202, -5000, -4802, + -4608, -4418, -4232, -4050, -3872, -3698, -3528, -3362, -3200, -3042, + -2888, -2738, -2592, -2450, -2312, -2178, -2048, -1922, -1800, -1682, + -1568, -1458, -1352, -1250, -1152, -1058, -968, -882, -800, -722, + -648, -578, -512, -450, -392, -338, -288, -242, -200, -162, + -128, -98, -72, -50, -32, -18, -8, -2, 0, 2, + 8, 18, 32, 50, 72, 98, 128, 162, 200, 242, + 288, 338, 392, 450, 512, 578, 648, 722, 800, 882, + 968, 1058, 1152, 1250, 1352, 1458, 1568, 1682, 1800, 1922, + 2048, 2178, 2312, 2450, 2592, 2738, 2888, 3042, 3200, 3362, + 3528, 3698, 3872, 4050, 4232, 4418, 4608, 4802, 5000, 5202, + 5408, 5618, 5832, 6050, 6272, 6498, 6728, 6962, 7200, 7442, + 7688, 7938, 8192, 8450, 8712, 8978, 9248, 9522, 9800, 10082, + 10368, 10658, 10952, 11250, 11552, 11858, 12168, 12482, 12800, 13122, + 13448, 13778, 14112, 14450, 14792, 15138, 15488, 15842, 16200, 16562, + 16928, 17298, 17672, 18050, 18432, 18818, 19208, 19602, 20000, 20402, + 20808, 21218, 21632, 22050, 22472, 22898, 23328, 23762, 24200, 24642, + 25088, 25538, 25992, 26450, 26912, 27378, 27848, 28322, 28800, 29282, + 29768, 30258, 30752, 31250, 31752, 32258 +}; + +SeekableAudioStream *make3DO_SDX2Stream(Common::SeekableReadStream *stream, uint32 size, uint16 sampleRate, byte audioFlags, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpace) { + int32 streamPos = 0; + int32 compressedSize = size; + int32 decompressedSize = 0; + int32 decompressedPos = 0; + + int8 compressedByte = 0; + uint8 squareTableOffset = 0; + int16 decodedSample = 0; + + audio_3DO_SDX2_PersistentSpace decoderData; + + assert(compressedSize <= stream->size()); + + if (audioFlags & Audio::FLAG_UNSIGNED) { + // Unsigned data is not allowed + warning("make3DO_SDX2Stream(): sample data result is expected to be signed"); + return 0; + } + if (!(audioFlags & Audio::FLAG_16BITS)) { + // 8-bit sample data is not allowed + warning("make3DO_SDX2Stream(): sample data result is expected to be 16-bit"); + return 0; + } + if (audioFlags & Audio::FLAG_LITTLE_ENDIAN) { + // LE sample data is not allowed + warning("make3DO_SDX2Stream(): sample data result is expected to be Big Endian"); + return 0; + } + if (audioFlags & Audio::FLAG_STEREO) { + if (compressedSize & 1) { + warning("make3DO_SDX2Stream(): stereo data is uneven size"); + return 0; + } + } + + if (persistentSpace) { + memcpy(&decoderData, persistentSpace, sizeof(decoderData)); + } else { + memset(&decoderData, 0, sizeof(decoderData)); + } + + assert(compressedSize < 0x40000000); // safety check + + decompressedSize = compressedSize * 2; // 1 byte == 1 16-bit sample + byte *decompressedData = (byte *)malloc(decompressedSize); + assert(decompressedData); + + if (!(audioFlags & Audio::FLAG_STEREO)) { + // Mono + for (streamPos = 0; streamPos < compressedSize; streamPos++) { + compressedByte = stream->readSByte(); + squareTableOffset = compressedByte + 128; + + if (!(compressedByte & 1)) + decoderData.lastSample1 = 0; + + decodedSample = decoderData.lastSample1 + audio_3DO_SDX2_SquareTable[squareTableOffset]; + decoderData.lastSample1 = decodedSample; + + WRITE_BE_UINT16(decompressedData + decompressedPos, decodedSample); + decompressedPos += 2; + } + + } else { + // Stereo + for (streamPos = 0; streamPos < compressedSize; streamPos++) { + compressedByte = stream->readSByte(); + squareTableOffset = compressedByte + 128; + + if (!(streamPos & 1)) { + // First channel + if (!(compressedByte & 1)) + decoderData.lastSample1 = 0; + + decodedSample = decoderData.lastSample1 + audio_3DO_SDX2_SquareTable[squareTableOffset]; + decoderData.lastSample1 = decodedSample; + } else { + // Second channel + if (!(compressedByte & 1)) + decoderData.lastSample2 = 0; + + decodedSample = decoderData.lastSample2 + audio_3DO_SDX2_SquareTable[squareTableOffset]; + decoderData.lastSample2 = decodedSample; + } + + WRITE_BE_UINT16(decompressedData + decompressedPos, decodedSample); + decompressedPos += 2; + } + } + + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + + if (persistentSpace) { + memcpy(persistentSpace, &decoderData, sizeof(decoderData)); + } + + // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES. + return makeRawStream(decompressedData, decompressedSize, sampleRate, audioFlags); +} + +} // End of namespace Audio diff --git a/audio/decoders/3do.h b/audio/decoders/3do.h new file mode 100644 index 0000000000..4d8412a4cb --- /dev/null +++ b/audio/decoders/3do.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. + * + */ + +/** + * @file + * Sound decoder used in engines: + * - sherlock (3DO version of Serrated Scalpel) + */ + +#ifndef AUDIO_3DO_SDX2_H +#define AUDIO_3DO_SDX2_H + +#include "common/scummsys.h" +#include "common/types.h" + +namespace Common { +class SeekableReadStream; +} + +namespace Audio { + +class SeekableAudioStream; + +struct audio_3DO_ADP4_PersistentSpace { + int16 lastSample; + int16 stepIndex; +}; + +struct audio_3DO_SDX2_PersistentSpace { + int16 lastSample1; + int16 lastSample2; +}; + + +/** + * Try to decode 3DO ADP4 data from the given seekable stream and create a SeekableAudioStream + * from that data. + * + * @param stream the SeekableReadStream from which to read the 3DO ADP4 data + * @size how many bytes to read from stream + * @sampleRate sample rate + * @audioFlags flags, that specify the type of output + * @param disposeAfterUse whether to delete the stream after use + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +SeekableAudioStream *make3DO_ADP4Stream( + Common::SeekableReadStream *stream, + uint32 size, + uint16 sampleRate, + byte audioFlags, + DisposeAfterUse::Flag disposeAfterUse, + audio_3DO_ADP4_PersistentSpace *persistentSpace = NULL +); + +/** + * Try to decode 3DO SDX2 data from the given seekable stream and create a SeekableAudioStream + * from that data. + * + * @param stream the SeekableReadStream from which to read the 3DO SDX2 data + * @size how many bytes to read from stream + * @sampleRate sample rate + * @audioFlags flags, that specify the type of output + * @param disposeAfterUse whether to delete the stream after use + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +SeekableAudioStream *make3DO_SDX2Stream( + Common::SeekableReadStream *stream, + uint32 size, + uint16 sampleRate, + byte audioFlags, + DisposeAfterUse::Flag disposeAfterUse, + audio_3DO_SDX2_PersistentSpace *persistentSpace = NULL +); + +} // End of namespace Audio + +#endif diff --git a/audio/module.mk b/audio/module.mk index 4e1c031c83..bdb71ab4a3 100644 --- a/audio/module.mk +++ b/audio/module.mk @@ -14,6 +14,7 @@ MODULE_OBJS := \ musicplugin.o \ null.o \ timestamp.o \ + decoders/3do.o \ decoders/aac.o \ decoders/adpcm.o \ decoders/aiff.o \ diff --git a/engines/sherlock/scalpel/3do/movie_decoder.cpp b/engines/sherlock/scalpel/3do/movie_decoder.cpp index 857e010dda..a50362c99a 100644 --- a/engines/sherlock/scalpel/3do/movie_decoder.cpp +++ b/engines/sherlock/scalpel/3do/movie_decoder.cpp @@ -26,6 +26,7 @@ #include "audio/audiostream.h" #include "audio/decoders/raw.h" +#include "audio/decoders/3do.h" #include "sherlock/scalpel/3do/movie_decoder.h" #include "image/codecs/cinepak.h" @@ -44,6 +45,8 @@ namespace Sherlock { Scalpel3DOMovieDecoder::Scalpel3DOMovieDecoder() : _stream(0), _videoTrack(0), _audioTrack(0) { + _streamVideoOffset = 0; + _streamAudioOffset = 0; } Scalpel3DOMovieDecoder::~Scalpel3DOMovieDecoder() { @@ -64,18 +67,11 @@ bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) { close(); _stream = stream; - - // CTRL Header - if (_stream->readUint32BE() != MKTAG('C', 'T', 'R', 'L')) { - close(); - return false; - } - - uint32 ctrlSize = _stream->readUint32BE() - 8; - _stream->skip(ctrlSize); + _streamVideoOffset = 0; + _streamAudioOffset = 0; // Look for packets that we care about - static const int maxPacketCheckCount = 10; + static const int maxPacketCheckCount = 20; for (int i = 0; i < maxPacketCheckCount; i++) { uint32 tag = _stream->readUint32BE(); uint32 chunkSize = _stream->readUint32BE() - 8; @@ -116,8 +112,9 @@ bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) { break; default: - error("Sherlock 3DO movie: Unknown subtype inside FILM packet"); - break; + warning("Sherlock 3DO movie: Unknown subtype inside FILM packet"); + close(); + return false; } break; } @@ -158,8 +155,9 @@ bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) { // Audio data break; default: - error("Sherlock 3DO movie: Unknown subtype inside FILM packet"); - break; + warning("Sherlock 3DO movie: Unknown subtype inside FILM packet"); + close(); + return false; } break; } @@ -169,6 +167,10 @@ bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) { // Ignore but also accept CTRL + FILL packets break; + case MKTAG('S','H','D','R'): + // Happens for EA logo, seems to be garbage data right at the start of the file + break; + default: warning("Unknown header inside Sherlock 3DO movie"); close(); @@ -201,26 +203,58 @@ void Scalpel3DOMovieDecoder::close() { _videoTrack = 0; } +// We try to at least decode 1 frame +// and also try to get at least 0.5 seconds of audio queued up void Scalpel3DOMovieDecoder::readNextPacket() { - uint32 videoSubType = 0; + uint32 currentMovieTime = getTime(); + uint32 wantedAudioQueued = currentMovieTime + 500; // always try to be 0.500 seconds in front of movie time + + int32 chunkOffset = 0; + int32 dataStartOffset = 0; + int32 nextChunkOffset = 0; + uint32 chunkTag = 0; + uint32 chunkSize = 0; + + uint32 videoSubType = 0; uint32 videoTimeStamp = 0; uint32 videoFrameSize = 0; - uint32 audioSubType = 0; - uint32 audioSampleBytes = 0; - bool gotAudio = false; - bool gotVideo = false; + uint32 audioSubType = 0; + uint32 audioBytes = 0; + bool videoGotFrame = false; + bool videoDone = false; + bool audioDone = false; + + // Seek to smallest stream offset + if (_streamVideoOffset <= _streamAudioOffset) { + _stream->seek(_streamVideoOffset); + } else { + _stream->seek(_streamAudioOffset); + } - while (!endOfVideoTracks()) { - uint32 tag = _stream->readUint32BE(); - uint32 chunkSize = _stream->readUint32BE() - 8; - uint32 dataStartOffset = _stream->pos(); + if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) { + // already got enough audio queued up + audioDone = true; + } + + while (1) { + chunkOffset = _stream->pos(); + assert(chunkOffset >= 0); + + // Read chunk header + chunkTag = _stream->readUint32BE(); + chunkSize = _stream->readUint32BE() - 8; + + // Calculate offsets + dataStartOffset = _stream->pos(); + assert(dataStartOffset >= 0); + nextChunkOffset = dataStartOffset + chunkSize; //warning("offset %lx - tag %lx", dataStartOffset, tag); if (_stream->eos()) break; - switch (tag) { + switch (chunkTag) { case MKTAG('F','I','L','M'): videoTimeStamp = _stream->readUint32BE(); _stream->skip(4); // Unknown @@ -232,13 +266,34 @@ void Scalpel3DOMovieDecoder::readNextPacket() { break; case MKTAG('F', 'R', 'M', 'E'): - // Have a frame! - - // If we previously found one, this is just to get the time offset of the next one - /* uint32 frmeSize = */ _stream->readUint32BE(); - videoFrameSize = _stream->readUint32BE(); - _videoTrack->decodeFrame(_stream->readStream(videoFrameSize), videoTimeStamp); - gotVideo = true; + // Found frame data + if (_streamVideoOffset <= chunkOffset) { + if (!videoDone) { + if (!videoGotFrame) { + // If we previously found one, this is just to get the time offset of the next one + _stream->readUint32BE(); + videoFrameSize = _stream->readUint32BE(); + _videoTrack->decodeFrame(_stream->readStream(videoFrameSize), videoTimeStamp); + + _streamVideoOffset = nextChunkOffset; + videoGotFrame = true; + + } else { + // Already decoded a frame, so seek back to current chunk and exit + + // Calculate next frame time + // 3DO clock time for movies runs at 240Hh, that's why timestamps are based on 240. + uint32 currentFrameStartTime = _videoTrack->getNextFrameStartTime(); + uint32 nextFrameStartTime = videoTimeStamp * 1000 / 240; + assert(currentFrameStartTime <= nextFrameStartTime); + _videoTrack->setNextFrameStartTime(nextFrameStartTime); + + // next time we want to start at the current chunk + _streamVideoOffset = chunkOffset; + videoDone = true; + } + } + } break; default: @@ -258,9 +313,18 @@ void Scalpel3DOMovieDecoder::readNextPacket() { case MKTAG('S', 'S', 'M', 'P'): // Got audio chunk - audioSampleBytes = _stream->readUint32BE(); - _audioTrack->queueAudio(_stream, audioSampleBytes); - gotAudio = true; + if (_streamAudioOffset <= chunkOffset) { + if (!audioDone) { + audioBytes = _stream->readUint32BE(); + _audioTrack->queueAudio(_stream, audioBytes); + + _streamAudioOffset = nextChunkOffset; + if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) { + // Got enough audio + audioDone = true; + } + } + } break; default: @@ -274,16 +338,21 @@ void Scalpel3DOMovieDecoder::readNextPacket() { // Ignore but also accept CTRL + FILL packets break; + case MKTAG('S','H','D','R'): + // Happens for EA logo, seems to be garbage data right at the start of the file + break; + default: error("Unknown header inside Sherlock 3DO movie"); } // Always seek to end of chunk // Sometimes not all of the chunk is filled with audio - _stream->seek(dataStartOffset + chunkSize); + _stream->seek(nextChunkOffset); - if (gotVideo) + if ((videoDone) && (audioDone)) { return; + } } } @@ -314,101 +383,61 @@ Graphics::PixelFormat Scalpel3DOMovieDecoder::StreamVideoTrack::getPixelFormat() } void Scalpel3DOMovieDecoder::StreamVideoTrack::decodeFrame(Common::SeekableReadStream *stream, uint32 videoTimeStamp) { - uint32 currentFrameStartTime = 0; - _surface = _codec->decodeFrame(*stream); _curFrame++; - - // Calculate next frame time - currentFrameStartTime = videoTimeStamp * 1000 / 240; - assert(currentFrameStartTime >= _nextFrameStartTime); - _nextFrameStartTime = currentFrameStartTime; } -#define STREAMAUDIO_STEPSIZETABLE_MAX 88 - -static int16 streamAudio_stepSizeTable[STREAMAUDIO_STEPSIZETABLE_MAX + 1] = { - 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, - 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, - 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, - 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, - 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, - 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, - 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, - 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, - 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 -}; - -static int16 streamAudio_stepSizeIndex[] = { - -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 -}; - Scalpel3DOMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels) { - if (channels != 1) { - error("Sherlock 3DO stream audio is not mono"); - } - if (codecTag != MKTAG('A','D','P','4')) { + switch (codecTag) { + case MKTAG('A','D','P','4'): + case MKTAG('S','D','X','2'): + // ADP4 + SDX2 are both allowed + break; + + default: error("Sherlock 3DO stream audio is not using codec ADP4"); } - _audioStream = Audio::makeQueuingAudioStream(sampleRate, false); - // reset ADPCM decoding - _lastSample = 0; - _stepIndex = 0; -} + _totalAudioQueued = 0; // currently 0 milliseconds queued -Scalpel3DOMovieDecoder::StreamAudioTrack::~StreamAudioTrack() { - delete _audioStream; -} - -int16 Scalpel3DOMovieDecoder::StreamAudioTrack::decodeSample(uint8 dataNibble) { - int16 currentStep = streamAudio_stepSizeTable[_stepIndex]; - int32 decodedSample = _lastSample; - int16 delta = currentStep >> 3; - - if (dataNibble & 1) - delta += currentStep >> 2; - - if (dataNibble & 2) - delta += currentStep >> 1; - - if (dataNibble & 4) - delta += currentStep; + _codecTag = codecTag; + _sampleRate = sampleRate; + _audioFlags = Audio::FLAG_16BITS; + if (channels > 1) + _audioFlags |= Audio::FLAG_STEREO; - if (dataNibble & 8) { - decodedSample -= delta; + if (_audioFlags & Audio::FLAG_STEREO) { + _audioStream = Audio::makeQueuingAudioStream(sampleRate, true); } else { - decodedSample += delta; + _audioStream = Audio::makeQueuingAudioStream(sampleRate, false); } - _lastSample = CLIP<int32>(decodedSample, -32768, 32767); - - _stepIndex += streamAudio_stepSizeIndex[dataNibble & 0x07]; - _stepIndex = CLIP<int16>(_stepIndex, 0, STREAMAUDIO_STEPSIZETABLE_MAX); + // reset audio decoder persistant spaces + memset(&_ADP4_PersistentSpace, 0, sizeof(_ADP4_PersistentSpace)); + memset(&_SDX2_PersistentSpace, 0, sizeof(_SDX2_PersistentSpace)); +} - return _lastSample; +Scalpel3DOMovieDecoder::StreamAudioTrack::~StreamAudioTrack() { + delete _audioStream; } -void Scalpel3DOMovieDecoder::StreamAudioTrack::queueAudio(Common::SeekableReadStream *stream, uint32 length) { - uint32 decodedAudioSize = length * 4; - byte *decodedAudioBuffer = NULL; - uint8 streamByte = 0; - uint32 streamPos = 0; - uint32 audioPos = 0; - - decodedAudioBuffer = (byte *)malloc(decodedAudioSize); - assert(decodedAudioBuffer); - - for (streamPos = 0; streamPos < length; streamPos++) { - streamByte = stream->readByte(); - WRITE_BE_UINT16(decodedAudioBuffer + audioPos, decodeSample(streamByte >> 4)); - audioPos += 2; - WRITE_BE_UINT16(decodedAudioBuffer + audioPos, decodeSample(streamByte & 0x0F)); - audioPos += 2; +void Scalpel3DOMovieDecoder::StreamAudioTrack::queueAudio(Common::SeekableReadStream *stream, uint32 size) { + Audio::SeekableAudioStream *audioStream = 0; + + switch(_codecTag) { + case MKTAG('A','D','P','4'): + audioStream = Audio::make3DO_ADP4Stream(stream, size, _sampleRate, _audioFlags, DisposeAfterUse::NO, &_ADP4_PersistentSpace); + break; + case MKTAG('S','D','X','2'): + audioStream = Audio::make3DO_SDX2Stream(stream, size, _sampleRate, _audioFlags, DisposeAfterUse::NO, &_SDX2_PersistentSpace); + break; + default: + break; + } + if (audioStream) { + _totalAudioQueued += audioStream->getLength().msecs(); + _audioStream->queueAudioStream(audioStream, DisposeAfterUse::YES); } - - // Now the audio is loaded, so let's queue it - _audioStream->queueBuffer(decodedAudioBuffer, decodedAudioSize, DisposeAfterUse::YES, Audio::FLAG_16BITS); } Audio::AudioStream *Scalpel3DOMovieDecoder::StreamAudioTrack::getAudioStream() const { diff --git a/engines/sherlock/scalpel/3do/movie_decoder.h b/engines/sherlock/scalpel/3do/movie_decoder.h index be5a834414..60e79fa238 100644 --- a/engines/sherlock/scalpel/3do/movie_decoder.h +++ b/engines/sherlock/scalpel/3do/movie_decoder.h @@ -24,6 +24,7 @@ #define SHERLOCK_SCALPEL_3DO_MOVIE_DECODER_H #include "video/video_decoder.h" +#include "audio/decoders/3do.h" namespace Audio { class QueuingAudioStream; @@ -51,6 +52,10 @@ protected: void readNextPacket(); private: + int32 _streamVideoOffset; /* current stream offset for video decoding */ + int32 _streamAudioOffset; /* current stream offset for audio decoding */ + +private: class StreamVideoTrack : public VideoTrack { public: StreamVideoTrack(uint32 width, uint32 height, uint32 codecTag, uint32 frameCount); @@ -63,6 +68,7 @@ private: Graphics::PixelFormat getPixelFormat() const; int getCurFrame() const { return _curFrame; } int getFrameCount() const { return _frameCount; } + void setNextFrameStartTime(uint32 nextFrameStartTime) { _nextFrameStartTime = nextFrameStartTime; } uint32 getNextFrameStartTime() const { return _nextFrameStartTime; } const Graphics::Surface *decodeNextFrame() { return _surface; } @@ -84,19 +90,27 @@ private: StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels); ~StreamAudioTrack(); - void queueAudio(Common::SeekableReadStream *stream, uint32 length); + void queueAudio(Common::SeekableReadStream *stream, uint32 size); protected: Audio::AudioStream *getAudioStream() const; private: Audio::QueuingAudioStream *_audioStream; + uint32 _totalAudioQueued; /* total amount of milliseconds of audio, that we queued up already */ + + public: + uint32 getTotalAudioQueued() const { return _totalAudioQueued; } private: int16 decodeSample(uint8 dataNibble); - int16 _lastSample; - int16 _stepIndex; + uint32 _codecTag; + uint16 _sampleRate; + byte _audioFlags; + + Audio::audio_3DO_ADP4_PersistentSpace _ADP4_PersistentSpace; + Audio::audio_3DO_SDX2_PersistentSpace _SDX2_PersistentSpace; }; Common::SeekableReadStream *_stream; |