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.
This commit is contained in:
Alex Szpakowski
2022-04-21 19:57:32 -03:00
parent d5865e160c
commit c09fef8e79
20 changed files with 318 additions and 468 deletions
+27 -14
View File
@@ -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<Source*> readSourceList(lua_State *L, int n)
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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<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
View File
@@ -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
View File
@@ -22,6 +22,7 @@
#include "FLACDecoder.h"
#include <set>
#include <algorithm>
#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<int64>(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<Stream> s(stream->clone(), Acquire::NORETAIN);
return new FLACDecoder(s, bufferSize);
}
int FLACDecoder::decode()
+12 -13
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+28 -27
View File
@@ -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<Stream> s(stream->clone(), Acquire::NORETAIN);
return new ModPlugDecoder(s, bufferSize);
}
int ModPlugDecoder::decode()
+13 -13
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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<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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;