[Scummvm-cvs-logs] SF.net SVN: scummvm:[50543] scummvm/trunk/engines/draci

spalek at users.sourceforge.net spalek at users.sourceforge.net
Thu Jul 1 09:10:40 CEST 2010


Revision: 50543
          http://scummvm.svn.sourceforge.net/scummvm/?rev=50543&view=rev
Author:   spalek
Date:     2010-07-01 07:10:40 +0000 (Thu, 01 Jul 2010)

Log Message:
-----------
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.

Modified Paths:
--------------
    scummvm/trunk/engines/draci/sound.cpp
    scummvm/trunk/engines/draci/sound.h

Modified: scummvm/trunk/engines/draci/sound.cpp
===================================================================
--- scummvm/trunk/engines/draci/sound.cpp	2010-07-01 06:07:24 UTC (rev 50542)
+++ scummvm/trunk/engines/draci/sound.cpp	2010-07-01 07:10:40 UTC (rev 50543)
@@ -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 @@
 			_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 @@
 	}
 
 	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 @@
 
 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) {

Modified: scummvm/trunk/engines/draci/sound.h
===================================================================
--- scummvm/trunk/engines/draci/sound.h	2010-07-01 06:07:24 UTC (rev 50542)
+++ scummvm/trunk/engines/draci/sound.h	2010-07-01 07:10:40 UTC (rev 50543)
@@ -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 @@
 
 	/**
 	 * 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 @@
 	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 @@
 	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 {


This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.




More information about the Scummvm-git-logs mailing list