Added Source:getDuration (thanks Boolsheet!)

For streaming sources, the duration may not always be sample-accurate and may return -1 if it cannot be determined at all.
This commit is contained in:
Alex Szpakowski
2015-11-07 01:49:37 -04:00
parent 1b5a0f49fa
commit 0558fcdbd4
28 changed files with 321 additions and 90 deletions
+2 -1
View File
@@ -39,7 +39,7 @@ public:
TYPE_STATIC,
TYPE_STREAM,
TYPE_MAX_ENUM
}; // Type
};
enum Unit
{
@@ -71,6 +71,7 @@ public:
virtual void seek(float offset, Unit unit) = 0;
virtual float tell(Unit unit) = 0;
virtual double getDuration(Unit unit) = 0;
// all float * v must be of size 3
virtual void setPosition(float *v) = 0;
+5
View File
@@ -112,6 +112,11 @@ float Source::tell(Source::Unit)
return 0.0f;
}
double Source::getDuration(Unit)
{
return -1.0f;
}
void Source::setPosition(float *)
{
}
+1
View File
@@ -54,6 +54,7 @@ public:
virtual float getVolume() const;
virtual void seek(float offset, Unit unit);
virtual float tell(Unit unit);
virtual double getDuration(Unit unit);
virtual void setPosition(float *v);
virtual void getPosition(float *v) const;
virtual void setVelocity(float *v);
+6
View File
@@ -263,6 +263,12 @@ float Pool::tell(Source *source, void *unit)
return source->tellAtomic(unit);
}
double Pool::getDuration(Source *source, void *unit)
{
thread::Lock lock(mutex);
return source->getDurationAtomic(unit);
}
ALuint Pool::findi(const Source *source) const
{
std::map<Source *, ALuint>::const_iterator i = playing.find((Source *)source);
+1
View File
@@ -93,6 +93,7 @@ public:
void softRewind(Source *source);
void seek(Source *source, float offset, void *unit);
float tell(Source *source, void *unit);
double getDuration(Source *source, void *unit);
private:
+34
View File
@@ -65,6 +65,7 @@ Ensure the Source is not multi-channel before calling this function.")
};
StaticDataBuffer::StaticDataBuffer(ALenum format, const ALvoid *data, ALsizei size, ALsizei freq)
: size(size)
{
alGenBuffers(1, &buffer);
alBufferData(buffer, format, data, size, freq);
@@ -95,6 +96,7 @@ Source::Source(Pool *pool, love::sound::SoundData *soundData)
, offsetSeconds(0)
, sampleRate(soundData->getSampleRate())
, channels(soundData->getChannels())
, bitDepth(soundData->getBitDepth())
, decoder(nullptr)
, toLoop(0)
{
@@ -134,6 +136,7 @@ Source::Source(Pool *pool, love::sound::Decoder *decoder)
, offsetSeconds(0)
, sampleRate(decoder->getSampleRate())
, channels(decoder->getChannels())
, bitDepth(decoder->getBitDepth())
, decoder(decoder)
, toLoop(0)
{
@@ -169,6 +172,7 @@ Source::Source(const Source &s)
, offsetSeconds(0)
, sampleRate(s.sampleRate)
, channels(s.channels)
, bitDepth(s.bitDepth)
, decoder(nullptr)
, toLoop(0)
{
@@ -441,6 +445,36 @@ float Source::tell(Source::Unit unit)
return pool->tell(this, &unit);
}
double Source::getDurationAtomic(void *vunit)
{
Unit unit = *(Unit *) vunit;
if (type == TYPE_STREAM)
{
double seconds = decoder->getDuration();
if (unit == UNIT_SECONDS)
return seconds;
else
return seconds * decoder->getSampleRate();
}
else
{
ALsizei size = staticBuffer->getSize();
ALsizei samples = (size / channels) / (bitDepth / 8);
if (unit == UNIT_SAMPLES)
return (double) samples;
else
return (double) samples / (double) sampleRate;
}
}
double Source::getDuration(Unit unit)
{
return pool->getDuration(this, &unit);
}
void Source::setPosition(float *v)
{
if (channels > 1)
+9
View File
@@ -65,9 +65,15 @@ public:
return buffer;
}
inline ALsizei getSize() const
{
return size;
}
private:
ALuint buffer;
ALsizei size;
}; // StaticDataBuffer
@@ -98,6 +104,8 @@ public:
virtual void seek(float offset, Unit unit);
virtual float tellAtomic(void *unit) const;
virtual float tell(Unit unit);
virtual double getDurationAtomic(void *unit);
virtual double getDuration(Unit unit);
virtual void setPosition(float *v);
virtual void getPosition(float *v) const;
virtual void setVelocity(float *v);
@@ -180,6 +188,7 @@ private:
int sampleRate;
int channels;
int bitDepth;
StrongRef<love::sound::Decoder> decoder;
+14
View File
@@ -140,6 +140,19 @@ int w_Source_tell(lua_State *L)
return 1;
}
int w_Source_getDuration(lua_State *L)
{
Source *t = luax_checksource(L, 1);
Source::Unit u = Source::UNIT_SECONDS;
const char *unit = lua_isnoneornil(L, 2) ? 0 : lua_tostring(L, 2);
if (unit && !t->getConstant(unit, u))
return luaL_error(L, "Invalid Source time unit: %s", unit);
lua_pushnumber(L, t->getDuration(u));
return 1;
}
int w_Source_setPosition(lua_State *L)
{
Source *t = luax_checksource(L, 1);
@@ -373,6 +386,7 @@ static const luaL_Reg functions[] =
{ "getVolume", w_Source_getVolume },
{ "seek", w_Source_seek },
{ "tell", w_Source_tell },
{ "getDuration", w_Source_getDuration },
{ "setPosition", w_Source_setPosition },
{ "getPosition", w_Source_getPosition },
{ "setVelocity", w_Source_setVelocity },
+1
View File
@@ -42,6 +42,7 @@ int w_Source_setVolume(lua_State *L);
int w_Source_getVolume(lua_State *L);
int w_Source_seek(lua_State *L);
int w_Source_tell(lua_State *L);
int w_Source_getDuration(lua_State *L);
int w_Source_setPosition(lua_State *L);
int w_Source_getPosition(lua_State *L);
int w_Source_setVelocity(lua_State *L);
+1 -3
View File
@@ -581,11 +581,9 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
enabledattribs |= 1 << uint32(attriblocation);
}
// Not supported on all platforms or GL versions, I believe.
if (!(enabledattribs & ATTRIBFLAG_POS))
{
// Not supported on all platforms or GL versions at least, I believe.
throw love::Exception("Mesh must have an enabled VertexPosition attribute to be drawn.");
}
gl.useVertexAttribArrays(enabledattribs);
+6
View File
@@ -134,6 +134,12 @@ public:
**/
virtual int getSampleRate() const = 0;
/**
* Gets the estimated total duration of the stream. in seconds. May return
* -1 if the duration cannot be determined.
**/
virtual double getDuration() = 0;
}; // Decoder
} // sound
@@ -69,6 +69,7 @@ CoreAudioDecoder::CoreAudioDecoder(Data *data, const std::string &ext, int buffe
, extAudioFile(nullptr)
, inputInfo()
, outputInfo()
, duration(-2.0)
{
try
{
@@ -267,6 +268,25 @@ int CoreAudioDecoder::getBitDepth() const
return outputInfo.mBitsPerChannel;
}
double CoreAudioDecoder::getDuration()
{
// Only calculate the duration if we haven't done so already.
if (duration == -2.0)
{
SInt64 samples = 0;
UInt32 psize = (UInt32) sizeof(samples);
OSStatus err = ExtAudioFileGetProperty(extAudioFile, kExtAudioFileProperty_FileLengthFrames, &psize, &samples);
if (err == noErr)
duration = (double) samples / (double) sampleRate;
else
duration = -1.0;
}
return duration;
}
} // lullaby
} // sound
} // love
@@ -59,6 +59,7 @@ public:
bool isSeekable();
int getChannels() const;
int getBitDepth() const;
double getDuration();
private:
@@ -70,6 +71,8 @@ private:
AudioStreamBasicDescription inputInfo;
AudioStreamBasicDescription outputInfo;
double duration;
}; // CoreAudioDecoder
} // lullaby
@@ -108,6 +108,11 @@ int FLACDecoder::getSampleRate() const
return get_sample_rate();
}
double FLACDecoder::getDuration()
{
return -1;
}
FLAC__StreamDecoderReadStatus FLACDecoder::read_callback(FLAC__byte buffer[], size_t *bytes)
{
int size = data->getSize();
+1
View File
@@ -52,6 +52,7 @@ public:
int getChannels() const;
int getBitDepth() const;
int getSampleRate() const;
double getDuration();
//needed for FLAC
FLAC__StreamDecoderReadStatus read_callback(FLAC__byte buffer[], size_t *bytes);
+5
View File
@@ -142,6 +142,11 @@ int GmeDecoder::getBitDepth() const
return 16;
}
double GmeDecoder::getDuration()
{
return -1;
}
} // lullaby
} // sound
} // love
+1
View File
@@ -56,6 +56,7 @@ public:
bool isSeekable();
int getChannels() const;
int getBitDepth() const;
double getDuration();
private:
Music_Emu *emu;
@@ -34,6 +34,7 @@ namespace lullaby
ModPlugDecoder::ModPlugDecoder(Data *data, const std::string &ext, int bufferSize)
: Decoder(data, ext, bufferSize)
, plug(0)
, duration(-2.0)
{
// Set some ModPlug settings.
@@ -141,6 +142,22 @@ int ModPlugDecoder::getBitDepth() const
return 16;
}
double ModPlugDecoder::getDuration()
{
// Only calculate the duration if we haven't done so already.
if (duration == -2.0)
{
int lengthms = ModPlug_GetLength(plug);
if (lengthms < 0)
duration = -1.0;
else
duration = (double) lengthms / 1000.0;
}
return duration;
}
} // lullaby
} // sound
} // love
@@ -59,11 +59,15 @@ public:
bool isSeekable();
int getChannels() const;
int getBitDepth() const;
double getDuration();
private:
ModPlugFile *plug;
ModPlug_Settings settings;
double duration;
}; // Decoder
} // lullaby
+134 -80
View File
@@ -31,18 +31,88 @@ namespace sound
namespace lullaby
{
/**
* mpg123 callbacks for seekable streams.
**/
static ssize_t read_callback(void *udata, void *buffer, size_t count)
{
DecoderFile *file = (DecoderFile *) udata;
// Calculates how much data is still left and takes that value or
// the buffer size, whichever is lower, as the number of bytes to write.
size_t countLeft = file->size - file->offset;
size_t countWrite = countLeft < count ? countLeft : count;
if (countWrite > 0)
{
memcpy(buffer, file->data + file->offset, countWrite);
file->offset += countWrite;
}
// Returns the number of written bytes. 0 means EOF.
return countWrite;
}
static off_t seek_callback(void *udata, off_t offset, int whence)
{
DecoderFile *file = (DecoderFile *) udata;
switch (whence)
{
case SEEK_SET:
// Negative values are invalid at this point.
if (offset < 0)
return -1;
// Prevents the offset from going over EOF.
if (file->size > (size_t) offset)
file->offset = offset;
else
file->offset = file->size;
break;
case SEEK_END:
// Offset is set to EOF. Offset calculation is just like SEEK_CUR.
file->offset = file->size;
case SEEK_CUR:
// Prevents the offset from going over EOF or below 0.
if (offset > 0)
{
if (file->size > file->offset + (size_t) offset)
file->offset = file->offset + offset;
else
file->offset = file->size;
}
else if (offset < 0)
{
if (file->offset >= (size_t) (-offset))
file->offset = file->offset - (size_t) (-offset);
else
file->offset = 0;
}
break;
default:
return -1;
};
return file->offset;
}
static void cleanup_callback(void *)
{
// Cleanup is done by the Decoder class.
}
bool Mpg123Decoder::inited = false;
Mpg123Decoder::Mpg123Decoder(Data *data, const std::string &ext, int bufferSize)
: Decoder(data, ext, bufferSize)
, decoder_file(data)
, handle(0)
, channels(MPG123_STEREO)
, duration(-2.0)
{
data_size = data->getSize();
data_offset = 0;
int ret;
int ret = 0;
if (!inited)
{
@@ -52,18 +122,46 @@ Mpg123Decoder::Mpg123Decoder(Data *data, const std::string &ext, int bufferSize)
inited = (ret == MPG123_OK);
}
//Intialize the handle
handle = mpg123_new(NULL, &ret);
if (handle == NULL)
throw love::Exception("Could not create handle.");
ret = mpg123_open_feed(handle);
if (ret != MPG123_OK)
throw love::Exception("Could not open feed.");
// Intialize the handle.
handle = mpg123_new(nullptr, nullptr);
if (handle == nullptr)
throw love::Exception("Could not create decoder.");
ret = feed(16384);
// Suppressing all mpg123 messages.
mpg123_param(handle, MPG123_ADD_FLAGS, MPG123_QUIET, 0);
if (ret != MPG123_OK && ret != MPG123_DONE)
throw love::Exception("Could not feed!");
try
{
ret = mpg123_replace_reader_handle(handle, &read_callback, &seek_callback, &cleanup_callback);
if (ret != MPG123_OK)
throw love::Exception("Could not set decoder callbacks.");
ret = mpg123_open_handle(handle, &decoder_file);
if (ret != MPG123_OK)
throw love::Exception("Could not open decoder.");
// mpg123_getformat should be able to tell us the properties of the stream's first frame.
long rate = 0;
ret = mpg123_getformat(handle, &rate, &channels, nullptr);
if (ret == MPG123_ERR)
throw love::Exception("Could not get stream information.");
// I forgot what this was about.
if (channels == 0)
channels = 2;
// Force signed 16-bit output.
mpg123_param(handle, MPG123_FLAGS, (channels == 2 ? MPG123_FORCE_STEREO : MPG123_MONO_MIX), 0);
mpg123_format_none(handle);
mpg123_format(handle, rate, channels, MPG123_ENC_SIGNED_16);
sampleRate = rate;
}
catch (love::Exception &)
{
mpg123_delete(handle);
throw;
}
}
Mpg123Decoder::~Mpg123Decoder()
@@ -102,64 +200,24 @@ love::sound::Decoder *Mpg123Decoder::clone()
int Mpg123Decoder::decode()
{
int size = 0;
bool done = false;
while (size < bufferSize && !done && !eof)
while (size < bufferSize && !eof)
{
size_t numbytes = 0;
int res = mpg123_read(handle, (unsigned char *) buffer + size, bufferSize - size, &numbytes);
int r = mpg123_read(handle, (unsigned char *) buffer + size, bufferSize - size, &numbytes);
switch (r)
switch (res)
{
case MPG123_NEW_FORMAT:
{
size += numbytes;
long rate = 0;
int encoding = 0;
mpg123_getformat(handle, &rate, &channels, &encoding);
if (rate == 0)
rate = sampleRate;
else
sampleRate = rate;
if (channels == 0)
channels = MPG123_STEREO;
if (encoding == 0)
encoding = MPG123_ENC_SIGNED_16;
int ret = mpg123_format(handle, rate, channels, encoding);
if (ret != MPG123_OK)
throw love::Exception("Could not set output format.");
}
continue;
case MPG123_NEED_MORE:
{
size += numbytes;
int v = feed(8192);
switch (v)
{
case MPG123_OK:
continue;
case MPG123_DONE:
if (numbytes == 0)
eof = true;
break;
default:
done = true;
}
continue;
}
case MPG123_NEW_FORMAT:
case MPG123_OK:
size += numbytes;
continue;
case MPG123_DONE:
// Apparently, mpg123_read does not return MPG123_DONE, but
// let's keep it here anyway.
size += numbytes;
eof = true;
default:
done = true;
break;
return size;
}
}
@@ -168,14 +226,13 @@ int Mpg123Decoder::decode()
bool Mpg123Decoder::seek(float s)
{
off_t offset = static_cast<off_t>(s * static_cast<float>(sampleRate));
off_t offset = (off_t) (s * (double) sampleRate);
if (offset < 0)
return false;
if (mpg123_feedseek(handle, offset, SEEK_SET, &offset) >= 0)
if (mpg123_seek(handle, offset, SEEK_SET) >= 0)
{
data_offset = offset;
eof = false;
return true;
}
@@ -186,13 +243,9 @@ bool Mpg123Decoder::seek(float s)
bool Mpg123Decoder::rewind()
{
eof = false;
off_t offset;
if (mpg123_feedseek(handle, 0, SEEK_SET, &offset) >= 0)
{
data_offset = offset;
if (mpg123_seek(handle, 0, SEEK_SET) >= 0)
return true;
}
else
return false;
}
@@ -212,21 +265,22 @@ int Mpg123Decoder::getBitDepth() const
return 16;
}
int Mpg123Decoder::feed(int bytes)
double Mpg123Decoder::getDuration()
{
int remaining = data_size - data_offset;
// Only calculate the duration if we haven't done so already.
if (duration == -2.0)
{
mpg123_scan(handle);
if (remaining <= 0)
return MPG123_DONE;
off_t length = mpg123_length(handle);
int feed_bytes = remaining < bytes ? remaining : bytes;
if (length == MPG123_ERR || length < 0)
duration = -1.0;
else
duration = (double) length / (double) sampleRate;
}
int r = mpg123_feed(handle, (unsigned char *)data->getData() + data_offset, feed_bytes);
if (r == MPG123_OK || r == MPG123_DONE)
data_offset += feed_bytes;
return r;
return duration;
}
} // lullaby
+17 -4
View File
@@ -39,6 +39,19 @@ namespace sound
namespace lullaby
{
struct DecoderFile
{
unsigned char *data;
size_t size;
size_t offset;
DecoderFile(Data *d)
: data((unsigned char *) d->getData())
, size(d->getSize())
, offset(0)
{}
};
class Mpg123Decoder : public Decoder
{
public:
@@ -56,19 +69,19 @@ public:
bool isSeekable();
int getChannels() const;
int getBitDepth() const;
double getDuration();
private:
int feed(int bytes);
DecoderFile decoder_file;
mpg123_handle *handle;
static bool inited;
int data_offset;
int data_size;
int channels;
double duration;
}; // Decoder
} // lullaby
+1 -1
View File
@@ -66,7 +66,7 @@ sound::Decoder *Sound::newDecoder(love::filesystem::FileData *data, int bufferSi
std::string ext = data->getExtension();
std::transform(ext.begin(), ext.end(), ext.begin(), tolower);
sound::Decoder *decoder = 0;
sound::Decoder *decoder = nullptr;
// Find a suitable decoder here, and return it.
if (false)
@@ -130,6 +130,7 @@ static long vorbisTell(void *datasource /* ptr to the data that the vorbis files
VorbisDecoder::VorbisDecoder(Data *data, const std::string &ext, int bufferSize)
: Decoder(data, ext, bufferSize)
, duration(-2.0)
{
// Initialize callbacks
vorbisCallbacks.close_func = vorbisClose;
@@ -264,6 +265,20 @@ int VorbisDecoder::getSampleRate() const
return (int) vorbisInfo->rate;
}
double VorbisDecoder::getDuration()
{
// Only calculate the duration if we haven't done so already.
if (duration == -2.0)
{
duration = ov_time_total(&handle, -1);
if (duration == OV_EINVAL || duration < 0.0)
duration = -1.0;
}
return duration;
}
} // lullaby
} // sound
} // love
@@ -62,6 +62,7 @@ public:
int getChannels() const;
int getBitDepth() const;
int getSampleRate() const;
double getDuration();
private:
SOggFile oggFile; // (see struct)
@@ -70,6 +71,7 @@ private:
vorbis_info *vorbisInfo; // Info
vorbis_comment *vorbisComment; // Comments
int endian; // Endianness
double duration;
}; // VorbisDecoder
} // lullaby
@@ -189,6 +189,11 @@ int WaveDecoder::getSampleRate() const
return info.sample_rate;
}
double WaveDecoder::getDuration()
{
return (double) info.length / (double) info.sample_rate;
}
} // lullaby
} // sound
} // love
+2 -1
View File
@@ -59,11 +59,12 @@ public:
int getChannels() const;
int getBitDepth() const;
int getSampleRate() const;
double getDuration();
private:
WaveFile dataFile;
wuff_handle * handle;
wuff_handle *handle;
wuff_info info;
}; // WaveDecoder
+8
View File
@@ -51,11 +51,19 @@ int w_Decoder_getSampleRate(lua_State *L)
return 1;
}
int w_Decoder_getDuration(lua_State *L)
{
Decoder *t = luax_checkdecoder(L, 1);
lua_pushnumber(L, t->getDuration());
return 1;
}
static const luaL_Reg functions[] =
{
{ "getChannels", w_Decoder_getChannels },
{ "getBitDepth", w_Decoder_getBitDepth },
{ "getSampleRate", w_Decoder_getSampleRate },
{ "getDuration", w_Decoder_getDuration },
{ 0, 0 }
};
+1
View File
@@ -34,6 +34,7 @@ Decoder *luax_checkdecoder(lua_State *L, int idx);
int w_Decoder_getChannels(lua_State *L);
int w_Decoder_getBitDepth(lua_State *L);
int w_Decoder_getSampleRate(lua_State *L);
int w_Decoder_getDuration(lua_State *L);
extern "C" int luaopen_decoder(lua_State *L);
} // sound