diff --git a/src/modules/audio/wrap_Audio.cpp b/src/modules/audio/wrap_Audio.cpp index 6cfa2fac2..21c8429e3 100644 --- a/src/modules/audio/wrap_Audio.cpp +++ b/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) { + 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); + Source *t = nullptr; + luax_catchexcept(L, [&]() { t = instance()->newSource(samplerate, bitdepth, channels, buffers); }); - 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)); - }); - - 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 readSourceList(lua_State *L, int n) diff --git a/src/modules/graphics/wrap_Graphics.lua b/src/modules/graphics/wrap_Graphics.lua index ea2b52579..8bb8a45f8 100644 --- a/src/modules/graphics/wrap_Graphics.lua +++ b/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) diff --git a/src/modules/sound/Decoder.cpp b/src/modules/sound/Decoder.cpp index 1182cb68a..355b783c9 100644 --- a/src/modules/sound/Decoder.cpp +++ b/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 diff --git a/src/modules/sound/Decoder.h b/src/modules/sound/Decoder.h index f3452567f..1c91be281 100644 --- a/src/modules/sound/Decoder.h +++ b/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 @@ -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; + // A readable stream containing the encoded data. + StrongRef stream; // When the decoder decodes data incrementally, it writes // this many bytes at a time (at most). diff --git a/src/modules/sound/Sound.h b/src/modules/sound/Sound.h index 30ec0cb9d..8cfe378a6 100644 --- a/src/modules/sound/Sound.h +++ b/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 diff --git a/src/modules/sound/lullaby/CoreAudioDecoder.cpp b/src/modules/sound/lullaby/CoreAudioDecoder.cpp index d1b117dd0..2365a2e91 100644 --- a/src/modules/sound/lullaby/CoreAudioDecoder.cpp +++ b/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; + auto stream = (Stream *) inClientData; + int64 readbytes = stream->read(buffer, requestCount); - if (bytesLeft > 0) - { - UInt32 actualSize = bytesLeft >= requestCount ? requestCount : (UInt32) bytesLeft; - memcpy(buffer, (char *) data->getData() + inPosition, actualSize); - *actualCount = actualSize; - } - else - { - *actualCount = 0; - return kAudioFilePositionError; - } - - 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 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 s(stream->clone(), Acquire::NORETAIN); + return new CoreAudioDecoder(s, bufferSize); } int CoreAudioDecoder::decode() diff --git a/src/modules/sound/lullaby/CoreAudioDecoder.h b/src/modules/sound/lullaby/CoreAudioDecoder.h index d0380f136..9153dd2c4 100644 --- a/src/modules/sound/lullaby/CoreAudioDecoder.h +++ b/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: diff --git a/src/modules/sound/lullaby/FLACDecoder.cpp b/src/modules/sound/lullaby/FLACDecoder.cpp index 71f4cf315..d45386490 100644 --- a/src/modules/sound/lullaby/FLACDecoder.cpp +++ b/src/modules/sound/lullaby/FLACDecoder.cpp @@ -22,6 +22,7 @@ #include "FLACDecoder.h" #include +#include #include "common/Exception.h" namespace love @@ -31,10 +32,24 @@ 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); + auto stream = (Stream *) pUserData; + int64 read = stream->read(pBufferOut, bytesToRead); + return std::max(0, read); +} + +static drflac_bool32 onSeek(void* pUserData, int offset, drflac_seek_origin origin) +{ + 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; +} + +FLACDecoder::FLACDecoder(Stream *stream, int nbufferSize) + : Decoder(stream, nbufferSize) +{ + flac = drflac_open(onRead, onSeek, stream, nullptr); if (flac == nullptr) throw love::Exception("Could not load FLAC file"); } @@ -44,29 +59,10 @@ FLACDecoder::~FLACDecoder() drflac_close(flac); } -bool FLACDecoder::accepts(const std::string &ext) -{ - // 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; - } - - return false; -} - love::sound::Decoder *FLACDecoder::clone() { - return new FLACDecoder(data.get(), bufferSize); + StrongRef s(stream->clone(), Acquire::NORETAIN); + return new FLACDecoder(s, bufferSize); } int FLACDecoder::decode() diff --git a/src/modules/sound/lullaby/FLACDecoder.h b/src/modules/sound/lullaby/FLACDecoder.h index 4ddae3191..7e5fcdc30 100644 --- a/src/modules/sound/lullaby/FLACDecoder.h +++ b/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 diff --git a/src/modules/sound/lullaby/MP3Decoder.cpp b/src/modules/sound/lullaby/MP3Decoder.cpp index 00c4740e1..2a1215639 100644 --- a/src/modules/sound/lullaby/MP3Decoder.cpp +++ b/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(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 s(stream->clone(), Acquire::NORETAIN); + return new MP3Decoder(s, bufferSize); } int MP3Decoder::decode() diff --git a/src/modules/sound/lullaby/MP3Decoder.h b/src/modules/sound/lullaby/MP3Decoder.h index 7a95564df..d8589210e 100644 --- a/src/modules/sound/lullaby/MP3Decoder.h +++ b/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: diff --git a/src/modules/sound/lullaby/ModPlugDecoder.cpp b/src/modules/sound/lullaby/ModPlugDecoder.cpp index f38df31b0..440aa44d8 100644 --- a/src/modules/sound/lullaby/ModPlugDecoder.cpp +++ b/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); - // Load the module. - plug = ModPlug_Load(data->getData(), (int) data->getSize()); + // 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); - if (plug == 0) + 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()); + + 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 s(stream->clone(), Acquire::NORETAIN); + return new ModPlugDecoder(s, bufferSize); } int ModPlugDecoder::decode() diff --git a/src/modules/sound/lullaby/ModPlugDecoder.h b/src/modules/sound/lullaby/ModPlugDecoder.h index ec45d47b3..b99a2d0e6 100644 --- a/src/modules/sound/lullaby/ModPlugDecoder.h +++ b/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; + ModPlugFile *plug; ModPlug_Settings settings; double duration; -}; // Decoder +}; // ModPlugDecoder } // lullaby } // sound diff --git a/src/modules/sound/lullaby/Sound.cpp b/src/modules/sound/lullaby/Sound.cpp index c20c90f99..63fe642d6 100644 --- a/src/modules/sound/lullaby/Sound.cpp +++ b/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 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 possibleDecoders = { -#ifndef LOVE_NO_MODPLUG - DecoderImplFor(), -#endif // LOVE_NO_MODPLUG DecoderImplFor(), DecoderImplFor(), #ifdef LOVE_SUPPORT_COREAUDIO @@ -92,24 +81,19 @@ sound::Decoder *Sound::newDecoder(love::filesystem::FileData *data, int bufferSi #endif DecoderImplFor(), DecoderImplFor(), - // DecoderImplFor(), +#ifndef LOVE_NO_MODPLUG + DecoderImplFor(), // 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; diff --git a/src/modules/sound/lullaby/Sound.h b/src/modules/sound/lullaby/Sound.h index a79392b84..028740020 100644 --- a/src/modules/sound/lullaby/Sound.h +++ b/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 diff --git a/src/modules/sound/lullaby/VorbisDecoder.cpp b/src/modules/sound/lullaby/VorbisDecoder.cpp index b9de52347..6aeac42b9 100644 --- a/src/modules/sound/lullaby/VorbisDecoder.cpp +++ b/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) + auto stream = (Stream *) datasource; + auto origin = Stream::SEEKORIGIN_BEGIN; - // Get the data in the right format - vorbisData = (SOggFile *) datasource; - - // 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 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); diff --git a/src/modules/sound/lullaby/VorbisDecoder.h b/src/modules/sound/lullaby/VorbisDecoder.h index dd58097ce..88f21e489 100644 --- a/src/modules/sound/lullaby/VorbisDecoder.h +++ b/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 diff --git a/src/modules/sound/lullaby/WaveDecoder.cpp b/src/modules/sound/lullaby/WaveDecoder.cpp index 95dbfa2b4..012e4d42d 100644 --- a/src/modules/sound/lullaby/WaveDecoder.cpp +++ b/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 s(stream->clone(), Acquire::NORETAIN); + return new WaveDecoder(s, bufferSize); } int WaveDecoder::decode() diff --git a/src/modules/sound/lullaby/WaveDecoder.h b/src/modules/sound/lullaby/WaveDecoder.h index 90832214a..9f7ddbeb4 100644 --- a/src/modules/sound/lullaby/WaveDecoder.h +++ b/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; diff --git a/src/modules/sound/wrap_Sound.cpp b/src/modules/sound/wrap_Sound.cpp index 8c229c9a3..364374880 100644 --- a/src/modules/sound/wrap_Sound.cpp +++ b/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 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(L, 1); + luax_catchexcept(L, [&]() { stream = new data::DataStream(data); }); + } + else + { + stream = luax_checktype(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;