1
0
Fork 0
mirror of https://github.com/ctruLua/ctruLua.git synced 2025-10-27 16:39:29 +00:00

Added streaming support to ctr.audio

Currently runs in the main thread because I can't get it to work on another thread.

You will need to call audio.update() each frame to make audio streaming work.
This commit is contained in:
Reuh 2016-03-23 19:50:10 +01:00
parent 5107f0277c
commit 6ac83ca6df

View file

@ -11,6 +11,7 @@ There are 24 audio channels available, numbered from 0 to 23.
#include <malloc.h> #include <malloc.h>
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <math.h>
#include <lua.h> #include <lua.h>
#include <lauxlib.h> #include <lauxlib.h>
@ -22,30 +23,78 @@ There are 24 audio channels available, numbered from 0 to 23.
typedef enum { typedef enum {
TYPE_UNKNOWN = -1, TYPE_UNKNOWN = -1,
TYPE_OGG = 0, TYPE_OGG = 0,
TYPE_WAV = 1 TYPE_WAV = 1,
TYPE_RAW = 2
} filetype; } filetype;
// Audio object userdata // Audio object userdata
typedef struct { typedef struct {
filetype type; // file type filetype type; // file type
// OGG Vorbis specific // File type specific
OggVorbis_File vf; // ogg vorbis file union {
// OGG Vorbis
struct {
OggVorbis_File vf;
int currentSection; // section and position at the end of the initial data
long rawPosition;
};
// WAV
struct {
FILE* file;
long fileSize;
long filePosition; // position at the end of the initial data
};
};
// Needed for playback // Needed for playback
float rate; // sample rate (per channel) (Hz) float rate; // sample rate (per channel) (Hz)
u32 channels; // channel count u32 channels; // channel count
u32 encoding; // data encoding (NDSP_ENCODING_*) u32 encoding; // data encoding (NDSP_ENCODING_*)
// Initial data
u32 nsamples; // numbers of samples in the audio (per channel, not the total) u32 nsamples; // numbers of samples in the audio (per channel, not the total)
u32 size; // number of bytes in the audio (total, ie data size) u32 size; // number of bytes in the audio (total, ie data size)
char* data; // raw audio data char* data; // raw audio data
// Other useful data
u16 bytePerSample; // bytes per sample (warning: undefined for ADPCM (only for raw data))
u32 chunkSize; // size per chunk (for streaming)
u32 chunkNsamples; // number of samples per chunk
// Playing parameters (type-independant) // Playing parameters (type-independant)
float mix[12]; // mix parameters float mix[12]; // mix parameters
ndspInterpType interp; // interpolation type ndspInterpType interp; // interpolation type
double speed; // playing speed double speed; // playing speed
} audio_userdata; } audio_userdata;
// Audio stream instance struct (when an audio is played; only used when streaming)
typedef struct {
audio_userdata* audio;
// Current position information
union {
// OGG
struct {
int currentSection;
long rawPosition;
};
// WAV
long filePosition;
};
double prevStartTime; // audio time when last chunk started playing
bool eof; // if reached end of file
bool done; // if streaming ended and the stream will be skipped on the next update
// (the struct should be keept in memory until replaced or it will break audio:time())
char* nextData; // the next data to play
ndspWaveBuf* nextWaveBuf;
char* prevData; // the data actually playing
ndspWaveBuf* prevWaveBuf;
} audio_stream;
// Indicate if NDSP was initialized or not. // Indicate if NDSP was initialized or not.
// NDSP doesn't work on citra yet. // NDSP doesn't work on citra yet.
// Please only throw an error related to this when using a ndsp function, so other parts of the // Please only throw an error related to this when using a ndsp function, so other parts of the
@ -55,12 +104,24 @@ bool isAudioInitialized = false;
// Array of the last audio_userdata sent to each channel; channels range from 0 to 23 // Array of the last audio_userdata sent to each channel; channels range from 0 to 23
audio_userdata* channels[24]; audio_userdata* channels[24];
// Array of the audio_instance that needs to be updated when calling audio.update (indexed per channel).
audio_stream* streaming[24];
/*** /***
Load an audio file. Load an audio file.
OGG Vorbis and PCM WAV file format are currently supported. OGG Vorbis and PCM WAV file format are currently supported.
(Most WAV files use the PCM encoding). (Most WAV files use the PCM encoding).
NOTE: audio streaming doesn't use threading for now, this means that the decoding will be done on the main thread.
It should work fine with WAVs, but with OGG files you may suffer slowdowns when a new chunk of data is decoded.
To avoid that, you can either reduce the chunkDuration or disable streaming, but be careful if you do so, audio files
can fill the memory really quickly.
@function load @function load
@tparam string path path to the file @tparam string path path to the file or the data if type is raw
@tparam[opt=0.1] number chunkDuration if set to -1, streaming will be disabled (all data is loaded in memory at once)
Other values are the stream chunk duration in seconds (ctrµLua will load
the audio per chunk of x seconds). Note that you need to call audio.update() each
frame in order for ctµLua to load new data from audio streams. Two chunks of data
will be loaded at the same time at most (one playing, the other ready to be played).
@tparam[opt=detect] string type file type, `"ogg"` or `"wav"`. @tparam[opt=detect] string type file type, `"ogg"` or `"wav"`.
If set to `"detect"`, will try to deduce the type from the filename. If set to `"detect"`, will try to deduce the type from the filename.
@treturn[1] audio the loaded audio object @treturn[1] audio the loaded audio object
@ -69,7 +130,8 @@ OGG Vorbis and PCM WAV file format are currently supported.
*/ */
static int audio_load(lua_State *L) { static int audio_load(lua_State *L) {
const char *path = luaL_checkstring(L, 1); const char *path = luaL_checkstring(L, 1);
const char* argType = luaL_optstring(L, 2, "detect"); double streamChunk = luaL_optnumber(L, 2, 0.1);
const char* argType = luaL_optstring(L, 3, "detect");
// Create userdata // Create userdata
audio_userdata *audio = lua_newuserdata(L, sizeof(*audio)); audio_userdata *audio = lua_newuserdata(L, sizeof(*audio));
@ -91,6 +153,8 @@ static int audio_load(lua_State *L) {
type = TYPE_OGG; type = TYPE_OGG;
} else if (strcmp(argType, "wav") == 0) { } else if (strcmp(argType, "wav") == 0) {
type = TYPE_WAV; type = TYPE_WAV;
} else if (strcmp(argType, "raw") == 0) {
type = TYPE_RAW;
} }
// Open and read file // Open and read file
@ -114,16 +178,26 @@ static int audio_load(lua_State *L) {
audio->encoding = NDSP_ENCODING_PCM16; audio->encoding = NDSP_ENCODING_PCM16;
audio->nsamples = ov_pcm_total(&audio->vf, -1); audio->nsamples = ov_pcm_total(&audio->vf, -1);
audio->size = audio->nsamples * audio->channels * 2; // *2 because output is PCM16 (2 bytes/sample) audio->size = audio->nsamples * audio->channels * 2; // *2 because output is PCM16 (2 bytes/sample)
audio->bytePerSample = 2;
if (linearSpaceFree() < audio->size) luaL_error(L, "not enough linear memory available"); // Streaming
audio->data = linearAlloc(audio->size); if (streamChunk < 0) {
audio->chunkNsamples = audio->nsamples;
audio->chunkSize = audio->size;
} else {
audio->chunkNsamples = round(streamChunk * audio->rate);
audio->chunkSize = audio->chunkNsamples * audio->channels * 2;
}
// Allocate
if (linearSpaceFree() < audio->chunkSize) luaL_error(L, "not enough linear memory available");
audio->data = linearAlloc(audio->chunkSize);
// Decoding loop // Decoding loop
int offset = 0; int offset = 0;
int eof = 0; int eof = 0;
int current_section; while (!eof && offset < audio->chunkSize) {
while (!eof) { long ret = ov_read(&audio->vf, &audio->data[offset], fmin(audio->chunkSize - offset, 4096), &audio->currentSection);
long ret = ov_read(&audio->vf, &audio->data[offset], 4096, &current_section);
if (ret == 0) { if (ret == 0) {
eof = 1; eof = 1;
@ -137,6 +211,7 @@ static int audio_load(lua_State *L) {
offset += ret; offset += ret;
} }
} }
audio->rawPosition = ov_raw_tell(&audio->vf);
return 1; return 1;
@ -222,13 +297,29 @@ static int audio_load(lua_State *L) {
return 0; return 0;
} }
audio->bytePerSample = byte_per_sample / audio->channels;
// Streaming
if (streamChunk < 0) {
audio->chunkNsamples = audio->nsamples;
audio->chunkSize = audio->size;
} else {
audio->chunkNsamples = round(streamChunk * audio->rate);
audio->chunkSize = audio->chunkNsamples * audio->channels * audio->bytePerSample;
}
// Read data // Read data
if (linearSpaceFree() < audio->size) luaL_error(L, "not enough linear memory available"); if (linearSpaceFree() < audio->chunkSize) luaL_error(L, "not enough linear memory available");
audio->data = linearAlloc(audio->size); audio->data = linearAlloc(audio->chunkSize);
fread(audio->data, audio->size, 1, file); fread(audio->data, audio->chunkSize, 1, file);
audio->file = file;
audio->filePosition = ftell(file);
fseek(file, 0, SEEK_END);
audio->fileSize = ftell(file);
fclose(file);
return 1; return 1;
} else { } else {
@ -244,6 +335,7 @@ static int audio_load(lua_State *L) {
/*** /***
Load raw audio data from a string. Load raw audio data from a string.
No streaming.
@function loadRaw @function loadRaw
@tparam string data raw audio data @tparam string data raw audio data
@tparam number rate sampling rate @tparam number rate sampling rate
@ -264,16 +356,18 @@ static int audio_loadRaw(lua_State *L) {
luaL_getmetatable(L, "LAudio"); luaL_getmetatable(L, "LAudio");
lua_setmetatable(L, -2); lua_setmetatable(L, -2);
audio->type = TYPE_WAV; audio->type = TYPE_RAW;
audio->rate = rate; audio->rate = rate;
audio->channels = channels; audio->channels = channels;
u8 sampleSize = 2; // default to 2 u8 sampleSize = 2; // default to 2
if (strcmp(argEncoding, "PCM8")) { if (strcmp(argEncoding, "PCM8")) {
audio->encoding = NDSP_ENCODING_PCM8; audio->encoding = NDSP_ENCODING_PCM8;
audio->bytePerSample = 1;
sampleSize = 1; sampleSize = 1;
} else if (strcmp(argEncoding, "PCM16")) { } else if (strcmp(argEncoding, "PCM16")) {
audio->encoding = NDSP_ENCODING_PCM16; audio->encoding = NDSP_ENCODING_PCM16;
audio->bytePerSample = 2;
} else if (strcmp(argEncoding, "ADPCM")) { } else if (strcmp(argEncoding, "ADPCM")) {
audio->encoding = NDSP_ENCODING_ADPCM; audio->encoding = NDSP_ENCODING_ADPCM;
} else { } else {
@ -286,6 +380,9 @@ static int audio_loadRaw(lua_State *L) {
audio->size = dataSize; audio->size = dataSize;
audio->data = data; audio->data = data;
audio->chunkSize = audio->size;
audio->chunkNsamples = audio->nsamples;
audio->speed = 1.0; audio->speed = 1.0;
return 1; return 1;
@ -463,6 +560,102 @@ static int audio_stop(lua_State *L) {
return 1; return 1;
} }
/***
Update all the currently playing audio streams.
Must be called every frame if you want to use audio with streaming.
@function update
*/
static int audio_update(lua_State *L) {
if (!isAudioInitialized) luaL_error(L, "audio wasn't initialized correctly");
for (int i = 0; i <= 23; i++) {
if (streaming[i] == NULL) continue;
audio_stream* stream = streaming[i];
if (stream->done) continue;
audio_userdata* audio = stream->audio;
// If the next chunk started to play, load the next one
if (stream->nextWaveBuf != NULL && ndspChnGetWaveBufSeq(i) == stream->nextWaveBuf->sequence_id) {
if (stream->prevWaveBuf) stream->prevStartTime = stream->prevStartTime + (double)(audio->chunkNsamples) / audio->rate;
if (!stream->eof) {
// Swap buffers
char* prevData = stream->prevData; // doesn't contain important data, can rewrite
char* nextData = stream->nextData; // contains the data that started playing
stream->prevData = nextData; // buffer in use
stream->nextData = prevData; // now contains an available buffer
stream->prevWaveBuf = stream->nextWaveBuf;
// Decoding loop
u32 chunkNsamples = audio->chunkNsamples; // chunk nsamples and size may be lower than the defaults if reached EOF
u32 chunkSize = audio->chunkSize;
if (audio->type == TYPE_OGG) {
if (ov_seekable(&audio->vf) && ov_raw_tell(&audio->vf) != stream->rawPosition)
ov_raw_seek(&audio->vf, stream->rawPosition); // goto last read end (audio file may be played multiple times at one)
int offset = 0;
while (!stream->eof && offset < audio->chunkSize) {
long ret = ov_read(&audio->vf, &stream->nextData[offset], fmin(audio->chunkSize - offset, 4096), &stream->currentSection);
if (ret == 0) {
stream->eof = 1;
} else if (ret < 0) {
luaL_error(L, "error in the ogg vorbis stream");
return 0;
} else {
offset += ret;
}
}
stream->rawPosition = ov_raw_tell(&audio->vf);
chunkSize = offset;
chunkNsamples = chunkSize / audio->channels / audio->bytePerSample;
} else if (audio->type == TYPE_WAV) {
chunkSize = fmin(audio->fileSize - stream->filePosition, audio->chunkSize);
chunkNsamples = chunkSize / audio->channels / audio->bytePerSample;
fseek(audio->file, stream->filePosition, SEEK_SET); // goto last read end (audio file may be played multiple times at one)
fread(stream->nextData, chunkSize, 1, audio->file);
stream->filePosition = ftell(audio->file);
if (stream->filePosition == audio->fileSize) stream->eof = 1;
} else luaL_error(L, "unknown audio type");
// Send & play audio data
ndspWaveBuf* waveBuf = calloc(1, sizeof(ndspWaveBuf));
waveBuf->data_vaddr = stream->nextData;
waveBuf->nsamples = chunkNsamples;
waveBuf->looping = false;
DSP_FlushDataCache((u32*)stream->nextData, chunkSize);
ndspChnWaveBufAdd(i, waveBuf);
stream->nextWaveBuf = waveBuf;
}
}
// Free the last chunk if it's no longer played
if (stream->prevWaveBuf != NULL && ndspChnGetWaveBufSeq(i) != stream->prevWaveBuf->sequence_id) {
free(stream->prevWaveBuf);
stream->prevWaveBuf = NULL;
}
// We're done
if (stream->prevWaveBuf == NULL && stream->nextWaveBuf != NULL && ndspChnGetWaveBufSeq(i) != stream->nextWaveBuf->sequence_id && stream->eof) {
free(stream->nextWaveBuf);
stream->nextWaveBuf = NULL;
linearFree(stream->prevData);
stream->prevData = NULL;
linearFree(stream->nextData);
stream->nextData = NULL;
stream->done = true;
}
}
return 0;
}
/*** /***
audio object audio object
@section Methods @section Methods
@ -505,8 +698,11 @@ static int audio_object_time(lua_State *L) {
if (channel == -1 || channels[channel] != audio || !isAudioInitialized) // audio not playing if (channel == -1 || channels[channel] != audio || !isAudioInitialized) // audio not playing
lua_pushnumber(L, 0); lua_pushnumber(L, 0);
else else {
lua_pushnumber(L, (double)(ndspChnGetSamplePos(channel)) / audio->rate); double additionnalTime = 0;
if (streaming[channel] != NULL) additionnalTime = streaming[channel]->prevStartTime;
lua_pushnumber(L, (double)(ndspChnGetSamplePos(channel)) / audio->rate + additionnalTime);
}
return 1; return 1;
} }
@ -650,20 +846,46 @@ static int audio_object_play(lua_State *L) {
ndspChnSetRate(channel, audio->rate * audio->speed); // maybe hackish way to set a different speed, but it works ndspChnSetRate(channel, audio->rate * audio->speed); // maybe hackish way to set a different speed, but it works
ndspChnSetFormat(channel, NDSP_CHANNELS(audio->channels) | NDSP_ENCODING(audio->encoding)); ndspChnSetFormat(channel, NDSP_CHANNELS(audio->channels) | NDSP_ENCODING(audio->encoding));
// Send & play audio data // Send & play audio initial data
ndspWaveBuf* waveBuf = calloc(1, sizeof(ndspWaveBuf)); ndspWaveBuf* waveBuf = calloc(1, sizeof(ndspWaveBuf));
waveBuf->data_vaddr = audio->data; waveBuf->data_vaddr = audio->data;
waveBuf->nsamples = audio->nsamples; waveBuf->nsamples = audio->chunkNsamples;
waveBuf->looping = loop; waveBuf->looping = loop;
DSP_FlushDataCache((u32*)audio->data, audio->size); DSP_FlushDataCache((u32*)audio->data, audio->chunkSize);
ndspChnWaveBufAdd(channel, waveBuf); ndspChnWaveBufAdd(channel, waveBuf);
channels[channel] = audio; channels[channel] = audio;
lua_pushinteger(L, channel); lua_pushinteger(L, channel);
// Remove last audio stream
if (streaming[channel] != NULL) {
free(streaming[channel]);
streaming[channel] = NULL;
}
// Stream the rest of the audio
if (audio->chunkSize < audio->size) {
audio_stream* stream = calloc(1, sizeof(audio_stream));
stream->audio = audio;
stream->nextWaveBuf = waveBuf;
// Allocate buffers
if (linearSpaceFree() < audio->chunkSize*2) luaL_error(L, "not enough linear memory available");
stream->nextData = linearAlloc(audio->chunkSize);
stream->prevData = linearAlloc(audio->chunkSize);
// Init stream values
if (audio->type == TYPE_OGG) {
stream->currentSection = audio->currentSection;
stream->rawPosition = audio->rawPosition;
} else if (audio->type == TYPE_WAV) stream->filePosition = audio->filePosition;
streaming[channel] = stream;
}
return 1; return 1;
} }
@ -718,6 +940,10 @@ static int audio_object_type(lua_State *L) {
lua_pushstring(L, "ogg"); lua_pushstring(L, "ogg");
else if (audio->type == TYPE_WAV) else if (audio->type == TYPE_WAV)
lua_pushstring(L, "wav"); lua_pushstring(L, "wav");
else if (audio->type == TYPE_RAW)
lua_pushstring(L, "raw");
else
lua_pushstring(L, "unknown");
return 1; return 1;
} }
@ -739,6 +965,7 @@ static int audio_object_unload(lua_State *L) {
} }
if (audio->type == TYPE_OGG) ov_clear(&audio->vf); if (audio->type == TYPE_OGG) ov_clear(&audio->vf);
else if (audio->type == TYPE_WAV) fclose(audio->file);
// Free memory // Free memory
linearFree(audio->data); linearFree(audio->data);
@ -872,6 +1099,7 @@ static const struct luaL_Reg audio_lib[] = {
{ "interpolation", audio_interpolation }, { "interpolation", audio_interpolation },
{ "speed", audio_speed }, { "speed", audio_speed },
{ "stop", audio_stop }, { "stop", audio_stop },
{ "update", audio_update },
{ NULL, NULL } { NULL, NULL }
}; };
@ -887,9 +1115,7 @@ int luaopen_audio_lib(lua_State *L) {
} }
void load_audio_lib(lua_State *L) { void load_audio_lib(lua_State *L) {
if (!isAudioInitialized) { if (!isAudioInitialized) isAudioInitialized = !ndspInit(); // ndspInit returns 0 in case of success
isAudioInitialized = !ndspInit(); // ndspInit returns 0 in case of success
}
luaL_requiref(L, "ctr.audio", luaopen_audio_lib, false); luaL_requiref(L, "ctr.audio", luaopen_audio_lib, false);
} }