diff options
author | Robert Špalek | 2010-07-01 07:10:40 +0000 |
---|---|---|
committer | Robert Špalek | 2010-07-01 07:10:40 +0000 |
commit | b45d2cd92b137dc429f12f67b887a193f601afb2 (patch) | |
tree | 7a3c8a044908a0f76a1171749d669a2e8a90c163 | |
parent | b0b4f34d276b3451d6d46ac29cab6f5166cea757 (diff) | |
download | scummvm-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.cpp | 157 | ||||
-rw-r--r-- | engines/draci/sound.h | 78 |
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 { |