aboutsummaryrefslogtreecommitdiff
path: root/sound/decoders/vorbis.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sound/decoders/vorbis.cpp')
-rw-r--r--sound/decoders/vorbis.cpp250
1 files changed, 250 insertions, 0 deletions
diff --git a/sound/decoders/vorbis.cpp b/sound/decoders/vorbis.cpp
new file mode 100644
index 0000000000..46a94a6214
--- /dev/null
+++ b/sound/decoders/vorbis.cpp
@@ -0,0 +1,250 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "sound/decoders/vorbis.h"
+
+#ifdef USE_VORBIS
+
+#include "common/debug.h"
+#include "common/stream.h"
+#include "common/util.h"
+
+#include "sound/audiostream.h"
+#include "sound/audiocd.h"
+
+#ifdef USE_TREMOR
+#ifdef __GP32__ // GP32 uses custom libtremor
+#include <ivorbisfile.h>
+#else
+#include <tremor/ivorbisfile.h>
+#endif
+#else
+#include <vorbis/vorbisfile.h>
+#endif
+
+
+namespace Audio {
+
+// These are wrapper functions to allow using a SeekableReadStream object to
+// provide data to the OggVorbis_File object.
+
+static size_t read_stream_wrap(void *ptr, size_t size, size_t nmemb, void *datasource) {
+ Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource;
+
+ uint32 result = stream->read(ptr, size * nmemb);
+
+ return result / size;
+}
+
+static int seek_stream_wrap(void *datasource, ogg_int64_t offset, int whence) {
+ Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource;
+ stream->seek((int32)offset, whence);
+ return stream->pos();
+}
+
+static int close_stream_wrap(void *datasource) {
+ // Do nothing -- we leave it up to the VorbisInputStream to free memory as appropriate.
+ return 0;
+}
+
+static long tell_stream_wrap(void *datasource) {
+ Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource;
+ return stream->pos();
+}
+
+static ov_callbacks g_stream_wrap = {
+ read_stream_wrap, seek_stream_wrap, close_stream_wrap, tell_stream_wrap
+};
+
+
+
+#pragma mark -
+#pragma mark --- Ogg Vorbis stream ---
+#pragma mark -
+
+
+class VorbisInputStream : public SeekableAudioStream {
+protected:
+ Common::SeekableReadStream *_inStream;
+ DisposeAfterUse::Flag _disposeAfterUse;
+
+ bool _isStereo;
+ int _rate;
+
+ Timestamp _length;
+
+ OggVorbis_File _ovFile;
+
+ int16 _buffer[4096];
+ const int16 *_bufferEnd;
+ const int16 *_pos;
+
+public:
+ // startTime / duration are in milliseconds
+ VorbisInputStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose);
+ ~VorbisInputStream();
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool endOfData() const { return _pos >= _bufferEnd; }
+ bool isStereo() const { return _isStereo; }
+ int getRate() const { return _rate; }
+
+ bool seek(const Timestamp &where);
+ Timestamp getLength() const { return _length; }
+protected:
+ bool refill();
+};
+
+VorbisInputStream::VorbisInputStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) :
+ _inStream(inStream),
+ _disposeAfterUse(dispose),
+ _length(0, 1000),
+ _bufferEnd(_buffer + ARRAYSIZE(_buffer)) {
+
+ int res = ov_open_callbacks(inStream, &_ovFile, NULL, 0, g_stream_wrap);
+ if (res < 0) {
+ warning("Could not create Vorbis stream (%d)", res);
+ _pos = _bufferEnd;
+ return;
+ }
+
+ // Read in initial data
+ if (!refill())
+ return;
+
+ // Setup some header information
+ _isStereo = ov_info(&_ovFile, -1)->channels >= 2;
+ _rate = ov_info(&_ovFile, -1)->rate;
+
+#ifdef USE_TREMOR
+ _length = Timestamp(ov_time_total(&_ovFile, -1), getRate());
+#else
+ _length = Timestamp(uint32(ov_time_total(&_ovFile, -1) * 1000.0), getRate());
+#endif
+}
+
+VorbisInputStream::~VorbisInputStream() {
+ ov_clear(&_ovFile);
+ if (_disposeAfterUse == DisposeAfterUse::YES)
+ delete _inStream;
+}
+
+int VorbisInputStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples = 0;
+ while (samples < numSamples && _pos < _bufferEnd) {
+ const int len = MIN(numSamples - samples, (int)(_bufferEnd - _pos));
+ memcpy(buffer, _pos, len * 2);
+ buffer += len;
+ _pos += len;
+ samples += len;
+ if (_pos >= _bufferEnd) {
+ if (!refill())
+ break;
+ }
+ }
+ return samples;
+}
+
+bool VorbisInputStream::seek(const Timestamp &where) {
+ int res = ov_pcm_seek(&_ovFile, calculateSampleOffset(where, getRate()));
+ if (res) {
+ warning("Error seeking in Vorbis stream (%d)", res);
+ _pos = _bufferEnd;
+ return false;
+ }
+
+ return refill();
+}
+
+bool VorbisInputStream::refill() {
+ // Read the samples
+ uint len_left = sizeof(_buffer);
+ char *read_pos = (char *)_buffer;
+
+ while (len_left > 0) {
+ long result;
+
+#ifdef USE_TREMOR
+ // Tremor ov_read() always returns data as signed 16 bit interleaved PCM
+ // in host byte order. As such, it does not take arguments to request
+ // specific signedness, byte order or bit depth as in Vorbisfile.
+ result = ov_read(&_ovFile, read_pos, len_left,
+ NULL);
+#else
+#ifdef SCUMM_BIG_ENDIAN
+ result = ov_read(&_ovFile, read_pos, len_left,
+ 1,
+ 2, // 16 bit
+ 1, // signed
+ NULL);
+#else
+ result = ov_read(&_ovFile, read_pos, len_left,
+ 0,
+ 2, // 16 bit
+ 1, // signed
+ NULL);
+#endif
+#endif
+ if (result == OV_HOLE) {
+ // Possibly recoverable, just warn about it
+ warning("Corrupted data in Vorbis file");
+ } else if (result == 0) {
+ //warning("End of file while reading from Vorbis file");
+ //_pos = _bufferEnd;
+ //return false;
+ break;
+ } else if (result < 0) {
+ warning("Error reading from Vorbis stream (%d)", int(result));
+ _pos = _bufferEnd;
+ // Don't delete it yet, that causes problems in
+ // the CD player emulation code.
+ return false;
+ } else {
+ len_left -= result;
+ read_pos += result;
+ }
+ }
+
+ _pos = _buffer;
+ _bufferEnd = (int16 *)read_pos;
+
+ return true;
+}
+
+
+#pragma mark -
+#pragma mark --- Ogg Vorbis factory functions ---
+#pragma mark -
+
+SeekableAudioStream *makeVorbisStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse) {
+ return new VorbisInputStream(stream, disposeAfterUse);
+}
+
+} // End of namespace Audio
+
+#endif // #ifdef USE_VORBIS