Audio: Implement RGSS2 ogg looping
With this we now link to libvorbis/ogg directly. When this is enabled, one can theoretically also build SDL_sound without ogg support, although I doubt it makes much of a difference. Adittionally, count frames instead of samples for playback offset calculation.
This commit is contained in:
parent
226e860c38
commit
04526987e4
11
mkxp.pro
11
mkxp.pro
|
@ -13,10 +13,19 @@ isEmpty(BINDING) {
|
||||||
|
|
||||||
CONFIG += $$BINDING
|
CONFIG += $$BINDING
|
||||||
|
|
||||||
|
RGSS2 {
|
||||||
|
DEFINES += RGSS2
|
||||||
|
}
|
||||||
|
|
||||||
unix {
|
unix {
|
||||||
CONFIG += link_pkgconfig
|
CONFIG += link_pkgconfig
|
||||||
PKGCONFIG += QtCore sigc++-2.0 glew pixman-1 zlib \
|
PKGCONFIG += QtCore sigc++-2.0 glew pixman-1 zlib \
|
||||||
physfs sdl2 SDL2_image SDL2_ttf SDL_sound openal
|
physfs sdl2 SDL2_image SDL2_ttf SDL_sound \
|
||||||
|
openal
|
||||||
|
|
||||||
|
RGSS2 {
|
||||||
|
PKGCONFIG += vorbisfile
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# 'slots' keyword fucks with libsigc++
|
# 'slots' keyword fucks with libsigc++
|
||||||
|
|
|
@ -85,6 +85,11 @@ namespace Buffer
|
||||||
{
|
{
|
||||||
return getInteger(id, AL_BITS);
|
return getInteger(id, AL_BITS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline ALint getChannels(Buffer::ID id)
|
||||||
|
{
|
||||||
|
return getInteger(id, AL_CHANNELS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Source
|
namespace Source
|
||||||
|
@ -145,9 +150,12 @@ namespace Source
|
||||||
return getInteger(id, AL_SOURCE_STATE);
|
return getInteger(id, AL_SOURCE_STATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ALint getSampleOffset(Source::ID id)
|
inline ALfloat getSecOffset(Source::ID id)
|
||||||
{
|
{
|
||||||
return getInteger(id, AL_SAMPLE_OFFSET);
|
ALfloat value;
|
||||||
|
alGetSourcef(id.al, AL_SEC_OFFSET, &value);
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void setVolume(Source::ID id, float value)
|
inline void setVolume(Source::ID id, float value)
|
||||||
|
|
310
src/audio.cpp
310
src/audio.cpp
|
@ -40,6 +40,10 @@
|
||||||
#include <SDL_timer.h>
|
#include <SDL_timer.h>
|
||||||
#include <SDL_sound.h>
|
#include <SDL_sound.h>
|
||||||
|
|
||||||
|
#ifdef RGSS2
|
||||||
|
#include <vorbis/vorbisfile.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <alc.h>
|
#include <alc.h>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
@ -316,36 +320,35 @@ struct ALDataSource
|
||||||
* to provided AL buffer */
|
* to provided AL buffer */
|
||||||
virtual Status fillBuffer(AL::Buffer::ID alBuffer) = 0;
|
virtual Status fillBuffer(AL::Buffer::ID alBuffer) = 0;
|
||||||
|
|
||||||
virtual float samplesToOffset(uint32_t samples) = 0;
|
virtual int sampleRate() = 0;
|
||||||
|
|
||||||
virtual void seekToOffset(float offset) = 0;
|
virtual void seekToOffset(float seconds) = 0;
|
||||||
|
|
||||||
/* Seek back to start */
|
/* Seek back to start */
|
||||||
virtual void reset() = 0;
|
virtual void reset() = 0;
|
||||||
|
|
||||||
|
/* The frame count right after wrap around */
|
||||||
|
virtual uint32_t loopStartFrames() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SDLSoundSource : ALDataSource
|
struct SDLSoundSource : ALDataSource
|
||||||
{
|
{
|
||||||
Sound_Sample *sample;
|
Sound_Sample *sample;
|
||||||
SDL_RWops ops;
|
SDL_RWops &srcOps;
|
||||||
uint8_t sampleSize;
|
uint8_t sampleSize;
|
||||||
bool looped;
|
bool looped;
|
||||||
|
|
||||||
ALenum alFormat;
|
ALenum alFormat;
|
||||||
ALsizei alFreq;
|
ALsizei alFreq;
|
||||||
|
|
||||||
SDLSoundSource(const std::string &filename,
|
SDLSoundSource(SDL_RWops &ops,
|
||||||
|
const char *extension,
|
||||||
uint32_t maxBufSize,
|
uint32_t maxBufSize,
|
||||||
bool looped)
|
bool looped)
|
||||||
: looped(looped)
|
: srcOps(ops),
|
||||||
|
looped(looped)
|
||||||
{
|
{
|
||||||
const char *extension;
|
sample = Sound_NewSample(&srcOps, extension, 0, maxBufSize);
|
||||||
shState->fileSystem().openRead(ops,
|
|
||||||
filename.c_str(),
|
|
||||||
FileSystem::Audio,
|
|
||||||
false, &extension);
|
|
||||||
|
|
||||||
sample = Sound_NewSample(&ops, extension, 0, maxBufSize);
|
|
||||||
|
|
||||||
if (!sample)
|
if (!sample)
|
||||||
{
|
{
|
||||||
|
@ -399,24 +402,256 @@ struct SDLSoundSource : ALDataSource
|
||||||
return ALDataSource::NoError;
|
return ALDataSource::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
float samplesToOffset(uint32_t samples)
|
int sampleRate()
|
||||||
{
|
{
|
||||||
uint32_t frames = samples / sample->actual.channels;
|
return sample->actual.rate;
|
||||||
|
|
||||||
return static_cast<float>(frames) / sample->actual.rate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void seekToOffset(float offset)
|
void seekToOffset(float seconds)
|
||||||
{
|
{
|
||||||
Sound_Seek(sample, static_cast<uint32_t>(offset * 1000));
|
Sound_Seek(sample, static_cast<uint32_t>(seconds * 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
Sound_Rewind(sample);
|
Sound_Rewind(sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t loopStartFrames()
|
||||||
|
{
|
||||||
|
/* Loops from the beginning of the file */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef RGSS2
|
||||||
|
static size_t vfRead(void *ptr, size_t size, size_t nmemb, void *ops)
|
||||||
|
{
|
||||||
|
return SDL_RWread(static_cast<SDL_RWops*>(ops), ptr, size, nmemb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int vfSeek(void *ops, ogg_int64_t offset, int whence)
|
||||||
|
{
|
||||||
|
return SDL_RWseek(static_cast<SDL_RWops*>(ops), offset, whence);
|
||||||
|
}
|
||||||
|
|
||||||
|
static long vfTell(void *ops)
|
||||||
|
{
|
||||||
|
return SDL_RWtell(static_cast<SDL_RWops*>(ops));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ov_callbacks OvCallbacks =
|
||||||
|
{
|
||||||
|
vfRead,
|
||||||
|
vfSeek,
|
||||||
|
0,
|
||||||
|
vfTell
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct VorbisSource : ALDataSource
|
||||||
|
{
|
||||||
|
SDL_RWops &src;
|
||||||
|
|
||||||
|
OggVorbis_File vf;
|
||||||
|
|
||||||
|
uint32_t currentFrame;
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint32_t start;
|
||||||
|
uint32_t length;
|
||||||
|
uint32_t end;
|
||||||
|
bool valid;
|
||||||
|
bool requested;
|
||||||
|
} loop;
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
int channels;
|
||||||
|
int rate;
|
||||||
|
int frameSize;
|
||||||
|
ALenum alFormat;
|
||||||
|
} info;
|
||||||
|
|
||||||
|
std::vector<int16_t> sampleBuf;
|
||||||
|
|
||||||
|
VorbisSource(SDL_RWops &ops,
|
||||||
|
bool looped)
|
||||||
|
: src(ops),
|
||||||
|
currentFrame(0)
|
||||||
|
{
|
||||||
|
int error = ov_open_callbacks(&src, &vf, 0, 0, OvCallbacks);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
SDL_RWclose(&src);
|
||||||
|
throw Exception(Exception::MKXPError,
|
||||||
|
"Vorbisfile: Cannot read ogg file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extract bitstream info */
|
||||||
|
info.channels = vf.vi->channels;
|
||||||
|
info.rate = vf.vi->rate;
|
||||||
|
|
||||||
|
if (info.channels > 2)
|
||||||
|
{
|
||||||
|
ov_clear(&vf);
|
||||||
|
SDL_RWclose(&src);
|
||||||
|
throw Exception(Exception::MKXPError,
|
||||||
|
"Cannot handle audio with more than 2 channels");
|
||||||
|
}
|
||||||
|
|
||||||
|
info.alFormat = chooseALFormat(sizeof(int16_t), info.channels);
|
||||||
|
info.frameSize = sizeof(int16_t) * info.channels;
|
||||||
|
|
||||||
|
sampleBuf.resize(streamBufSize);
|
||||||
|
|
||||||
|
loop.requested = looped;
|
||||||
|
loop.valid = false;
|
||||||
|
loop.start = loop.length = 0;
|
||||||
|
|
||||||
|
if (!loop.requested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Try to extract loop info */
|
||||||
|
for (int i = 0; i < vf.vc->comments; ++i)
|
||||||
|
{
|
||||||
|
char *comment = vf.vc->user_comments[i];
|
||||||
|
char *sep = strstr(comment, "=");
|
||||||
|
|
||||||
|
/* No '=' found */
|
||||||
|
if (!sep)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Empty value */
|
||||||
|
if (!*(sep+1))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
*sep = '\0';
|
||||||
|
|
||||||
|
if (!strcmp(comment, "LOOPSTART"))
|
||||||
|
loop.start = strtol(sep+1, 0, 10);
|
||||||
|
|
||||||
|
if (!strcmp(comment, "LOOPLENGTH"))
|
||||||
|
loop.length = strtol(sep+1, 0, 10);
|
||||||
|
|
||||||
|
*sep = '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
loop.end = loop.start + loop.length;
|
||||||
|
loop.valid = (loop.start && loop.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
~VorbisSource()
|
||||||
|
{
|
||||||
|
ov_clear(&vf);
|
||||||
|
SDL_RWclose(&src);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sampleRate()
|
||||||
|
{
|
||||||
|
return info.rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void seekToOffset(float seconds)
|
||||||
|
{
|
||||||
|
ov_time_seek(&vf, seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
Status fillBuffer(AL::Buffer::ID alBuffer)
|
||||||
|
{
|
||||||
|
void *bufPtr = sampleBuf.data();
|
||||||
|
int availBuf = sampleBuf.size();
|
||||||
|
int bufUsed = 0;
|
||||||
|
|
||||||
|
int canRead = availBuf;
|
||||||
|
|
||||||
|
Status retStatus = ALDataSource::NoError;
|
||||||
|
|
||||||
|
if (loop.valid)
|
||||||
|
{
|
||||||
|
int tilLoopEnd = loop.end * info.frameSize;
|
||||||
|
|
||||||
|
canRead = std::min(availBuf, tilLoopEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (canRead > 16)
|
||||||
|
{
|
||||||
|
long res = ov_read(&vf, static_cast<char*>(bufPtr),
|
||||||
|
canRead, 0, sizeof(int16_t), 1, 0);
|
||||||
|
|
||||||
|
if (res < 0)
|
||||||
|
{
|
||||||
|
/* Read error */
|
||||||
|
retStatus = ALDataSource::Error;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res == 0)
|
||||||
|
{
|
||||||
|
/* EOF */
|
||||||
|
if (loop.requested)
|
||||||
|
{
|
||||||
|
retStatus = ALDataSource::WrapAround;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
retStatus = ALDataSource::EndOfStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufUsed += (res / sizeof(int16_t));
|
||||||
|
bufPtr = &sampleBuf[bufUsed];
|
||||||
|
currentFrame += (res / info.frameSize);
|
||||||
|
|
||||||
|
if (loop.valid && currentFrame >= loop.end)
|
||||||
|
{
|
||||||
|
/* Determine how many frames we're
|
||||||
|
* over the loop end */
|
||||||
|
int discardFrames = currentFrame - loop.end;
|
||||||
|
bufUsed -= discardFrames * info.channels;
|
||||||
|
|
||||||
|
retStatus = ALDataSource::WrapAround;
|
||||||
|
|
||||||
|
/* Seek to loop start */
|
||||||
|
currentFrame = loop.start;
|
||||||
|
if (ov_pcm_seek(&vf, currentFrame) != 0)
|
||||||
|
retStatus = ALDataSource::Error;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
canRead -= res;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retStatus != ALDataSource::Error)
|
||||||
|
AL::Buffer::uploadData(alBuffer, info.alFormat, sampleBuf.data(),
|
||||||
|
bufUsed*sizeof(int16_t), info.rate);
|
||||||
|
|
||||||
|
return retStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
ov_raw_seek(&vf, 0);
|
||||||
|
currentFrame = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t loopStartFrames()
|
||||||
|
{
|
||||||
|
if (loop.valid)
|
||||||
|
return loop.start;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
/* State-machine like audio playback stream.
|
/* State-machine like audio playback stream.
|
||||||
* This class is NOT thread safe */
|
* This class is NOT thread safe */
|
||||||
struct ALStream
|
struct ALStream
|
||||||
|
@ -451,9 +686,11 @@ struct ALStream
|
||||||
AL::Source::ID alSrc;
|
AL::Source::ID alSrc;
|
||||||
AL::Buffer::ID alBuf[streamBufs];
|
AL::Buffer::ID alBuf[streamBufs];
|
||||||
|
|
||||||
uint64_t procSamples;
|
uint64_t procFrames;
|
||||||
AL::Buffer::ID lastBuf;
|
AL::Buffer::ID lastBuf;
|
||||||
|
|
||||||
|
SDL_RWops srcOps;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
ALenum format;
|
ALenum format;
|
||||||
|
@ -620,10 +857,9 @@ struct ALStream
|
||||||
if (state == Closed)
|
if (state == Closed)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
uint32_t sampleSum =
|
float procOffset = static_cast<float>(procFrames) / source->sampleRate();
|
||||||
procSamples + AL::Source::getSampleOffset(alSrc);
|
|
||||||
|
|
||||||
return source->samplesToOffset(sampleSum);
|
return procOffset + AL::Source::getSecOffset(alSrc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -634,7 +870,24 @@ private:
|
||||||
|
|
||||||
void openSource(const std::string &filename)
|
void openSource(const std::string &filename)
|
||||||
{
|
{
|
||||||
source = new SDLSoundSource(filename, streamBufSize, looped);
|
const char *ext;
|
||||||
|
shState->fileSystem().openRead(srcOps, filename.c_str(), FileSystem::Audio, false, &ext);
|
||||||
|
|
||||||
|
#ifdef RGSS2
|
||||||
|
/* Try to read ogg file signature */
|
||||||
|
char sig[5];
|
||||||
|
memset(sig, '\0', sizeof(sig));
|
||||||
|
SDL_RWread(&srcOps, sig, 1, 4);
|
||||||
|
SDL_RWseek(&srcOps, 0, RW_SEEK_SET);
|
||||||
|
|
||||||
|
if (!strcmp(sig, "OggS"))
|
||||||
|
source = new VorbisSource(srcOps, looped);
|
||||||
|
else
|
||||||
|
source = new SDLSoundSource(srcOps, ext, streamBufSize, looped);
|
||||||
|
#else
|
||||||
|
source = new SDLSoundSource(srcOps, ext, streamBufSize, looped);
|
||||||
|
#endif
|
||||||
|
|
||||||
needsRewind = false;
|
needsRewind = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -651,14 +904,14 @@ private:
|
||||||
needsRewind = true;
|
needsRewind = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
procSamples = 0;
|
procFrames = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void startStream()
|
void startStream()
|
||||||
{
|
{
|
||||||
clearALQueue();
|
clearALQueue();
|
||||||
|
|
||||||
procSamples = 0;
|
procFrames = 0;
|
||||||
|
|
||||||
preemptPause = false;
|
preemptPause = false;
|
||||||
streamInited = false;
|
streamInited = false;
|
||||||
|
@ -783,17 +1036,18 @@ private:
|
||||||
{
|
{
|
||||||
/* Reset the processed sample count so
|
/* Reset the processed sample count so
|
||||||
* querying the playback offset returns 0.0 again */
|
* querying the playback offset returns 0.0 again */
|
||||||
procSamples = 0;
|
procFrames = source->loopStartFrames();
|
||||||
lastBuf = AL::Buffer::ID(0);
|
lastBuf = AL::Buffer::ID(0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Add the sample count contained in this
|
/* Add the frame count contained in this
|
||||||
* buffer to the total count */
|
* buffer to the total count */
|
||||||
ALint bits = AL::Buffer::getBits(buf);
|
ALint bits = AL::Buffer::getBits(buf);
|
||||||
ALint size = AL::Buffer::getSize(buf);
|
ALint size = AL::Buffer::getSize(buf);
|
||||||
|
ALint chan = AL::Buffer::getChannels(buf);
|
||||||
|
|
||||||
procSamples += (size / (bits / 8));
|
procFrames += ((size / (bits / 8)) / chan);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceExhausted)
|
if (sourceExhausted)
|
||||||
|
|
Loading…
Reference in New Issue