From b22df31feeb7e695faabecfd7cc6fdd24609b0e1 Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Mon, 28 Aug 2023 18:57:55 -0700 Subject: til: add preliminary audio backend This is an early implementation of something resembling an audio backend for rototiller/libtil. The assumption for now is that everything will use signed 16-bit native-endian stereo output @ 44.1khz. --- src/Makefile.am | 7 +- src/main.c | 101 +++++++++++++++++++++++- src/mem_audio.c | 143 +++++++++++++++++++++++++++++++++ src/sdl_audio.c | 204 ++++++++++++++++++++++++++++++++++++++++++++++++ src/til_args.c | 5 +- src/til_args.h | 1 + src/til_audio.c | 148 +++++++++++++++++++++++++++++++++++ src/til_audio.h | 39 +++++++++ src/til_audio_context.c | 35 +++++++++ src/til_audio_context.h | 21 +++++ 10 files changed, 699 insertions(+), 5 deletions(-) create mode 100644 src/mem_audio.c create mode 100644 src/sdl_audio.c create mode 100644 src/til_audio.c create mode 100644 src/til_audio.h create mode 100644 src/til_audio_context.c create mode 100644 src/til_audio_context.h diff --git a/src/Makefile.am b/src/Makefile.am index c6877a7..fda9b40 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -6,6 +6,10 @@ libtil_la_SOURCES = \ til.h \ til_args.c \ til_args.h \ + til_audio.c \ + til_audio.h \ + til_audio_context.c \ + til_audio_context.h \ til_builtins.c \ til_fb.c \ til_fb.h \ @@ -75,6 +79,7 @@ rototiller_SOURCES = \ fps.c \ fps.h \ main.c \ + mem_audio.c \ mem_fb.c \ setup.c \ setup.h \ @@ -89,7 +94,7 @@ rototiller_SOURCES = \ til_util.h if ENABLE_SDL -rototiller_SOURCES += sdl_fb.c +rototiller_SOURCES += sdl_audio.c sdl_fb.c endif if ENABLE_DRM rototiller_SOURCES += drm_fb.c diff --git a/src/main.c b/src/main.c index 45e8c3b..70f83e2 100644 --- a/src/main.c +++ b/src/main.c @@ -12,6 +12,7 @@ #include "til.h" #include "til_args.h" +#include "til_audio.h" #include "til_fb.h" #include "til_settings.h" #include "til_stream.h" @@ -45,11 +46,25 @@ #define DEFAULT_VIDEO "mem" #endif +#ifndef DEFAULT_AUDIO +#ifdef HAVE_SDL +#define DEFAULT_AUDIO "sdl" +#endif +#endif + +#ifndef DEFAULT_AUDIO +#define DEFAULT_AUDIO "mem" +#endif + extern til_fb_ops_t drm_fb_ops; extern til_fb_ops_t mem_fb_ops; extern til_fb_ops_t sdl_fb_ops; static til_fb_ops_t *fb_ops; +extern til_audio_ops_t sdl_audio_ops; +extern til_audio_ops_t mem_audio_ops; +static til_audio_ops_t *audio_ops; + typedef struct rototiller_t { til_args_t args; const til_module_t *module; @@ -58,6 +73,7 @@ typedef struct rototiller_t { til_fb_fragment_t *fragment; pthread_t thread; til_fb_t *fb; + til_audio_context_t *audio; } rototiller_t; static rototiller_t rototiller; @@ -66,6 +82,8 @@ static rototiller_t rototiller; typedef struct setup_t { til_settings_t *module_settings; til_setup_t *module_setup; + til_settings_t *audio_settings; + til_setup_t *audio_setup; til_settings_t *video_settings; til_setup_t *video_setup; unsigned seed; @@ -77,6 +95,59 @@ typedef struct setup_t { * subclass the video backend vs. renderer stuff. */ +/* select audio backend if not yet selected, then setup the selected backend. */ +static int setup_audio(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup) +{ + til_setting_t *setting; + const char *audio; + + audio = til_settings_get_value_by_idx(settings, 0, &setting); + if (!audio || !setting->desc) { + const char *values[] = { +#ifdef HAVE_SDL + "sdl", +#endif + "mem", + NULL, + }; + int r; + + r = til_setting_desc_new( settings, + &(til_setting_spec_t){ + .name = "Audio backend", + .key = NULL, + .regex = "[a-z]+", + .preferred = DEFAULT_AUDIO, + .values = values, + .annotations = NULL, + .as_label = 1, + }, res_desc); + + if (r < 0) + return r; + + *res_setting = audio ? setting : NULL; + + return 1; + } + + /* XXX: this is kind of hacky for now */ + if (!strcasecmp(audio, "mem")) { + audio_ops = &mem_audio_ops; + + return mem_audio_ops.setup(settings, res_setting, res_desc, res_setup); + } +#ifdef HAVE_SDL + if (!strcasecmp(audio, "sdl")) { + audio_ops = &sdl_audio_ops; + + return sdl_audio_ops.setup(settings, res_setting, res_desc, res_setup); + } +#endif + + return -EINVAL; +} + /* select video backend if not yet selected, then setup the selected backend. */ static int setup_video(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup) { @@ -228,6 +299,10 @@ static int setup_from_args(til_args_t *args, setup_t *res_setup, const char **re if (!setup.module_settings) goto _err; + setup.audio_settings = til_settings_new(NULL, NULL, "audio", args->audio); + if (!setup.audio_settings) + goto _err; + setup.video_settings = til_settings_new(NULL, NULL, "video", args->video); if (!setup.video_settings) goto _err; @@ -238,6 +313,12 @@ static int setup_from_args(til_args_t *args, setup_t *res_setup, const char **re if (r) changes = 1; + r = setup_interactively(setup.audio_settings, setup_audio, args->use_defaults, &setup.audio_setup, res_failed_desc_path); + if (r < 0) + goto _err; + if (r) + changes = 1; + r = setup_interactively(setup.video_settings, setup_video, args->use_defaults, &setup.video_setup, res_failed_desc_path); if (r < 0) goto _err; @@ -251,6 +332,7 @@ static int setup_from_args(til_args_t *args, setup_t *res_setup, const char **re _err: free((void *)setup.title); til_settings_free(setup.module_settings); + til_settings_free(setup.audio_settings); til_settings_free(setup.video_settings); return r; @@ -269,7 +351,7 @@ static char * seed_as_arg(unsigned seed) static int print_setup_as_args(setup_t *setup, int wait) { - char *seed_arg, *module_args, *video_args; + char *seed_arg, *module_args, *audio_args, *video_args; char buf[64]; int r = -ENOMEM; @@ -281,13 +363,18 @@ static int print_setup_as_args(setup_t *setup, int wait) if (!module_args) goto _out_seed; + audio_args = til_settings_as_arg(setup->audio_settings); + if (!audio_args) + goto _out_module; + video_args = til_settings_as_arg(setup->video_settings); if (!video_args) - goto _out_module; + goto _out_audio; - r = printf("\nConfigured settings as flags:\n --seed=%s '--module=%s' '--video=%s'\n", + r = printf("\nConfigured settings as flags:\n --seed=%s '--module=%s' '--audio=%s' '--video=%s'\n", seed_arg, module_args, + audio_args, video_args); if (r < 0) goto _out_video; @@ -302,6 +389,8 @@ static int print_setup_as_args(setup_t *setup, int wait) _out_video: free(video_args); +_out_audio: + free(audio_args); _out_module: free(module_args); _out_seed: @@ -395,6 +484,9 @@ int main(int argc, const char *argv[]) exit_if((r = til_fb_new(fb_ops, setup.title, setup.video_setup, NUM_FB_PAGES, &rototiller.fb)) < 0, "unable to create fb: %s", strerror(-r)); + exit_if((r = til_audio_open(audio_ops, setup.audio_setup, &rototiller.audio)) < 0, + "unable to open audio: %s", strerror(-r)); + exit_if(!(rototiller.stream = til_stream_new()), "unable to create root stream"); @@ -426,12 +518,15 @@ int main(int argc, const char *argv[]) til_module_context_free(rototiller.module_context); til_stream_free(rototiller.stream); + til_audio_shutdown(rototiller.audio); til_fb_free(rototiller.fb); { /* free setup (move to function? and disambiguate from til_setup_t w/rename? TODO) */ free((void *)setup.title); til_setup_free(setup.video_setup); til_settings_free(setup.video_settings); + til_setup_free(setup.audio_setup); + til_settings_free(setup.audio_settings); til_setup_free(setup.module_setup); til_settings_free(setup.module_settings); } diff --git a/src/mem_audio.c b/src/mem_audio.c new file mode 100644 index 0000000..b8d51df --- /dev/null +++ b/src/mem_audio.c @@ -0,0 +1,143 @@ +#include +#include +#include + +#include "til.h" +#include "til_audio.h" +#include "til_audio_context.h" +#include "til_settings.h" +#include "til_setup.h" + +/* "mem" audio backend */ + +typedef struct mem_audio_setup_t { + til_setup_t til_setup; +} mem_audio_setup_t; + +typedef struct mem_audio_t { + til_audio_context_t til_audio_context; + + unsigned n_queued; + unsigned n_queued_start_ticks; + unsigned paused:1; +} mem_audio_t; + + +static int mem_audio_init(til_setup_t *setup, til_audio_context_t **res_context); +static int mem_audio_queue(til_audio_context_t *context, int16_t *frames, int n_frames);; +static unsigned mem_audio_n_queued(til_audio_context_t *context); +static void mem_audio_drop(til_audio_context_t *context); +static void mem_audio_pause(til_audio_context_t *context); +static void mem_audio_unpause(til_audio_context_t *context); +static int mem_audio_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup); + + +til_audio_ops_t mem_audio_ops = { + .init = mem_audio_init, + .drop = mem_audio_drop, + .pause = mem_audio_pause, + .unpause = mem_audio_unpause, + .queue = mem_audio_queue, + .n_queued = mem_audio_n_queued, + .setup = mem_audio_setup, +}; + + +/* this simulates an audio timer grinding through the queued frames when unpaused, + * returns the remaining n_queued (if any) for convenience, but it's also maintained + * at c->n_queued. + */ +static unsigned mem_refresh_n_queued(mem_audio_t *c) +{ + if (!c->paused && c->n_queued) { + unsigned now = til_ticks_now(); + unsigned n_played = ((float)(now - c->n_queued_start_ticks) * 44.1f); + + c->n_queued -= MIN(c->n_queued, n_played); + } + + return c->n_queued; +} + + +static int mem_audio_init(til_setup_t *setup, til_audio_context_t **res_context) +{ + mem_audio_t *c; + + assert(setup); + assert(res_context); + + c = til_audio_context_new(&mem_audio_ops, sizeof(mem_audio_t), setup); + if (!c) + return -ENOMEM; + + c->paused = 1; + *res_context = &c->til_audio_context; + + return 0; +} + + +static void mem_audio_drop(til_audio_context_t *context) +{ + mem_audio_t *c = (mem_audio_t *)context; + + c->n_queued = 0; +} + + +static void mem_audio_pause(til_audio_context_t *context) +{ + mem_audio_t *c = (mem_audio_t *)context; + + if (!c->paused) { + mem_refresh_n_queued(c); + c->paused = 1; + } +} + + +static void mem_audio_unpause(til_audio_context_t *context) +{ + mem_audio_t *c = (mem_audio_t *)context; + + if (c->paused) { + c->paused = 0; + c->n_queued_start_ticks = til_ticks_now(); + } +} + + +static int mem_audio_queue(til_audio_context_t *context, int16_t *frames, int n_frames) +{ + mem_audio_t *c = (mem_audio_t *)context; + + mem_refresh_n_queued(c); + c->n_queued += n_frames; + + return 0; +} + + +static unsigned mem_audio_n_queued(til_audio_context_t *context) +{ + mem_audio_t *c = (mem_audio_t *)context; + + return mem_refresh_n_queued(c); +} + + +static int mem_audio_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup) +{ + if (res_setup) { + mem_audio_setup_t *setup; + + setup = til_setup_new(settings, sizeof(*setup), NULL, &mem_audio_ops); + if (!setup) + return -ENOMEM; + + *res_setup = &setup->til_setup; + } + + return 0; +} diff --git a/src/sdl_audio.c b/src/sdl_audio.c new file mode 100644 index 0000000..90fea35 --- /dev/null +++ b/src/sdl_audio.c @@ -0,0 +1,204 @@ +#define SDL_MAIN_HANDLED +#include +#include +#include +#include + +#include "til_audio.h" +#include "til_audio_context.h" +#include "til_settings.h" +#include "til_setup.h" + +/* sdl audio backend */ + +#define SDL_AUDIO_DEFAULT_SAMPLES 1024 + +typedef struct sdl_audio_setup_t { + til_setup_t til_setup; + + unsigned frames; +} sdl_audio_setup_t; + +typedef struct sdl_audio_t { + til_audio_context_t til_audio_context; + + SDL_AudioDeviceID dev; +} sdl_audio_t; + + +static int sdl_audio_init(til_setup_t *setup, til_audio_context_t **res_context); +static void sdl_audio_shutdown(til_audio_context_t *context); +static int sdl_audio_queue(til_audio_context_t *context, int16_t *frames, int n_frames); +static unsigned sdl_audio_n_queued(til_audio_context_t *context); +static void sdl_audio_drop(til_audio_context_t *context); +static void sdl_audio_pause(til_audio_context_t *context); +static void sdl_audio_unpause(til_audio_context_t *context); +static int sdl_audio_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup); + + +til_audio_ops_t sdl_audio_ops = { + .init = sdl_audio_init, + .shutdown = sdl_audio_shutdown, + .drop = sdl_audio_drop, + .pause = sdl_audio_pause, + .unpause = sdl_audio_unpause, + .queue = sdl_audio_queue, + .n_queued = sdl_audio_n_queued, + .setup = sdl_audio_setup, +}; + + +static int sdl_err_to_errno(int err) +{ + switch (err) { + case SDL_ENOMEM: + return ENOMEM; + case SDL_EFREAD: + case SDL_EFWRITE: + case SDL_EFSEEK: + return EIO; + case SDL_UNSUPPORTED: + return ENOTSUP; + default: + return EINVAL; + } +} + + +static int sdl_audio_init(til_setup_t *setup, til_audio_context_t **res_context) +{ + sdl_audio_setup_t *s = (sdl_audio_setup_t *)setup; + sdl_audio_t *c; + int r; + + assert(setup); + assert(res_context); + + c = til_audio_context_new(&sdl_audio_ops, sizeof(sdl_audio_t), setup); + if (!c) + return -ENOMEM; + + SDL_SetMainReady(); /* is it a problem (or necessary) for both sdl_fb and sdl_audio to do this? */ + r = SDL_InitSubSystem(SDL_INIT_AUDIO); + if (r < 0) { + free(c); + return -sdl_err_to_errno(r); + } + + { + SDL_AudioSpec aspec = { + .freq = 44100, + .format = AUDIO_S16, + .channels = 2, + .samples = s->frames, + }; + + c->dev = SDL_OpenAudioDevice(NULL, 0, &aspec, NULL, 0); + if (!c->dev) { + /* SDL_GetError only returns strings, we need an errno FIXME */ + free(c); + return -EPERM; + } + } + + *res_context = &c->til_audio_context; + + return 0; +} + + +static void sdl_audio_shutdown(til_audio_context_t *context) +{ + sdl_audio_t *c = (sdl_audio_t *)context; + + SDL_CloseAudioDevice(c->dev); +} + + +static void sdl_audio_drop(til_audio_context_t *context) +{ + sdl_audio_t *c = (sdl_audio_t *)context; + + SDL_ClearQueuedAudio(c->dev); +} + + +static void sdl_audio_pause(til_audio_context_t *context) +{ + sdl_audio_t *c = (sdl_audio_t *)context; + + SDL_PauseAudioDevice(c->dev, 1); +} + + +static void sdl_audio_unpause(til_audio_context_t *context) +{ + sdl_audio_t *c = (sdl_audio_t *)context; + + SDL_PauseAudioDevice(c->dev, 0); +} + + +static int sdl_audio_queue(til_audio_context_t *context, int16_t *frames, int n_frames) +{ + sdl_audio_t *c = (sdl_audio_t *)context; + + /* TODO FIXME: SDL_audio.h says this returns a negative error code on + * error, but that's not the same as a -errno, so directly returning + * from here isn't exactly appropriate. + */ + return SDL_QueueAudio(c->dev, frames, n_frames * (sizeof(*frames) * 2)); +} + + +static unsigned sdl_audio_n_queued(til_audio_context_t *context) +{ + sdl_audio_t *c = (sdl_audio_t *)context; + + return SDL_GetQueuedAudioSize(c->dev) / (sizeof(int16_t) * 2); +} + + +static int sdl_audio_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup) +{ + til_setting_t *frames; + const char *frames_values[] = { + "512", + "1024", + "2048", + "4096", + "8192", + NULL + }; + int r; + + r = til_settings_get_and_describe_setting(settings, + &(til_setting_spec_t){ + .name = "Audio frames buffered", + .key = "frames", + .regex = "[0-9]+", + .preferred = TIL_SETTINGS_STR(SDL_AUDIO_DEFAULT_SAMPLES), + .values = frames_values, + .annotations = NULL + }, + &frames, + res_setting, + res_desc); + if (r) + return r; + + if (res_setup) { + sdl_audio_setup_t *setup; + + setup = til_setup_new(settings, sizeof(*setup), NULL, &sdl_audio_ops); + if (!setup) + return -ENOMEM; + + if (sscanf(frames->value, "%u", &setup->frames) != 1) + return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, frames, res_setting, -EINVAL); + + *res_setup = &setup->til_setup; + } + + return 0; +} diff --git a/src/til_args.c b/src/til_args.c index b51f81b..c4dd95f 100644 --- a/src/til_args.c +++ b/src/til_args.c @@ -44,7 +44,9 @@ static int args_parse(int argc, const char *argv[], til_args_t *res_args, int *r /* this is intentionally being kept very simple, no new dependencies like getopt. */ for (int i = 1; i < argc; i++) { - if (!strncasecmp("--video=", argv[i], 8)) { + if (!strncasecmp("--audio=", argv[i], 8)) { + res_args->audio = &argv[i][8]; + } else if (!strncasecmp("--video=", argv[i], 8)) { res_args->video = &argv[i][8]; } else if (!strncasecmp("--module=", argv[i], 9)) { res_args->module = &argv[i][9]; @@ -92,6 +94,7 @@ int til_args_parse(int argc, const char *argv[], til_args_t *res_args) int til_args_help(FILE *out) { return fprintf(out, + " --audio audio settings\n" " --defaults use defaults for unspecified settings\n" " --go start rendering immediately upon fulfilling all required settings\n" " --help this help\n" diff --git a/src/til_args.h b/src/til_args.h index d49211e..fce5f20 100644 --- a/src/til_args.h +++ b/src/til_args.h @@ -5,6 +5,7 @@ typedef struct til_args_t { const char *module; + const char *audio; const char *video; const char *seed; const char *title; diff --git a/src/til_audio.c b/src/til_audio.c new file mode 100644 index 0000000..ed1ff9b --- /dev/null +++ b/src/til_audio.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include + +#include "til_audio.h" +#include "til_audio_context.h" + + +/* initialize audio via ops using setup, context stored in res_audio_context on success. + * returns -errno on error, 0 on success + * playback is left in a paused state, with an empty queue + */ +int til_audio_open(const til_audio_ops_t *ops, til_setup_t *setup, til_audio_context_t **res_audio_context) +{ + til_audio_context_t *c; + int r; + + assert(ops); + assert(ops->init); + assert(ops->queue); + assert(ops->n_queued); + assert(setup); + assert(res_audio_context); + + r = ops->init(setup, &c); + if (r < 0) + return r; + + *res_audio_context = c; + + return 0; +} + + +/* closes audio and frees context, + * callers are expected to use this and not til_audio_context_free(). + */ +void til_audio_shutdown(til_audio_context_t *audio_context) +{ + assert(audio_context); + assert(audio_context->ops); + + if (audio_context->ops->shutdown) + audio_context->ops->shutdown(audio_context); + + til_audio_context_free(audio_context); +} + + +/* install audio hooks to receive notification on events like seek/pause/unpause */ +int til_audio_set_hooks(til_audio_context_t *audio_context, til_audio_hooks_t *hooks, void *hooks_context) +{ + assert(audio_context); + assert(hooks); + + if (audio_context->hooks && audio_context->hooks != hooks) + return -EEXIST; + + audio_context->hooks = hooks; + audio_context->hooks_context = hooks_context; + + return 0; +} + + +/* remove audio hooks */ +int til_audio_unset_hooks(til_audio_context_t *audio_context, til_audio_hooks_t *hooks, void *hooks_context) +{ + assert(audio_context); + assert(hooks); + + /* this is kind of silly, but seems potentially useful in the defensive department */ + if (audio_context->hooks != hooks || + audio_context->hooks_context != hooks_context) + return -EINVAL; + + audio_context->hooks = NULL; + audio_context->hooks_context = NULL; + + return 0; +} + + +/* seek to an absolute ticks, playback is left in a paused state with an empty queue */ +void til_audio_seek(til_audio_context_t *audio_context, unsigned ticks) +{ + assert(audio_context); + + if (audio_context->ops->pause) + audio_context->ops->pause(audio_context); + + if (audio_context->ops->drop) + audio_context->ops->drop(audio_context); + + if (audio_context->hooks && audio_context->hooks->seeked) + audio_context->hooks->seeked(audio_context->hooks_context, audio_context, ticks); +} + + +/* queue n_frames frames to audio_context, + * returns 0 on success, -errno on error + */ +int til_audio_queue(til_audio_context_t *audio_context, int16_t *frames, int n_frames) +{ + assert(audio_context); + assert(frames); + assert(n_frames > 0); + + return audio_context->ops->queue(audio_context, frames, n_frames); +} + + +/* query how many frames are currently queued + * returns 0 on success, -errno on error + */ +unsigned til_audio_n_queued(til_audio_context_t *audio_context) +{ + assert(audio_context); + + return audio_context->ops->n_queued(audio_context); +} + + +/* pause the underlying audio playback, queue is left as-is, should be idempotent */ +void til_audio_pause(til_audio_context_t *audio_context) +{ + assert(audio_context); + + if (audio_context->ops->pause) + audio_context->ops->pause(audio_context); + + if (audio_context->hooks && audio_context->hooks->paused) + audio_context->hooks->paused(audio_context->hooks_context, audio_context); +} + + +/* unpause the underlying audio playback, queue is left as-is, should be idempotent */ +void til_audio_unpause(til_audio_context_t *audio_context) +{ + assert(audio_context); + + if (audio_context->ops->unpause) + audio_context->ops->unpause(audio_context); + + if (audio_context->hooks && audio_context->hooks->unpaused) + audio_context->hooks->unpaused(audio_context->hooks_context, audio_context); +} diff --git a/src/til_audio.h b/src/til_audio.h new file mode 100644 index 0000000..f5640b6 --- /dev/null +++ b/src/til_audio.h @@ -0,0 +1,39 @@ +#ifndef _TIL_AUDIO_H +#define _TIL_AUDIO_H + +#include + +typedef struct til_settings_t til_settings_t; +typedef struct til_setting_t til_setting_t; +typedef struct til_setting_desc_t til_setting_desc_t; +typedef struct til_setup_t til_setup_t; +typedef struct til_audio_context_t til_audio_context_t; + +typedef struct til_audio_ops_t { + int (*setup)(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup); + int (*init)(til_setup_t *setup, til_audio_context_t **res_audio_context); + void (*shutdown)(til_audio_context_t *audio_context); + void (*drop)(til_audio_context_t *audio_context); + void (*pause)(til_audio_context_t *audio_context); + void (*unpause)(til_audio_context_t *audio_context); + int (*queue)(til_audio_context_t *audio_context, int16_t *frames, int n_frames); + unsigned (*n_queued)(til_audio_context_t *audio_context); +} til_audio_ops_t; + +typedef struct til_audio_hooks_t { + void (*seeked)(void *hooks_context, const til_audio_context_t *audio_context, unsigned ticks); + void (*paused)(void *hooks_context, const til_audio_context_t *audio_context); + void (*unpaused)(void *hooks_context, const til_audio_context_t *audio_context); +} til_audio_hooks_t; + +int til_audio_open(const til_audio_ops_t *ops, til_setup_t *setup, til_audio_context_t **res_audio_context); +void til_audio_shutdown(til_audio_context_t *audio_context); +int til_audio_set_hooks(til_audio_context_t *audio_context, til_audio_hooks_t *hooks, void *hooks_context); +int til_audio_unset_hooks(til_audio_context_t *audio_context, til_audio_hooks_t *hooks, void *hooks_context); +int til_audio_queue(til_audio_context_t *audio_context, int16_t *frames, int n_frames); +unsigned til_audio_n_queued(til_audio_context_t *audio_context); +void til_audio_seek(til_audio_context_t *audio_context, unsigned ticks); +void til_audio_pause(til_audio_context_t *audio_context); +void til_audio_unpause(til_audio_context_t *audio_context); + +#endif diff --git a/src/til_audio_context.c b/src/til_audio_context.c new file mode 100644 index 0000000..63cc6f5 --- /dev/null +++ b/src/til_audio_context.c @@ -0,0 +1,35 @@ +#include + +#include "til_audio.h" +#include "til_audio_context.h" +#include "til_setup.h" + +/* XXX: this isn't intended to be used by anything other than til_audio.[ch], + * use til_audio_{init,shutdown}(). This is purely in service of that. + */ + +void * til_audio_context_new(const til_audio_ops_t *ops, size_t size, til_setup_t *setup) +{ + til_audio_context_t *c; + + c = calloc(1, size); + if (!c) + return NULL; + + c->setup = til_setup_ref(setup); + c->ops = ops; + + return c; +} + + +til_audio_context_t * til_audio_context_free(til_audio_context_t *audio_context) +{ + if (!audio_context) + return NULL; + + til_setup_free(audio_context->setup); + free(audio_context); + + return NULL; +} diff --git a/src/til_audio_context.h b/src/til_audio_context.h new file mode 100644 index 0000000..8efdbac --- /dev/null +++ b/src/til_audio_context.h @@ -0,0 +1,21 @@ +#ifndef _TIL_AUDIO_CONTEXT_H +#define _TIL_AUDIO_CONTEXT_H + +/* XXX: this isn't intended to be used outside of til_audio.[ch] */ + +#include + +typedef struct til_setup_t til_setup_t; +typedef struct til_audio_ops_t til_audio_ops_t; + +typedef struct til_audio_context_t { + til_setup_t *setup; + const til_audio_ops_t *ops; + const til_audio_hooks_t *hooks; + void *hooks_context; +} til_audio_context_t; + +void * til_audio_context_new(const til_audio_ops_t *ops, size_t size, til_setup_t *setup); +til_audio_context_t * til_audio_context_free(til_audio_context_t *audio_context); + +#endif -- cgit v1.2.1