aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Špalek2010-07-01 07:10:40 +0000
committerRobert Špalek2010-07-01 07:10:40 +0000
commitb45d2cd92b137dc429f12f67b887a193f601afb2 (patch)
tree7a3c8a044908a0f76a1171749d669a2e8a90c163
parentb0b4f34d276b3451d6d46ac29cab6f5166cea757 (diff)
downloadscummvm-rg350-b45d2cd92b137dc429f12f67b887a193f601afb2.tar.gz
scummvm-rg350-b45d2cd92b137dc429f12f67b887a193f601afb2.tar.bz2
scummvm-rg350-b45d2cd92b137dc429f12f67b887a193f601afb2.zip
Implement playing RAW/MP3/OGG/FLAC dubbing from ZIP archives.
Playing works well, but I am not enabling it in the game player yet, because I have not implemented measuring the time duration of compressed dubbing, which is needed in the (exclusively used) blocking mode. svn-id: r50543
-rw-r--r--engines/draci/sound.cpp157
-rw-r--r--engines/draci/sound.h78
2 files changed, 209 insertions, 26 deletions
diff --git a/engines/draci/sound.cpp b/engines/draci/sound.cpp
index 65e7e41ac4..e430da7bdc 100644
--- a/engines/draci/sound.cpp
+++ b/engines/draci/sound.cpp
@@ -23,11 +23,13 @@
*
*/
+#include "common/archive.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/str.h"
#include "common/stream.h"
+#include "common/unzip.h"
#include "draci/sound.h"
#include "draci/draci.h"
@@ -36,14 +38,17 @@
#include "sound/audiostream.h"
#include "sound/mixer.h"
#include "sound/decoders/raw.h"
+#include "sound/decoders/mp3.h"
+#include "sound/decoders/vorbis.h"
+#include "sound/decoders/flac.h"
namespace Draci {
-void LegacySoundArchive::openArchive(const Common::String &path) {
+void LegacySoundArchive::openArchive(const char *path) {
// Close previously opened archive (if any)
closeArchive();
- debugCN(2, kDraciArchiverDebugLevel, "Loading samples %s: ", path.c_str());
+ debugCN(2, kDraciArchiverDebugLevel, "Loading samples %s: ", path);
_f = new Common::File();
_f->open(path);
@@ -83,7 +88,6 @@ void LegacySoundArchive::openArchive(const Common::String &path) {
_samples[i]._offset = sampleStarts[i];
_samples[i]._length = sampleStarts[i+1] - sampleStarts[i];
_samples[i]._frequency = 0; // set in getSample()
- _samples[i]._data = NULL;
}
if (_samples[_sampleCount-1]._offset + _samples[_sampleCount-1]._length != totalLength &&
_samples[_sampleCount-1]._offset + _samples[_sampleCount-1]._length - _samples[0]._offset != totalLength) {
@@ -144,25 +148,106 @@ SoundSample *LegacySoundArchive::getSample(int i, uint freq) {
}
debugCN(2, kDraciArchiverDebugLevel, "Accessing sample %d from archive %s... ",
- i, _path.c_str());
+ i, _path);
// Check if file has already been opened and return that
if (_samples[i]._data) {
- debugC(2, kDraciArchiverDebugLevel, "Success");
+ debugC(2, kDraciArchiverDebugLevel, "Cached");
} else {
+ // It would be nice to unify the approach with ZipSoundArchive
+ // and allocate a MemoryReadStream with buffer stored inside it
+ // that playSoundBuffer() would just play. Unfortunately,
+ // streams are not thread-safe and the same sample couldn't
+ // thus be played more than once at the same time (this holds
+ // even if we create a SeekableSubReadStream from it as this
+ // just uses the parent). The only thread-safe solution is to
+ // share a read-only buffer and allocate separate
+ // MemoryReadStream's on top of it.
+ _samples[i]._data = new byte[_samples[i]._length];
+ _samples[i]._format = RAW;
+
// Read in the file (without the file header)
_f->seek(_samples[i]._offset);
- _samples[i]._data = new byte[_samples[i]._length];
_f->read(_samples[i]._data, _samples[i]._length);
- debugC(3, kDraciArchiverDebugLevel, "Cached sample %d from archive %s",
- i, _path.c_str());
+ debugC(3, kDraciArchiverDebugLevel, "Read sample %d from archive %s",
+ i, _path);
}
_samples[i]._frequency = freq ? freq : _defaultFreq;
return _samples + i;
}
+void ZipSoundArchive::openArchive(const char *path, const char *extension, SoundFormat format, int raw_frequency) {
+ closeArchive();
+
+ _archive = Common::makeZipArchive(path);
+ _path = path;
+ _extension = extension;
+ _format = format;
+ _defaultFreq = raw_frequency;
+
+ if (_archive) {
+ Common::ArchiveMemberList files;
+ _archive->listMembers(files);
+ _sampleCount = files.size();
+ }
+}
+
+void ZipSoundArchive::closeArchive() {
+ clearCache();
+ delete _archive;
+ _archive = NULL;
+ _path = _extension = NULL;
+ _sampleCount = _defaultFreq = 0;
+ _format = RAW;
+}
+
+void ZipSoundArchive::clearCache() {
+ // Just deallocate the link-list of (very short) headers for each
+ // dubbed sentence played in the current location. If the callers have
+ // not called .close() on any of the items, call them now.
+ for (Common::List<SoundSample>::iterator it = _cache.begin(); it != _cache.end(); ++it) {
+ it->close();
+ }
+ _cache.clear();
+}
+
+SoundSample *ZipSoundArchive::getSample(int i, uint freq) {
+ if (i < 0 || i >= (int) _sampleCount) {
+ return NULL;
+ }
+ debugCN(2, kDraciArchiverDebugLevel, "Accessing sample %d.%s from archive %s (format %d@%d, capacity %d): ",
+ i, _extension, _path, static_cast<int> (_format), _defaultFreq, _sampleCount);
+ if (freq != 0 && (_format != RAW && _format != RAW80)) {
+ error("Cannot resample a sound in compressed format");
+ return NULL;
+ }
+
+ // We cannot really cache anything, because createReadStreamForMember()
+ // returns the data as a ReadStream, which is not thread-safe. We thus
+ // read it again each time even if it has possibly been already cached
+ // a while ago. This is not such a problem for dubbing as for regular
+ // sound samples.
+ SoundSample sample;
+ sample._frequency = freq ? freq : _defaultFreq;
+ sample._format = _format;
+ // Read in the file (without the file header)
+ char file_name[20];
+ sprintf(file_name, "%d.%s", i+1, _extension);
+ sample._stream = _archive->createReadStreamForMember(file_name);
+ if (!sample._stream) {
+ debugC(2, kDraciArchiverDebugLevel, "Doesn't exist");
+ return NULL;
+ } else {
+ debugC(2, kDraciArchiverDebugLevel, "Read");
+ _cache.push_back(sample);
+ // Return a pointer that we own and which we will deallocate
+ // including its contents.
+ return &_cache.back();
+ }
+}
+
Sound::Sound(Audio::Mixer *mixer) : _mixer(mixer), _muteSound(false), _muteVoice(false),
_showSubtitles(true), _talkSpeed(kStandardSpeed) {
@@ -194,17 +279,59 @@ SndHandle *Sound::getHandle() {
void Sound::playSoundBuffer(Audio::SoundHandle *handle, const SoundSample &buffer, int volume,
sndHandleType handleType, bool loop) {
+ if (!buffer._stream && !buffer._data) {
+ warning("Empty stream");
+ return;
+ }
+ // Create a new SeekableReadStream which will be automatically disposed
+ // after the sample stops playing. Do not dispose the original
+ // data/stream though.
+ // Beware that if the sample comes from an archive (i.e., is stored in
+ // buffer._stream), then you must NOT play it more than once at the
+ // same time, because streams are not thread-safe. Playing it
+ // repeatedly is OK. Currently this is ensured by that archives are
+ // only used for dubbing, which is only played from one place in
+ // script.cpp, which blocks until the dubbed sentence has finished
+ // playing.
+ Common::SeekableReadStream* stream;
+ if (buffer._stream) {
+ stream = new Common::SeekableSubReadStream(buffer._stream, 0, buffer._stream->size(), DisposeAfterUse::NO);
+ } else {
+ stream = new Common::MemoryReadStream(buffer._data, buffer._length, DisposeAfterUse::NO);
+ }
- byte flags = Audio::FLAG_UNSIGNED;
+ Audio::SeekableAudioStream *reader = NULL;
+ switch (buffer._format) {
+ case RAW80:
+ stream->skip(80); // and fall-thru
+ case RAW:
+ reader = Audio::makeRawStream(stream, buffer._frequency, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
+ break;
+#ifdef USE_MAD
+ case MP3:
+ reader = Audio::makeMP3Stream(stream, DisposeAfterUse::YES);
+ break;
+#endif
+#ifdef USE_VORBIS
+ case OGG:
+ reader = Audio::makeVorbisStream(stream, DisposeAfterUse::YES);
+ break;
+#endif
+#ifdef USE_FLAC
+ case FLAC:
+ reader = Audio::makeFLACStream(stream, DisposeAfterUse::YES);
+ break;
+#endif
+ default:
+ error("Unsupported compression format %d", static_cast<int> (buffer._format));
+ delete stream;
+ return;
+ }
const Audio::Mixer::SoundType soundType = (handleType == kVoiceHandle) ?
Audio::Mixer::kSpeechSoundType : Audio::Mixer::kSFXSoundType;
-
- // Don't use DisposeAfterUse::YES, because our caching system deletes samples by itself.
- Audio::AudioStream *stream = Audio::makeLoopingAudioStream(
- Audio::makeRawStream(buffer._data, buffer._length, buffer._frequency, flags, DisposeAfterUse::NO),
- loop ? 0 : 1);
- _mixer->playStream(soundType, handle, stream, -1, volume);
+ Audio::AudioStream *audio_stream = Audio::makeLoopingAudioStream(reader, loop ? 0 : 1);
+ _mixer->playStream(soundType, handle, audio_stream, -1, volume);
}
void Sound::playSound(const SoundSample *buffer, int volume, bool loop) {
diff --git a/engines/draci/sound.h b/engines/draci/sound.h
index e4cf7efdec..b1a91bb55b 100644
--- a/engines/draci/sound.h
+++ b/engines/draci/sound.h
@@ -28,22 +28,39 @@
#include "common/str.h"
#include "common/file.h"
+#include "common/list.h"
#include "sound/mixer.h"
+namespace Common {
+class Archive;
+class SeekableReadStream;
+}
+
namespace Draci {
+enum SoundFormat { RAW, RAW80, MP3, OGG, FLAC }; // RAW80 means skip the first 80 bytes
+
/**
* Represents individual files inside the archive.
*/
struct SoundSample {
- uint _offset;
+ uint _offset; // For internal use of LegacySoundArchive
uint _length;
- uint _frequency;
- byte* _data;
+ uint _frequency; // Only when _format == RAW or RAW80
+ SoundFormat _format;
+
+ byte *_data; // At most one of these two pointer can be non-NULL
+ Common::SeekableReadStream* _stream;
+
+ SoundSample() : _offset(0), _length(0), _frequency(0), _format(RAW), _data(NULL), _stream(NULL) { }
+ // The standard copy constructor is good enough, since we only stored numbers and pointers.
+ // Don't call close() automaticall in the destructor, otherwise copying causes SIGSEGV.
void close() {
delete[] _data;
+ delete _stream;
_data = NULL;
+ _stream = NULL;
}
};
@@ -75,27 +92,28 @@ public:
/**
* Caches a given sample into memory and returns a pointer into it. We
- * own the pointer. If freq is nonzero, then the sample is played at a
- * different frequency (only used for uncompressed samples).
+ * own the returned pointer, but the user may call .close() on it,
+ * which deallocates the memory of the actual sample data. If freq is
+ * nonzero, then the sample is played at a different frequency (only
+ * works for uncompressed samples).
*/
virtual SoundSample *getSample(int i, uint freq) = 0;
};
/**
* Reads CD.SAM (with dubbing) and CD2.SAM (with sound samples) from the
- * original game.
+ * original game. Caches all read samples in a thread-safe manner.
*/
class LegacySoundArchive : public SoundArchive {
public:
- LegacySoundArchive(const Common::String &path, uint defaultFreq) :
- _path(), _samples(NULL), _sampleCount(0), _defaultFreq(defaultFreq), _opened(false), _f(NULL) {
+ LegacySoundArchive(const char *path, uint defaultFreq) :
+ _path(NULL), _samples(NULL), _sampleCount(0), _defaultFreq(defaultFreq), _opened(false), _f(NULL) {
openArchive(path);
}
-
virtual ~LegacySoundArchive() { closeArchive(); }
+ void openArchive(const char *path);
void closeArchive();
- void openArchive(const Common::String &path);
virtual uint size() const { return _sampleCount; }
virtual bool isOpen() const { return _opened; }
@@ -104,7 +122,7 @@ public:
virtual SoundSample *getSample(int i, uint freq);
private:
- Common::String _path; ///< Path to file
+ const char *_path; ///< Path to file
SoundSample *_samples; ///< Internal array of files
uint _sampleCount; ///< Number of files in archive
uint _defaultFreq; ///< The default sampling frequency of the archived samples
@@ -112,6 +130,44 @@ private:
Common::File *_f; ///< Opened file
};
+/**
+ * Reads ZIP archives with uncompressed files containing lossy-compressed
+ * samples in arbitrary format. Doesn't do any real caching and is
+ * thread-safe.
+ */
+class ZipSoundArchive : public SoundArchive {
+public:
+ ZipSoundArchive() : _archive(NULL), _path(NULL), _extension(NULL), _format(RAW), _sampleCount(0), _defaultFreq(0), _cache() { }
+ virtual ~ZipSoundArchive() { closeArchive(); }
+
+ void openArchive(const char *path, const char *extension, SoundFormat format, int raw_frequency = 0);
+ void closeArchive();
+
+ virtual uint size() const { return _sampleCount; }
+ virtual bool isOpen() const { return _archive != NULL; }
+
+ virtual void clearCache();
+ virtual SoundSample *getSample(int i, uint freq);
+
+private:
+ Common::Archive *_archive;
+ const char *_path;
+ const char *_extension;
+ SoundFormat _format;
+ uint _sampleCount;
+ uint _defaultFreq;
+
+ // Since we typically play at most 1 dubbing at a time, we could get
+ // away with having just 1 record allocated and reusing it each time.
+ // However, that would be thread-unsafe if two samples were played.
+ // Although the player does not do that, it is nicer to allow for it in
+ // principle. For each dubbed sentence, we allocate a new record in
+ // the following link-list, which is cleared during each location
+ // change. The dubbed samples themselves are manually deallocated
+ // after they end.
+ Common::List<SoundSample> _cache;
+};
+
#define SOUND_HANDLES 10
enum sndHandleType {