Browse Source

Audio source decoding improvements.

- Streaming sources now also stream the file contents instead of loading it into memory, by default. Added a new stream type enum parameter to newSource ("file" or "memory").
- Audio file decoding now chooses the most appropriate decoder based on the file contents instead of the file extension.
- Videos now stream audio from the file instead of loading all of the video file into memory for use with audio decoding.
Alex Szpakowski 3 years ago
parent
commit
c09fef8e79

+ 27 - 14
src/modules/audio/wrap_Audio.cpp

@@ -20,6 +20,7 @@
 
 // LOVE
 #include "wrap_Audio.h"
+#include "filesystem/wrap_Filesystem.h"
 
 #include "openal/Audio.h"
 #include "null/Audio.h"
@@ -57,8 +58,23 @@ int w_newSource(lua_State *L)
 			return luaL_error(L, "Cannot create queueable sources using newSource. Use newQueueableSource instead.");
 	}
 
-	if (lua_isstring(L, 1) || luax_istype(L, 1, love::filesystem::File::type) || luax_istype(L, 1, love::filesystem::FileData::type))
-		luax_convobj(L, 1, "sound", "newDecoder");
+	if (love::filesystem::luax_cangetdata(L, 1))
+	{
+		// stream type
+		if (stype == Source::TYPE_STATIC)
+			lua_pushstring(L, "memory");
+		else if (!lua_isnone(L, 3))
+			lua_pushvalue(L, 3);
+		else
+			lua_pushnil(L);
+
+		// buffer size
+		lua_pushnil(L);
+
+		// (file, buffer size, stream type)
+		int idxs[] = { 1, lua_gettop(L), lua_gettop(L) - 1 };
+		luax_convobj(L, idxs, 3, "sound", "newDecoder");
+	}
 
 	if (stype == Source::TYPE_STATIC && luax_istype(L, 1, love::sound::Decoder::type))
 		luax_convobj(L, 1, "sound", "newSoundData");
@@ -84,20 +100,17 @@ int w_newSource(lua_State *L)
 
 int w_newQueueableSource(lua_State *L)
 {
-	Source *t = nullptr;
+	int samplerate = (int) luaL_checkinteger(L, 1);
+	int bitdepth = (int) luaL_checkinteger(L, 2);
+	int channels = (int) luaL_checkinteger(L, 3);
+	int buffers = (int) luaL_optinteger(L, 4, 0);
 
-	luax_catchexcept(L, [&]() {
-		t = instance()->newSource((int)luaL_checkinteger(L, 1), (int)luaL_checkinteger(L, 2), (int)luaL_checkinteger(L, 3), (int)luaL_optinteger(L, 4, 0));
-	});
+	Source *t = nullptr;
+	luax_catchexcept(L, [&]() { t = instance()->newSource(samplerate, bitdepth, channels, buffers); });
 
-	if (t != nullptr)
-	{
-		luax_pushtype(L, t);
-		t->release();
-		return 1;
-	}
-	else
-		return 0; //all argument type errors are checked in above constructor
+	luax_pushtype(L, t);
+	t->release();
+	return 1;
 }
 
 static std::vector<Source*> readSourceList(lua_State *L, int n)

+ 1 - 1
src/modules/graphics/wrap_Graphics.lua

@@ -33,7 +33,7 @@ function love.graphics.newVideo(file, settings)
 	local source, success
 
 	if settings.audio ~= false and love.audio then
-		success, source = pcall(love.audio.newSource, video:getStream():getFilename(), "stream")
+		success, source = pcall(love.audio.newSource, video:getStream():getFilename(), "stream", "file")
 	end
 	if success then
 		video:setSource(source)

+ 12 - 2
src/modules/sound/Decoder.cpp

@@ -29,13 +29,16 @@ namespace sound
 
 love::Type Decoder::type("Decoder", &Object::type);
 
-Decoder::Decoder(Data *data, int bufferSize)
-	: data(data)
+Decoder::Decoder(Stream *stream, int bufferSize)
+	: stream(stream)
 	, bufferSize(bufferSize)
 	, sampleRate(DEFAULT_SAMPLE_RATE)
 	, buffer(0)
 	, eof(false)
 {
+	if (!stream->isReadable() || !stream->isSeekable())
+		throw love::Exception("Decoder input stream must be readable and seekable.");
+
 	buffer = new char[bufferSize];
 }
 
@@ -65,5 +68,12 @@ bool Decoder::isFinished()
 	return eof;
 }
 
+STRINGMAP_CLASS_BEGIN(Decoder, Decoder::StreamSource, Decoder::STREAM_MAX_ENUM, streamSource)
+{
+	{ "memory", Decoder::STREAM_MEMORY },
+	{ "file",   Decoder::STREAM_FILE   },
+}
+STRINGMAP_CLASS_END(Decoder, Decoder::StreamSource, Decoder::STREAM_MAX_ENUM, streamSource)
+
 } // sound
 } // love

+ 14 - 5
src/modules/sound/Decoder.h

@@ -23,7 +23,8 @@
 
 // LOVE
 #include "common/Object.h"
-#include "filesystem/File.h"
+#include "common/Stream.h"
+#include "common/StringMap.h"
 
 #include <string>
 
@@ -39,9 +40,16 @@ class Decoder : public Object
 {
 public:
 
+	enum StreamSource
+	{
+		STREAM_MEMORY,
+		STREAM_FILE,
+		STREAM_MAX_ENUM
+	};
+
 	static love::Type type;
 
-	Decoder(Data *data, int bufferSize);
+	Decoder(Stream *stream, int bufferSize);
 	virtual ~Decoder();
 
 	/**
@@ -143,11 +151,12 @@ public:
 	 **/
 	virtual double getDuration() = 0;
 
+	STRINGMAP_CLASS_DECLARE(StreamSource);
+
 protected:
 
-	// The encoded data. This should be replaced with buffered file
-	// reads in the future.
-	StrongRef<Data> data;
+	// A readable stream containing the encoded data.
+	StrongRef<Stream> stream;
 
 	// When the decoder decodes data incrementally, it writes
 	// this many bytes at a time (at most).

+ 3 - 3
src/modules/sound/Sound.h

@@ -23,7 +23,7 @@
 
 // LOVE
 #include "common/Module.h"
-#include "filesystem/File.h"
+#include "common/Stream.h"
 
 #include "SoundData.h"
 #include "Decoder.h"
@@ -83,11 +83,11 @@ public:
 	/**
 	 * Attempts to find a decoder for the encoded sound data in the
 	 * specified file.
-	 * @param file The file with encoded sound data.
+	 * @param stream The readable Stream with encoded sound data.
 	 * @param bufferSize The size of each decoded chunk.
 	 * @return A Decoder object on success, or zero if no decoder could be found.
 	 **/
-	virtual Decoder *newDecoder(filesystem::FileData *file, int bufferSize) = 0;
+	virtual Decoder *newDecoder(Stream *stream, int bufferSize) = 0;
 
 }; // Sound
 

+ 18 - 81
src/modules/sound/lullaby/CoreAudioDecoder.cpp

@@ -34,37 +34,23 @@ namespace lullaby
 {
 
 // Callbacks
-namespace
+static OSStatus readFunc(void *inClientData, SInt64 inPosition, UInt32 requestCount, void *buffer, UInt32 *actualCount)
 {
-OSStatus readFunc(void *inClientData, SInt64 inPosition, UInt32 requestCount, void *buffer, UInt32 *actualCount)
-{
-	Data *data = (Data *) inClientData;
-	SInt64 bytesLeft = data->getSize() - inPosition;
-
-	if (bytesLeft > 0)
-	{
-		UInt32 actualSize = bytesLeft >= requestCount ? requestCount : (UInt32) bytesLeft;
-		memcpy(buffer, (char *) data->getData() + inPosition, actualSize);
-		*actualCount = actualSize;
-	}
-	else
-	{
-		*actualCount = 0;
-		return kAudioFilePositionError;
-	}
+	auto stream = (Stream *) inClientData;
+	int64 readbytes = stream->read(buffer, requestCount);
 
-	return noErr;
+	*actualCount = (UInt32) readbytes;
+	return readbytes > 0 ? noErr : kAudioFilePositionError;
 }
 
-SInt64 getSizeFunc(void *inClientData)
+static SInt64 getSizeFunc(void *inClientData)
 {
-	Data *data = (Data *) inClientData;
-	return data->getSize();
+	auto stream = (Stream *) inClientData;
+	return stream->getSize();
 }
-} // anonymous namespace
 
-CoreAudioDecoder::CoreAudioDecoder(Data *data, int bufferSize)
-	: Decoder(data, bufferSize)
+CoreAudioDecoder::CoreAudioDecoder(Stream *stream, int bufferSize)
+	: Decoder(stream, bufferSize)
 	, audioFile(nullptr)
 	, extAudioFile(nullptr)
 	, inputInfo()
@@ -75,23 +61,23 @@ CoreAudioDecoder::CoreAudioDecoder(Data *data, int bufferSize)
 	{
 		OSStatus err = noErr;
 
-		// Open the file represented by the Data.
-		err = AudioFileOpenWithCallbacks(data, readFunc, nullptr, getSizeFunc, nullptr, kAudioFileMP3Type, &audioFile);
+		// Open the file represented by the Stream.
+		err = AudioFileOpenWithCallbacks(stream, readFunc, nullptr, getSizeFunc, nullptr, kAudioFileMP3Type, &audioFile);
 		if (err != noErr)
-			throw love::Exception("Could open audio file for decoding.");
+			throw love::Exception("Could open audio file for decoding with CoreAudio.");
 
 		// We want to use the Extended AudioFile API.
 		err = ExtAudioFileWrapAudioFileID(audioFile, false, &extAudioFile);
 
 		if (err != noErr)
-			throw love::Exception("Could open audio file for decoding.");
+			throw love::Exception("Could open audio file for decoding with CoreAudio.");
 
 		// Get the format of the audio data.
 		UInt32 propertySize = sizeof(inputInfo);
 		err = ExtAudioFileGetProperty(extAudioFile, kExtAudioFileProperty_FileDataFormat, &propertySize, &inputInfo);
 
 		if (err != noErr)
-			throw love::Exception("Could not determine file format.");
+			throw love::Exception("Could not determine CoreAudio file format.");
 
 		// Set the output format to 16 bit signed integer (native-endian) data.
 		// Keep the channel count and sample rate of the source format.
@@ -116,7 +102,7 @@ CoreAudioDecoder::CoreAudioDecoder(Data *data, int bufferSize)
 		err = ExtAudioFileSetProperty(extAudioFile, kExtAudioFileProperty_ClientDataFormat, propertySize, &outputInfo);
 
 		if (err != noErr)
-			throw love::Exception("Could not set decoder properties.");
+			throw love::Exception("Could not set CoreAudio decoder properties.");
 	}
 	catch (love::Exception &)
 	{
@@ -143,59 +129,10 @@ void CoreAudioDecoder::closeAudioFile()
 	audioFile = nullptr;
 }
 
-bool CoreAudioDecoder::accepts(const std::string &ext)
-{
-	UInt32 size = 0;
-	std::vector<UInt32> types;
-
-	// Get the size in bytes of the type array we're about to get.
-	OSStatus err = AudioFileGetGlobalInfoSize(kAudioFileGlobalInfo_ReadableTypes, sizeof(UInt32), nullptr, &size);
-	if (err != noErr)
-		return false;
-
-	types.resize(size / sizeof(UInt32));
-
-	// Get an array of supported types.
-	err = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_ReadableTypes, 0, nullptr, &size, &types[0]);
-	if (err != noErr)
-		return false;
-
-	// Turn the extension string into a CFStringRef.
-	CFStringRef extstr = CFStringCreateWithCString(nullptr, ext.c_str(), kCFStringEncodingUTF8);
-
-	CFArrayRef exts = nullptr;
-	size = sizeof(CFArrayRef);
-
-	for (UInt32 type : types)
-	{
-		// Get the extension strings for the type.
-		err = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_ExtensionsForType, sizeof(UInt32), &type, &size, &exts);
-		if (err != noErr)
-			continue;
-
-		// A type can have more than one extension string.
-		for (CFIndex i = 0; i < CFArrayGetCount(exts); i++)
-		{
-			CFStringRef value = (CFStringRef) CFArrayGetValueAtIndex(exts, i);
-
-			if (CFStringCompare(extstr, value, 0) == kCFCompareEqualTo)
-			{
-				CFRelease(extstr);
-				CFRelease(exts);
-				return true;
-			}
-		}
-
-		CFRelease(exts);
-	}
-
-	CFRelease(extstr);
-	return false;
-}
-
 love::sound::Decoder *CoreAudioDecoder::clone()
 {
-	return new CoreAudioDecoder(data.get(), bufferSize);
+	StrongRef<Stream> s(stream->clone(), Acquire::NORETAIN);
+	return new CoreAudioDecoder(s, bufferSize);
 }
 
 int CoreAudioDecoder::decode()

+ 10 - 12
src/modules/sound/lullaby/CoreAudioDecoder.h

@@ -26,7 +26,7 @@
 #ifdef LOVE_SUPPORT_COREAUDIO
 
 // LOVE
-#include "common/Data.h"
+#include "common/Stream.h"
 #include "sound/Decoder.h"
 
 // Core Audio
@@ -47,19 +47,17 @@ class CoreAudioDecoder : public Decoder
 {
 public:
 
-	CoreAudioDecoder(Data *data, int bufferSize);
+	CoreAudioDecoder(Stream *stream, int bufferSize);
 	virtual ~CoreAudioDecoder();
 
-	static bool accepts(const std::string &ext);
-
-	love::sound::Decoder *clone();
-	int decode();
-	bool seek(double s);
-	bool rewind();
-	bool isSeekable();
-	int getChannelCount() const;
-	int getBitDepth() const;
-	double getDuration();
+	love::sound::Decoder *clone() override;
+	int decode() override;
+	bool seek(double s) override;
+	bool rewind() override;
+	bool isSeekable() override;
+	int getChannelCount() const override;
+	int getBitDepth() const override;
+	double getDuration() override;
 
 private:
 

+ 20 - 24
src/modules/sound/lullaby/FLACDecoder.cpp

@@ -22,6 +22,7 @@
 #include "FLACDecoder.h"
 
 #include <set>
+#include <algorithm>
 #include "common/Exception.h"
 
 namespace love
@@ -31,42 +32,37 @@ namespace sound
 namespace lullaby
 {
 
-FLACDecoder::FLACDecoder(Data *data, int nbufferSize)
-: Decoder(data, nbufferSize)
+static size_t onRead(void *pUserData, void *pBufferOut, size_t bytesToRead)
 {
-	flac = drflac_open_memory(data->getData(), data->getSize(), nullptr);
-	if (flac == nullptr)
-		throw love::Exception("Could not load FLAC file");
+	auto stream = (Stream *) pUserData;
+	int64 read = stream->read(pBufferOut, bytesToRead);
+	return std::max<int64>(0, read);
 }
 
-FLACDecoder::~FLACDecoder()
+static drflac_bool32 onSeek(void* pUserData, int offset, drflac_seek_origin origin)
 {
-	drflac_close(flac);
+	auto stream = (Stream *) pUserData;
+	auto seekorigin = origin == drflac_seek_origin_current ? Stream::SEEKORIGIN_CURRENT : Stream::SEEKORIGIN_BEGIN;
+	return stream->seek(offset, seekorigin) ? DRFLAC_TRUE : DRFLAC_FALSE;
 }
 
-bool FLACDecoder::accepts(const std::string &ext)
+FLACDecoder::FLACDecoder(Stream *stream, int nbufferSize)
+	: Decoder(stream, nbufferSize)
 {
-	// dr_flac supports FLAC encapsulated in Ogg, but unfortunately
-	// LOVE detects .ogg extension as Vorbis. It would be a good idea
-	// to always probe in the future (see #1487 and commit ccf9e63).
-	// Please remove once it's no longer the case.
-	static const std::string supported[] =
-	{
-		"flac", "ogg"
-	};
-
-	for (const auto& s : supported)
-	{
-		if (s.compare(ext) == 0)
-			return true;
-	}
+	flac = drflac_open(onRead, onSeek, stream, nullptr);
+	if (flac == nullptr)
+		throw love::Exception("Could not load FLAC file");
+}
 
-	return false;
+FLACDecoder::~FLACDecoder()
+{
+	drflac_close(flac);
 }
 
 love::sound::Decoder *FLACDecoder::clone()
 {
-	return new FLACDecoder(data.get(), bufferSize);
+	StrongRef<Stream> s(stream->clone(), Acquire::NORETAIN);
+	return new FLACDecoder(s, bufferSize);
 }
 
 int FLACDecoder::decode()

+ 12 - 13
src/modules/sound/lullaby/FLACDecoder.h

@@ -22,7 +22,7 @@
 #define LOVE_SOUND_LULLABY_FLAC_DECODER_H
 
 // LOVE
-#include "common/Data.h"
+#include "common/Stream.h"
 #include "sound/Decoder.h"
 
 #include "dr/dr_flac.h"
@@ -38,23 +38,22 @@ namespace lullaby
 class FLACDecoder : public Decoder
 {
 public:
-	FLACDecoder(Data *data, int bufferSize);
+	FLACDecoder(Stream *stream, int bufferSize);
 	~FLACDecoder();
 
-	static bool accepts(const std::string &ext);
-	love::sound::Decoder *clone();
-	int decode();
-	bool seek(double s);
-	bool rewind();
-	bool isSeekable();
-	int getChannelCount() const;
-	int getBitDepth() const;
-	int getSampleRate() const;
-	double getDuration();
+	love::sound::Decoder *clone() override;
+	int decode() override;
+	bool seek(double s) override;
+	bool rewind() override;
+	bool isSeekable() override;
+	int getChannelCount() const override;
+	int getBitDepth() const override;
+	int getSampleRate() const override;
+	double getDuration() override;
 
 private:
 	drflac *flac;
-}; // Decoder
+}; // FLACDecoder
 
 } // lullaby
 } // sound

+ 23 - 23
src/modules/sound/lullaby/MP3Decoder.cpp

@@ -21,6 +21,7 @@
 #define DR_MP3_IMPLEMENTATION
 #define DR_MP3_NO_STDIO
 #include "MP3Decoder.h"
+#include "common/Exception.h"
 
 namespace love
 {
@@ -29,11 +30,25 @@ namespace sound
 namespace lullaby
 {
 
-MP3Decoder::MP3Decoder(Data *data, int bufferSize)
-: Decoder(data, bufferSize)
+static size_t onRead(void *pUserData, void *pBufferOut, size_t bytesToRead)
+{
+	auto stream = (Stream *) pUserData;
+	int64 read = stream->read(pBufferOut, bytesToRead);
+	return std::max<int64>(0, read);
+}
+
+static drmp3_bool32 onSeek(void *pUserData, int offset, drmp3_seek_origin origin)
+{
+	auto stream = (Stream *) pUserData;
+	auto seekorigin = origin == drmp3_seek_origin_current ? Stream::SEEKORIGIN_CURRENT : Stream::SEEKORIGIN_BEGIN;
+	return stream->seek(offset, seekorigin) ? DRMP3_TRUE : DRMP3_FALSE;
+}
+
+MP3Decoder::MP3Decoder(Stream *stream, int bufferSize)
+	: Decoder(stream, bufferSize)
 {
 	// initialize mp3 handle
-	if(drmp3_init_memory(&mp3, data->getData(), data->getSize(), nullptr, nullptr) == 0)
+	if (!drmp3_init(&mp3, onRead, onSeek, stream, nullptr, nullptr))
 		throw love::Exception("Could not read mp3 data.");
 
 	sampleRate = mp3.sampleRate;
@@ -43,7 +58,7 @@ MP3Decoder::MP3Decoder(Data *data, int bufferSize)
 	if (!drmp3_get_mp3_and_pcm_frame_count(&mp3, &mp3FrameCount, &pcmCount))
 	{
 		drmp3_uninit(&mp3);
-		throw love::Exception("Could not calculate duration.");
+		throw love::Exception("Could not calculate mp3 duration.");
 	}
 	duration = ((double) pcmCount) / ((double) mp3.sampleRate);
 
@@ -53,7 +68,7 @@ MP3Decoder::MP3Decoder(Data *data, int bufferSize)
 	if (!drmp3_calculate_seek_points(&mp3, &mp3FrameInt, seekTable.data()))
 	{
 		drmp3_uninit(&mp3);
-		throw love::Exception("Could not calculate seek table");
+		throw love::Exception("Could not calculate mp3 seek table");
 	}
 	mp3FrameInt = mp3FrameInt > mp3FrameCount ? mp3FrameCount : mp3FrameInt;
 
@@ -61,7 +76,7 @@ MP3Decoder::MP3Decoder(Data *data, int bufferSize)
 	if (!drmp3_bind_seek_table(&mp3, mp3FrameInt, seekTable.data()))
 	{
 		drmp3_uninit(&mp3);
-		throw love::Exception("Could not bind seek table");
+		throw love::Exception("Could not bind mp3 seek table");
 	}
 }
 
@@ -70,25 +85,10 @@ MP3Decoder::~MP3Decoder()
 	drmp3_uninit(&mp3);
 }
 
-bool MP3Decoder::accepts(const std::string &ext)
-{
-	static const std::string supported[] =
-	{
-		"mp3"
-	};
-
-	for (const auto& s : supported)
-	{
-		if (s.compare(ext) == 0)
-			return true;
-	}
-
-	return false;
-}
-
 love::sound::Decoder *MP3Decoder::clone()
 {
-	return new MP3Decoder(data, bufferSize);
+	StrongRef<Stream> s(stream->clone(), Acquire::NORETAIN);
+	return new MP3Decoder(s, bufferSize);
 }
 
 int MP3Decoder::decode()

+ 10 - 11
src/modules/sound/lullaby/MP3Decoder.h

@@ -22,7 +22,7 @@
 #define LOVE_SOUND_LULLABY_MP3_DECODER_H
 
 // LOVE
-#include "common/Data.h"
+#include "common/Stream.h"
 #include "sound/Decoder.h"
 
 // dr_mp3
@@ -41,18 +41,17 @@ class MP3Decoder: public love::sound::Decoder
 {
 public:
 
-	MP3Decoder(Data *data, int bufsize);
+	MP3Decoder(Stream *stream, int bufsize);
 	virtual ~MP3Decoder();
 
-	static bool accepts(const std::string &ext);
-	love::sound::Decoder *clone();
-	int decode();
-	bool seek(double s);
-	bool rewind();
-	bool isSeekable();
-	int getChannelCount() const;
-	int getBitDepth() const;
-	double getDuration();
+	love::sound::Decoder *clone() override;
+	int decode() override;
+	bool seek(double s) override;
+	bool rewind() override;
+	bool isSeekable() override;
+	int getChannelCount() const override;
+	int getBitDepth() const override;
+	double getDuration() override;
 
 private:
 

+ 27 - 26
src/modules/sound/lullaby/ModPlugDecoder.cpp

@@ -23,6 +23,7 @@
 #ifndef LOVE_NO_MODPLUG
 
 #include "common/Exception.h"
+#include "common/Data.h"
 
 namespace love
 {
@@ -31,12 +32,11 @@ namespace sound
 namespace lullaby
 {
 
-ModPlugDecoder::ModPlugDecoder(Data *data, int bufferSize)
-	: Decoder(data, bufferSize)
+ModPlugDecoder::ModPlugDecoder(Stream *stream, int bufferSize)
+	: Decoder(stream, bufferSize)
 	, plug(0)
 	, duration(-2.0)
 {
-
 	// Set some ModPlug settings.
 	settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION;
 	settings.mChannels = 2;
@@ -60,10 +60,29 @@ ModPlugDecoder::ModPlugDecoder(Data *data, int bufferSize)
 
 	ModPlug_SetSettings(&settings);
 
+	// ModPlug has no streaming API. Miserable.
+	// We don't want to load the entire stream immediately if it's big, because
+	// it might not be compatible with ModPlug. So we just try to load 4MB and
+	// see if that works, and then load the whole thing if it does.
+	if (stream->getSize() > 1024 * 1024 * 4)
+	{
+		data.set(stream->read(1024 * 1024 * 4), Acquire::NORETAIN);
+
+		plug = ModPlug_Load(data->getData(), (int)data->getSize());
+
+		if (plug == nullptr)
+			throw love::Exception("Could not load file with ModPlug.");
+
+		stream->seek(0);
+		ModPlug_Unload(plug);
+	}
+
+	data.set(stream->read(stream->getSize()), Acquire::NORETAIN);
+
 	// Load the module.
-	plug = ModPlug_Load(data->getData(), (int) data->getSize());
+	plug = ModPlug_Load(data->getData(), (int)data->getSize());
 
-	if (plug == 0)
+	if (plug == nullptr)
 		throw love::Exception("Could not load file with ModPlug.");
 
 	// set master volume for delicate ears
@@ -72,32 +91,14 @@ ModPlugDecoder::ModPlugDecoder(Data *data, int bufferSize)
 
 ModPlugDecoder::~ModPlugDecoder()
 {
-	if (plug != 0)
+	if (plug != nullptr)
 		ModPlug_Unload(plug);
 }
 
-bool ModPlugDecoder::accepts(const std::string &ext)
-{
-	static const std::string supported[] =
-	{
-		"699", "abc", "amf", "ams", "dbm", "dmf",
-		"dsm", "far", "it", "j2b", "mdl", "med",
-		"mid", "mod", "mt2", "mtm", "okt", "pat",
-		"psm", "s3m", "stm", "ult", "umx",  "xm",
-	};
-
-	for (const auto &s : supported)
-	{
-		if (s.compare(ext) == 0)
-			return true;
-	}
-
-	return false;
-}
-
 love::sound::Decoder *ModPlugDecoder::clone()
 {
-	return new ModPlugDecoder(data.get(), bufferSize);
+	StrongRef<Stream> s(stream->clone(), Acquire::NORETAIN);
+	return new ModPlugDecoder(s, bufferSize);
 }
 
 int ModPlugDecoder::decode()

+ 13 - 13
src/modules/sound/lullaby/ModPlugDecoder.h

@@ -26,7 +26,7 @@
 #ifndef LOVE_NO_MODPLUG
 
 // LOVE
-#include "common/Data.h"
+#include "common/Stream.h"
 #include "sound/Decoder.h"
 
 // libmodplug
@@ -47,28 +47,28 @@ class ModPlugDecoder : public Decoder
 {
 public:
 
-	ModPlugDecoder(Data *data, int bufferSize);
+	ModPlugDecoder(Stream *stream, int bufferSize);
 	virtual ~ModPlugDecoder();
 
-	static bool accepts(const std::string &ext);
-
-	love::sound::Decoder *clone();
-	int decode();
-	bool seek(double s);
-	bool rewind();
-	bool isSeekable();
-	int getChannelCount() const;
-	int getBitDepth() const;
-	double getDuration();
+	love::sound::Decoder *clone() override;
+	int decode() override;
+	bool seek(double s) override;
+	bool rewind() override;
+	bool isSeekable() override;
+	int getChannelCount() const override;
+	int getBitDepth() const override;
+	double getDuration() override;
 
 private:
 
+	StrongRef<Data> data;
+
 	ModPlugFile *plug;
 	ModPlug_Settings settings;
 
 	double duration;
 
-}; // Decoder
+}; // ModPlugDecoder
 
 } // lullaby
 } // sound

+ 11 - 27
src/modules/sound/lullaby/Sound.cpp

@@ -37,21 +37,16 @@
 
 struct DecoderImpl
 {
-	love::sound::Decoder *(*create)(love::filesystem::FileData *data, int bufferSize);
-	bool (*accepts)(const std::string& ext);
+	love::sound::Decoder *(*create)(love::Stream *stream, int bufferSize);
 };
 
 template<typename DecoderType>
 DecoderImpl DecoderImplFor()
 {
 	DecoderImpl decoderImpl;
-	decoderImpl.create = [](love::filesystem::FileData *data, int bufferSize) -> love::sound::Decoder*
+	decoderImpl.create = [](love::Stream *stream, int bufferSize) -> love::sound::Decoder*
 	{
-		return new DecoderType(data, bufferSize);
-	};
-	decoderImpl.accepts = [](const std::string& ext) -> bool
-	{
-		return DecoderType::accepts(ext);
+		return new DecoderType(stream, bufferSize);
 	};
 	return decoderImpl;
 }
@@ -76,15 +71,9 @@ const char *Sound::getName() const
 	return "love.sound.lullaby";
 }
 
-sound::Decoder *Sound::newDecoder(love::filesystem::FileData *data, int bufferSize)
+sound::Decoder *Sound::newDecoder(Stream *stream, int bufferSize)
 {
-	std::string ext = data->getExtension();
-	std::transform(ext.begin(), ext.end(), ext.begin(), tolower);
-
 	std::vector<DecoderImpl> possibleDecoders = {
-#ifndef LOVE_NO_MODPLUG
-		DecoderImplFor<ModPlugDecoder>(),
-#endif // LOVE_NO_MODPLUG
 		DecoderImplFor<MP3Decoder>(),
 		DecoderImplFor<VorbisDecoder>(),
 #ifdef LOVE_SUPPORT_COREAUDIO
@@ -92,24 +81,19 @@ sound::Decoder *Sound::newDecoder(love::filesystem::FileData *data, int bufferSi
 #endif
 		DecoderImplFor<WaveDecoder>(),
 		DecoderImplFor<FLACDecoder>(),
-		// DecoderImplFor<OtherDecoder>(),
+#ifndef LOVE_NO_MODPLUG
+		DecoderImplFor<ModPlugDecoder>(), // Last because it doesn't work well with Streams.
+#endif
 	};
 
-	// First find a matching decoder based on extension
-	for (DecoderImpl &possibleDecoder : possibleDecoders)
-	{
-		if (possibleDecoder.accepts(ext))
-			return possibleDecoder.create(data, bufferSize);
-	}
-
-	// If that fails, start probing instead
 	std::stringstream decodingErrors;
 	decodingErrors << "Failed to determine file type:\n";
 	for (DecoderImpl &possibleDecoder : possibleDecoders)
 	{
 		try
 		{
-			sound::Decoder *decoder = possibleDecoder.create(data, bufferSize);
+			stream->seek(0);
+			sound::Decoder *decoder = possibleDecoder.create(stream, bufferSize);
 			return decoder;
 		}
 		catch (love::Exception &e)
@@ -118,8 +102,8 @@ sound::Decoder *Sound::newDecoder(love::filesystem::FileData *data, int bufferSi
 		}
 	}
 
-	// Probing failed too, bail with the accumulated errors
-	throw love::Exception(decodingErrors.str().c_str());
+	std::string errors = decodingErrors.str();
+	throw love::Exception("No suitable audio decoders found.\n%s", errors.c_str());
 
 	// Unreachable, but here to prevent (possible) warnings
 	return nullptr;

+ 2 - 9
src/modules/sound/lullaby/Sound.h

@@ -45,21 +45,14 @@ class Sound : public love::sound::Sound
 {
 public:
 
-	/**
-	 * Constructor. Initializes relevant libraries.
-	 **/
 	Sound();
-
-	/**
-	 * Destructor. Deinitializes relevant libraries.
-	 **/
 	virtual ~Sound();
 
 	/// @copydoc love::Module::getName
-	const char *getName() const;
+	const char *getName() const override;
 
 	/// @copydoc love::sound::Sound::newDecoder
-	sound::Decoder *newDecoder(love::filesystem::FileData *file, int bufferSize);
+	sound::Decoder *newDecoder(Stream *stream, int bufferSize) override;
 
 }; // Sound
 

+ 33 - 109
src/modules/sound/lullaby/VorbisDecoder.cpp

@@ -31,132 +31,65 @@ namespace sound
 namespace lullaby
 {
 
-/**
- * CALLBACK FUNCTIONS
- **/
-static int vorbisClose(void *	/* ptr to the data that the vorbis files need*/)
+static int vorbisClose(void *)
 {
 	// Does nothing (handled elsewhere)
 	return 1;
 }
 
-static size_t vorbisRead(void *ptr	/* ptr to the data that the vorbis files need*/,
-				  size_t byteSize	/* how big a byte is*/,
-				  size_t sizeToRead	/* How much we can read*/,
-				  void *datasource	/* this is a pointer to the data we passed into ov_open_callbacks (our SOggFile struct*/)
+static size_t vorbisRead(void *ptr, size_t byteSize, size_t sizeToRead, void *datasource)
 {
-	size_t				spaceToEOF;			// How much more we can read till we hit the EOF marker
-	size_t				actualSizeToRead;	// How much data we are actually going to read from memory
-	SOggFile			 *vorbisData;			// Our vorbis data, for the typecast
-
-	// Get the data in the right format
-	vorbisData = (SOggFile *)datasource;
-
-	// Calculate how much we need to read.  This can be sizeToRead*byteSize or less depending on how near the EOF marker we are
-	spaceToEOF = vorbisData->dataSize - vorbisData->dataRead;
-	if ((sizeToRead*byteSize) < spaceToEOF)
-		actualSizeToRead = (sizeToRead*byteSize);
-	else
-		actualSizeToRead = spaceToEOF;
-
-	// A simple copy of the data from memory to the datastruct that the vorbis libs will use
-	if (actualSizeToRead)
-	{
-		// Copy the data from the start of the file PLUS how much we have already read in
-		memcpy(ptr, (const char *)vorbisData->dataPtr + vorbisData->dataRead, actualSizeToRead);
-		// Increase by how much we have read by
-		vorbisData->dataRead += (actualSizeToRead);
-	}
-
-	// Return how much we read (in the same way fread would)
-	return actualSizeToRead;
+	auto stream = (Stream *) datasource;
+	return stream->read(ptr, byteSize * sizeToRead);
 }
 
-static int vorbisSeek(void *datasource	/* ptr to the data that the vorbis files need*/,
-			   ogg_int64_t offset	/*offset from the point we wish to seek to*/,
-			   int whence			/*where we want to seek to*/)
+static int vorbisSeek(void *datasource, ogg_int64_t offset, int whence)
 {
-	int64    spaceToEOF;   // How much more we can read till we hit the EOF marker
-	int64    actualOffset; // How much we can actually offset it by
-	SOggFile *vorbisData;  // The data we passed in (for the typecast)
-
-	// Get the data in the right format
-	vorbisData = (SOggFile *) datasource;
+	auto stream = (Stream *) datasource;
+	auto origin = Stream::SEEKORIGIN_BEGIN;
 
-	// Goto where we wish to seek to
 	switch (whence)
 	{
-	case SEEK_SET: // Seek to the start of the data file
-		// Make sure we are not going to the end of the file
-		if (vorbisData->dataSize >= offset)
-			actualOffset = offset;
-		else
-			actualOffset = vorbisData->dataSize;
-		// Set where we now are
-		vorbisData->dataRead = (int)actualOffset;
+	case SEEK_SET:
+		origin = Stream::SEEKORIGIN_BEGIN;
 		break;
-	case SEEK_CUR: // Seek from where we are
-		// Make sure we dont go past the end
-		spaceToEOF = vorbisData->dataSize - vorbisData->dataRead;
-		if (offset < spaceToEOF)
-			actualOffset = (offset);
-		else
-			actualOffset = spaceToEOF;
-		// Seek from our currrent location
-		vorbisData->dataRead += actualOffset;
+	case SEEK_CUR:
+		origin = Stream::SEEKORIGIN_CURRENT;
 		break;
-	case SEEK_END: // Seek from the end of the file
-		if (offset < 0)
-			vorbisData->dataRead = vorbisData->dataSize + offset;
-		else
-			vorbisData->dataRead = vorbisData->dataSize;
+	case SEEK_END:
+		origin = Stream::SEEKORIGIN_END;
 		break;
 	default:
 		break;
 	};
 
-	return 0;
+	return stream->seek(offset, origin) ? 0 : -1;
 }
 
-static long vorbisTell(void *datasource	/* ptr to the data that the vorbis files need*/)
+static long vorbisTell(void *datasource)
 {
-	SOggFile *vorbisData;
-	vorbisData = (SOggFile *) datasource;
-	return vorbisData->dataRead;
+	auto stream = (Stream *) datasource;
+	return (long) stream->tell();
 }
 /**
  * END CALLBACK FUNCTIONS
  **/
 
-VorbisDecoder::VorbisDecoder(Data *data, int bufferSize)
-	: Decoder(data, bufferSize)
+VorbisDecoder::VorbisDecoder(Stream *stream, int bufferSize)
+	: Decoder(stream, bufferSize)
 	, duration(-2.0)
 {
-	// Initialize callbacks
-	vorbisCallbacks.close_func = vorbisClose;
-	vorbisCallbacks.seek_func  = vorbisSeek;
-	vorbisCallbacks.read_func  = vorbisRead;
-	vorbisCallbacks.tell_func  = vorbisTell;
-
-	// Check endianness
-#ifdef LOVE_BIG_ENDIAN
-	endian = 1;
-#else
-	endian = 0;
-#endif
-
-	// Initialize OGG file
-	oggFile.dataPtr = (const char *) data->getData();
-	oggFile.dataSize = data->getSize();
-	oggFile.dataRead = 0;
+	ov_callbacks callbacks = {};
+	callbacks.close_func = vorbisClose;
+	callbacks.seek_func  = vorbisSeek;
+	callbacks.read_func  = vorbisRead;
+	callbacks.tell_func  = vorbisTell;
 
 	// Open Vorbis handle
-	if (ov_open_callbacks(&oggFile, &handle, NULL, 0, vorbisCallbacks) < 0)
+	if (ov_open_callbacks(stream, &handle, nullptr, 0, callbacks) < 0)
 		throw love::Exception("Could not read Ogg bitstream");
 
-	// Get info and comments
 	vorbisInfo = ov_info(&handle, -1);
-	vorbisComment = ov_comment(&handle, -1);
 }
 
 VorbisDecoder::~VorbisDecoder()
@@ -164,31 +97,22 @@ VorbisDecoder::~VorbisDecoder()
 	ov_clear(&handle);
 }
 
-bool VorbisDecoder::accepts(const std::string &ext)
-{
-	static const std::string supported[] =
-	{
-		"ogg", "oga", "ogv"
-	};
-
-	for (const auto& s : supported)
-	{
-		if (s.compare(ext) == 0)
-			return true;
-	}
-
-	return false;
-}
-
 love::sound::Decoder *VorbisDecoder::clone()
 {
-	return new VorbisDecoder(data.get(), bufferSize);
+	StrongRef<Stream> s(stream->clone(), Acquire::NORETAIN);
+	return new VorbisDecoder(s, bufferSize);
 }
 
 int VorbisDecoder::decode()
 {
 	int size = 0;
 
+#ifdef LOVE_BIG_ENDIAN
+	int endian = 1;
+#else
+	int endian = 0;
+#endif
+
 	while (size < bufferSize)
 	{
 		long result = ov_read(&handle, (char *) buffer + size, bufferSize - size, endian, (getBitDepth() == 16 ? 2 : 1), 1, 0);

+ 15 - 27
src/modules/sound/lullaby/VorbisDecoder.h

@@ -22,7 +22,7 @@
 #define LOVE_SOUND_LULLABY_VORBIS_DECODER_H
 
 // LOVE
-#include "common/Data.h"
+#include "common/Stream.h"
 #include "common/int.h"
 #include "sound/Decoder.h"
 
@@ -38,41 +38,29 @@ namespace sound
 namespace lullaby
 {
 
-// Struct for handling data
-struct SOggFile
-{
-	const char *dataPtr;	// Pointer to the data in memory
-	int64 dataSize;	// Size of the data
-	int64 dataRead;	// How much we've read so far
-};
-
 class VorbisDecoder : public Decoder
 {
 public:
 
-	VorbisDecoder(Data *data, int bufferSize);
+	VorbisDecoder(Stream *stream, int bufferSize);
 	virtual ~VorbisDecoder();
 
-	static bool accepts(const std::string &ext);
-
-	love::sound::Decoder *clone();
-	int decode();
-	bool seek(double s);
-	bool rewind();
-	bool isSeekable();
-	int getChannelCount() const;
-	int getBitDepth() const;
-	int getSampleRate() const;
-	double getDuration();
+	love::sound::Decoder *clone() override;
+	int decode() override;
+	bool seek(double s) override;
+	bool rewind() override;
+	bool isSeekable() override;
+	int getChannelCount() const override;
+	int getBitDepth() const override;
+	int getSampleRate() const override;
+	double getDuration() override;
 
 private:
-	SOggFile oggFile;				// (see struct)
-	ov_callbacks vorbisCallbacks;	// Callbacks used to read the file from mem
-	OggVorbis_File handle;			// Handle to the file
-	vorbis_info *vorbisInfo;		// Info
-	vorbis_comment *vorbisComment;	// Comments
-	int endian;						// Endianness
+
+	OggVorbis_File handle;
+	vorbis_info *vorbisInfo;
 	double duration;
+
 }; // VorbisDecoder
 
 } // lullaby

+ 15 - 38
src/modules/sound/lullaby/WaveDecoder.cpp

@@ -34,40 +34,32 @@ namespace lullaby
 // Callbacks
 static wuff_sint32 read_callback(void *userdata, wuff_uint8 *buffer, size_t *size)
 {
-	WaveFile *input = (WaveFile *) userdata;
-	size_t bytes_left = input->size - input->offset;
-	size_t target_size = *size < bytes_left ? *size : bytes_left;
-	memcpy(buffer, input->data + input->offset, target_size);
-	input->offset += target_size;
-	*size = target_size;
+	auto stream = (Stream *) userdata;
+	size_t readsize = stream->read(buffer, *size);
+	*size = readsize;
 	return WUFF_SUCCESS;
 }
 
 static wuff_sint32 seek_callback(void *userdata, wuff_uint64 offset)
 {
-	WaveFile *input = (WaveFile *)userdata;
-	input->offset = (size_t) (offset < input->size ? offset : input->size);
+	auto stream = (Stream *) userdata;
+	stream->seek(offset, Stream::SEEKORIGIN_BEGIN);
 	return WUFF_SUCCESS;
 }
 
 static wuff_sint32 tell_callback(void *userdata, wuff_uint64 *offset)
 {
-	WaveFile *input = (WaveFile *)userdata;
-	*offset = input->offset;
+	auto stream = (Stream *) userdata;
+	*offset = stream->tell();
 	return WUFF_SUCCESS;
 }
 
-wuff_callback WaveDecoderCallbacks = {read_callback, seek_callback, tell_callback};
+static wuff_callback WaveDecoderCallbacks = {read_callback, seek_callback, tell_callback};
 
-
-WaveDecoder::WaveDecoder(Data *data, int bufferSize)
-	: Decoder(data, bufferSize)
+WaveDecoder::WaveDecoder(Stream *stream, int bufferSize)
+	: Decoder(stream, bufferSize)
 {
-	dataFile.data = (char *) data->getData();
-	dataFile.size = data->getSize();
-	dataFile.offset = 0;
-
-	int wuff_status = wuff_open(&handle, &WaveDecoderCallbacks, &dataFile);
+	int wuff_status = wuff_open(&handle, &WaveDecoderCallbacks, stream);
 	if (wuff_status < 0)
 		throw love::Exception("Could not open WAVE");
 
@@ -78,13 +70,13 @@ WaveDecoder::WaveDecoder(Data *data, int bufferSize)
 			throw love::Exception("Could not retrieve WAVE stream info");
 
 		if (info.channels > 2)
-			throw love::Exception("Multichannel audio not supported");
+			throw love::Exception("WAVE Multichannel audio not supported");
 
 		if (info.format != WUFF_FORMAT_PCM_U8 && info.format != WUFF_FORMAT_PCM_S16)
 		{
 			wuff_status = wuff_format(handle, WUFF_FORMAT_PCM_S16);
 			if (wuff_status < 0)
-				throw love::Exception("Could not set output format");
+				throw love::Exception("Could not set WAVE output format");
 		}
 	}
 	catch (love::Exception &)
@@ -99,25 +91,10 @@ WaveDecoder::~WaveDecoder()
 	wuff_close(handle);
 }
 
-bool WaveDecoder::accepts(const std::string &ext)
-{
-	static const std::string supported[] =
-	{
-		"wav"
-	};
-
-	for (const auto& s : supported)
-	{
-		if (s.compare(ext) == 0)
-			return true;
-	}
-
-	return false;
-}
-
 love::sound::Decoder *WaveDecoder::clone()
 {
-	return new WaveDecoder(data.get(), bufferSize);
+	StrongRef<Stream> s(stream->clone(), Acquire::NORETAIN);
+	return new WaveDecoder(s, bufferSize);
 }
 
 int WaveDecoder::decode()

+ 11 - 22
src/modules/sound/lullaby/WaveDecoder.h

@@ -22,7 +22,7 @@
 #define LOVE_SOUND_LULLABY_WAVE_DECODER_H
 
 // LOVE
-#include "common/Data.h"
+#include "common/Stream.h"
 #include "sound/Decoder.h"
 
 #include "libraries/Wuff/wuff.h"
@@ -34,36 +34,25 @@ namespace sound
 namespace lullaby
 {
 
-// Struct for handling data
-struct WaveFile
-{
-	char *data;
-	size_t size;
-	size_t offset;
-};
-
 class WaveDecoder : public Decoder
 {
 public:
 
-	WaveDecoder(Data *data, int bufferSize);
+	WaveDecoder(Stream *stream, int bufferSize);
 	virtual ~WaveDecoder();
 
-	static bool accepts(const std::string &ext);
-
-	love::sound::Decoder *clone();
-	int decode();
-	bool seek(double s);
-	bool rewind();
-	bool isSeekable();
-	int getChannelCount() const;
-	int getBitDepth() const;
-	int getSampleRate() const;
-	double getDuration();
+	love::sound::Decoder *clone() override;
+	int decode() override;
+	bool seek(double s) override;
+	bool rewind() override;
+	bool isSeekable() override;
+	int getChannelCount() const override;
+	int getBitDepth() const override;
+	int getSampleRate() const override;
+	double getDuration() override;
 
 private:
 
-	WaveFile dataFile;
 	wuff_handle *handle;
 	wuff_info info;
 

+ 40 - 7
src/modules/sound/wrap_Sound.cpp

@@ -21,6 +21,7 @@
 #include "wrap_Sound.h"
 
 #include "filesystem/wrap_Filesystem.h"
+#include "data/DataStream.h"
 
 // Implementations.
 #include "lullaby/Sound.h"
@@ -34,18 +35,50 @@ namespace sound
 
 int w_newDecoder(lua_State *L)
 {
-	love::filesystem::FileData *data = love::filesystem::luax_getfiledata(L, 1);
-	int bufferSize = (int) luaL_optinteger(L, 2, Decoder::DEFAULT_BUFFER_SIZE);
+	int bufferSize = (int)luaL_optinteger(L, 2, Decoder::DEFAULT_BUFFER_SIZE);
+	love::Stream *stream = nullptr;
+
+	if (love::filesystem::luax_cangetfile(L, 1))
+	{
+		Decoder::StreamSource source = Decoder::STREAM_FILE;
+
+		const char* sourcestr = lua_isnoneornil(L, 3) ? nullptr : luaL_checkstring(L, 3);
+		if (sourcestr != nullptr && !Decoder::getConstant(sourcestr, source))
+			return luax_enumerror(L, "stream type", Decoder::getConstants(source), sourcestr);
+
+		if (source == Decoder::STREAM_FILE)
+		{
+			auto file = love::filesystem::luax_getfile(L, 1);
+			luax_catchexcept(L, [&]() { file->open(love::filesystem::File::MODE_READ); });
+			stream = file;
+		}
+		else
+		{
+			luax_catchexcept(L, [&]()
+			{
+				StrongRef<love::filesystem::FileData> data(love::filesystem::luax_getfiledata(L, 1), Acquire::NORETAIN);
+				stream = new data::DataStream(data);
+			});
+		}
+
+	}
+	else if (luax_istype(L, 1, Data::type))
+	{
+		Data *data = luax_checktype<Data>(L, 1);
+		luax_catchexcept(L, [&]() { stream = new data::DataStream(data); });
+	}
+	else
+	{
+		stream = luax_checktype<Stream>(L, 1);
+		stream->retain();
+	}	
 
 	Decoder *t = nullptr;
 	luax_catchexcept(L,
-		[&]() { t = instance()->newDecoder(data, bufferSize); },
-		[&](bool) { data->release(); }
+		[&]() { t = instance()->newDecoder(stream, bufferSize); },
+		[&](bool) { stream->release(); }
 	);
 
-	if (t == nullptr)
-		return luaL_error(L, "Extension \"%s\" not supported.", data->getExtension().c_str());
-
 	luax_pushtype(L, t);
 	t->release();
 	return 1;