aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Kiewitz2015-06-04 15:53:54 +0200
committerMartin Kiewitz2015-06-04 15:53:54 +0200
commit4dec07bf2df4a15ef353ec95b72e76c3432751fb (patch)
tree233b3135ed3cbcc65038551864ef094e3efe55a4
parent9d67c9535940fb76b12b721325c92745611a4b96 (diff)
downloadscummvm-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.cpp275
-rw-r--r--audio/decoders/3do.h96
-rw-r--r--audio/module.mk1
-rw-r--r--engines/sherlock/scalpel/3do/movie_decoder.cpp257
-rw-r--r--engines/sherlock/scalpel/3do/movie_decoder.h20
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;