summaryrefslogtreecommitdiff
path: root/modules/auloop/auloop.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/auloop/auloop.c')
-rw-r--r--modules/auloop/auloop.c370
1 files changed, 370 insertions, 0 deletions
diff --git a/modules/auloop/auloop.c b/modules/auloop/auloop.c
new file mode 100644
index 0000000..821c5e6
--- /dev/null
+++ b/modules/auloop/auloop.c
@@ -0,0 +1,370 @@
+/**
+ * @file auloop.c Audio loop
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/* Configurable items */
+#define PTIME 20
+
+
+/** Audio Loop */
+struct audio_loop {
+ uint32_t index;
+ struct aubuf *ab;
+ struct ausrc_st *ausrc;
+ struct auplay_st *auplay;
+ const struct aucodec *ac;
+ struct auenc_state *enc;
+ struct audec_state *dec;
+ int16_t *sampv;
+ size_t sampc;
+ struct tmr tmr;
+ uint32_t srate;
+ uint32_t ch;
+
+ uint32_t n_read;
+ uint32_t n_write;
+};
+
+static const struct {
+ uint32_t srate;
+ uint32_t ch;
+} configv[] = {
+ { 8000, 1},
+ {16000, 1},
+ {32000, 1},
+ {48000, 1},
+ { 8000, 2},
+ {16000, 2},
+ {32000, 2},
+ {48000, 2},
+};
+
+static struct audio_loop *gal = NULL;
+static char aucodec[64];
+
+
+static void auloop_destructor(void *arg)
+{
+ struct audio_loop *al = arg;
+
+ tmr_cancel(&al->tmr);
+ mem_deref(al->ausrc);
+ mem_deref(al->auplay);
+ mem_deref(al->sampv);
+ mem_deref(al->ab);
+ mem_deref(al->enc);
+ mem_deref(al->dec);
+}
+
+
+static void print_stats(struct audio_loop *al)
+{
+ double rw_ratio = 0.0;
+
+ if (al->n_write)
+ rw_ratio = 1.0 * al->n_read / al->n_write;
+
+ (void)re_fprintf(stderr, "\r%uHz %dch "
+ " n_read=%u n_write=%u rw_ratio=%.2f",
+ al->srate, al->ch,
+ al->n_read, al->n_write, rw_ratio);
+
+ if (str_isset(aucodec))
+ (void)re_fprintf(stderr, " codec='%s'", aucodec);
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct audio_loop *al = arg;
+
+ tmr_start(&al->tmr, 100, tmr_handler, al);
+ print_stats(al);
+}
+
+
+static int codec_read(struct audio_loop *al, int16_t *sampv, size_t sampc)
+{
+ uint8_t x[2560];
+ size_t xlen = sizeof(x);
+ int err;
+
+ aubuf_read_samp(al->ab, al->sampv, al->sampc);
+
+ err = al->ac->ench(al->enc, x, &xlen, al->sampv, al->sampc);
+ if (err)
+ goto out;
+
+ err = al->ac->dech(al->dec, sampv, &sampc, x, xlen);
+ if (err)
+ goto out;
+
+ out:
+
+ return err;
+}
+
+
+static void read_handler(const uint8_t *buf, size_t sz, void *arg)
+{
+ struct audio_loop *al = arg;
+ int err;
+
+ ++al->n_read;
+
+ err = aubuf_write(al->ab, buf, sz);
+ if (err) {
+ warning("auloop: aubuf_write: %m\n", err);
+ }
+}
+
+
+static bool write_handler(uint8_t *buf, size_t sz, void *arg)
+{
+ struct audio_loop *al = arg;
+ int err;
+
+ ++al->n_write;
+
+ /* read from beginning */
+ if (al->ac) {
+ err = codec_read(al, (void *)buf, sz/2);
+ if (err) {
+ warning("auloop: codec_read error "
+ "on %u bytes (%m)\n", sz, err);
+ }
+ }
+ else {
+ aubuf_read(al->ab, buf, sz);
+ }
+
+ return true;
+}
+
+
+static void error_handler(int err, const char *str, void *arg)
+{
+ (void)arg;
+ warning("auloop: ausrc error: %m (%s)\n", err, str);
+ gal = mem_deref(gal);
+}
+
+
+static void start_codec(struct audio_loop *al, const char *name)
+{
+ struct auenc_param prm = {PTIME};
+ int err;
+
+ al->ac = aucodec_find(name,
+ configv[al->index].srate,
+ configv[al->index].ch);
+ if (!al->ac) {
+ warning("auloop: could not find codec: %s\n", name);
+ return;
+ }
+
+ if (al->ac->encupdh) {
+ err = al->ac->encupdh(&al->enc, al->ac, &prm, NULL);
+ if (err) {
+ warning("auloop: encoder update failed: %m\n", err);
+ }
+ }
+
+ if (al->ac->decupdh) {
+ err = al->ac->decupdh(&al->dec, al->ac, NULL);
+ if (err) {
+ warning("auloop: decoder update failed: %m\n", err);
+ }
+ }
+}
+
+
+static int auloop_reset(struct audio_loop *al)
+{
+ struct auplay_prm auplay_prm;
+ struct ausrc_prm ausrc_prm;
+ const struct config *cfg = conf_config();
+ int err;
+
+ if (!cfg)
+ return ENOENT;
+
+ /* Optional audio codec */
+ if (str_isset(aucodec))
+ start_codec(al, aucodec);
+
+ /* audio player/source must be stopped first */
+ al->auplay = mem_deref(al->auplay);
+ al->ausrc = mem_deref(al->ausrc);
+
+ al->sampv = mem_deref(al->sampv);
+ al->ab = mem_deref(al->ab);
+
+ al->srate = configv[al->index].srate;
+ al->ch = configv[al->index].ch;
+
+ if (str_isset(aucodec)) {
+ al->sampc = al->srate * al->ch * PTIME / 1000;
+ al->sampv = mem_alloc(al->sampc * 2, NULL);
+ if (!al->sampv)
+ return ENOMEM;
+ }
+
+ info("Audio-loop: %uHz, %dch\n", al->srate, al->ch);
+
+ err = aubuf_alloc(&al->ab, 320, 0);
+ if (err)
+ return err;
+
+ auplay_prm.fmt = AUFMT_S16LE;
+ auplay_prm.srate = al->srate;
+ auplay_prm.ch = al->ch;
+ auplay_prm.ptime = PTIME;
+ err = auplay_alloc(&al->auplay, cfg->audio.play_mod, &auplay_prm,
+ cfg->audio.play_dev, write_handler, al);
+ if (err) {
+ warning("auloop: auplay %s,%s failed: %m\n",
+ cfg->audio.play_mod, cfg->audio.play_dev,
+ err);
+ return err;
+ }
+
+ ausrc_prm.fmt = AUFMT_S16LE;
+ ausrc_prm.srate = al->srate;
+ ausrc_prm.ch = al->ch;
+ ausrc_prm.ptime = PTIME;
+ err = ausrc_alloc(&al->ausrc, NULL, cfg->audio.src_mod,
+ &ausrc_prm, cfg->audio.src_dev,
+ read_handler, error_handler, al);
+ if (err) {
+ warning("auloop: ausrc %s,%s failed: %m\n", cfg->audio.src_mod,
+ cfg->audio.src_dev, err);
+ return err;
+ }
+
+ return err;
+}
+
+
+static int audio_loop_alloc(struct audio_loop **alp)
+{
+ struct audio_loop *al;
+ int err;
+
+ al = mem_zalloc(sizeof(*al), auloop_destructor);
+ if (!al)
+ return ENOMEM;
+
+ tmr_start(&al->tmr, 100, tmr_handler, al);
+
+ err = auloop_reset(al);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(al);
+ else
+ *alp = al;
+
+ return err;
+}
+
+
+static int audio_loop_cycle(struct audio_loop *al)
+{
+ int err;
+
+ ++al->index;
+
+ if (al->index >= ARRAY_SIZE(configv)) {
+ gal = mem_deref(gal);
+ info("\nAudio-loop stopped\n");
+ return 0;
+ }
+
+ err = auloop_reset(al);
+ if (err)
+ return err;
+
+ info("\nAudio-loop started: %uHz, %dch\n", al->srate, al->ch);
+
+ return 0;
+}
+
+
+/**
+ * Start the audio loop (for testing)
+ */
+static int auloop_start(struct re_printf *pf, void *arg)
+{
+ int err;
+
+ (void)pf;
+ (void)arg;
+
+ if (gal) {
+ err = audio_loop_cycle(gal);
+ if (err) {
+ warning("auloop: loop cycle: %m\n", err);
+ }
+ }
+ else {
+ err = audio_loop_alloc(&gal);
+ if (err) {
+ warning("auloop: alloc failed %m\n", err);
+ }
+ }
+
+ return err;
+}
+
+
+static int auloop_stop(struct re_printf *pf, void *arg)
+{
+ (void)arg;
+
+ if (gal) {
+ (void)re_hprintf(pf, "audio-loop stopped\n");
+ gal = mem_deref(gal);
+ }
+
+ return 0;
+}
+
+
+static const struct cmd cmdv[] = {
+ {'a', 0, "Start audio-loop", auloop_start },
+ {'A', 0, "Stop audio-loop", auloop_stop },
+};
+
+
+static int module_init(void)
+{
+ conf_get_str(conf_cur(), "auloop_codec", aucodec, sizeof(aucodec));
+
+ return cmd_register(cmdv, ARRAY_SIZE(cmdv));
+}
+
+
+static int module_close(void)
+{
+ auloop_stop(NULL, NULL);
+ cmd_unregister(cmdv);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(auloop) = {
+ "auloop",
+ "application",
+ module_init,
+ module_close,
+};