aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDenis 'GNUtoo' Carikli <GNUtoo@no-log.org>2012-01-09 21:17:38 +0100
committerDenis 'GNUtoo' Carikli <GNUtoo@no-log.org>2012-01-25 19:45:55 +0100
commit51a0667d1d5dd0500f77051f988bd888889c3254 (patch)
tree22bc9e8c565990294fe1a27232e88dfae22cd2f7
parent5a6db3fa94e521dab1f1370f4274ecb19451e7f7 (diff)
downloadcornucopia-51a0667d1d5dd0500f77051f988bd888889c3254.tar.gz
cornucopia-51a0667d1d5dd0500f77051f988bd888889c3254.tar.bz2
cornucopia-51a0667d1d5dd0500f77051f988bd888889c3254.zip
fsoaudiod: import files for making an alsaloop based alsa forwarder plugin for the om-gta04
Signed-off-by: Denis 'GNUtoo' Carikli <GNUtoo@no-log.org>
-rw-r--r--fsoaudiod/configure.ac1
-rw-r--r--fsoaudiod/src/plugins/Makefile.am2
-rw-r--r--fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/Makefile.am60
-rw-r--r--fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/alsaloop.c923
-rw-r--r--fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/alsaloop.h218
-rw-r--r--fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/control.c424
-rw-r--r--fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/pcmjob.c1925
-rw-r--r--fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/plugin.vala97
8 files changed, 3650 insertions, 0 deletions
diff --git a/fsoaudiod/configure.ac b/fsoaudiod/configure.ac
index 584fdaea..7606ca0b 100644
--- a/fsoaudiod/configure.ac
+++ b/fsoaudiod/configure.ac
@@ -135,6 +135,7 @@ AC_CONFIG_FILES([
src/alsa_hooks/session/Makefile
src/plugins/Makefile
src/plugins/gsmvoice_alsa_cmtspeechdata/Makefile
+ src/plugins/gsmvoice_alsa_forwarder/Makefile
src/plugins/manager/Makefile
src/plugins/router_palmpre/Makefile
src/plugins/router_alsa/Makefile
diff --git a/fsoaudiod/src/plugins/Makefile.am b/fsoaudiod/src/plugins/Makefile.am
index 8cfe8c89..9aa7ad73 100644
--- a/fsoaudiod/src/plugins/Makefile.am
+++ b/fsoaudiod/src/plugins/Makefile.am
@@ -9,6 +9,8 @@ SUBDIRS = \
router_palmpre \
router_alsa \
\
+ gsmvoice_alsa_forwarder \
+ \
streamcontrol_alsa \
sessionpolicy_default \
\
diff --git a/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/Makefile.am b/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/Makefile.am
new file mode 100644
index 00000000..578c5138
--- /dev/null
+++ b/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/Makefile.am
@@ -0,0 +1,60 @@
+include $(top_srcdir)/Makefile.decl
+NULL =
+
+AM_CFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/vapi \
+ $(FSO_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(GEE_CFLAGS) \
+ $(NULL)
+
+noinst_PROGRAMS = $(TEST_PROGS)
+
+progs_ldadd = \
+ $(FSO_GLIB_LIBS) \
+ $(FSO_LIBS) \
+ $(GLIB_LIBS) \
+ $(GEE_LIBS) \
+ $(top_srcdir)/src/lib/libfsoaudio.la
+
+VALAC_ARGS = \
+ --basedir $(top_srcdir) \
+ --vapidir $(top_srcdir)/src/lib \
+ --vapidir $(top_srcdir)/vapi \
+ --pkg glib-2.0 \
+ --pkg gee-1.0 \
+ --pkg fso-glib-1.0 \
+ --pkg fsoframework-2.0 \
+ --pkg fsoaudio-2.0 \
+ --pkg alsa
+
+if WANT_DEBUG
+VALAC_ARGS += -g -D DEBUG
+AM_CFLAGS += -ggdb -O0
+endif
+
+#
+# plugin
+#
+modlibexecdir = $(libdir)/cornucopia/modules/fsoaudio
+modlibexec_LTLIBRARIES = gsmvoice_alsa_forwarder.la
+gsmvoice_alsa_forwarder_la_SOURCES = plugin.c
+gsmvoice_alsa_forwarder_la_VALASOURCES = plugin.vala
+$(gsmvoice_alsa_forwarder_la_SOURCES): $(gsmvoice_alsa_forwarder_la_VALASOURCES)
+ $(VALAC) -C $(VALAC_ARGS) $^
+ touch $@
+gsmvoice_alsa_forwarder_la_LIBADD = $(progs_ldadd)
+gsmvoice_alsa_forwarder_la_LDFLAGS = -no-undefined -module -avoid-version
+gsmvoice_alsa_forwarder_la_LIBTOOLFLAGS = --tag=disable-static
+
+CLEANFILES = \
+ *.c \
+ *.h \
+ *.la \
+ *.lo \
+ $(NULL)
+
+MAINTAINERCLEANFILES = \
+ Makefile.in \
+ $(NULL)
diff --git a/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/alsaloop.c b/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/alsaloop.c
new file mode 100644
index 00000000..8710dd19
--- /dev/null
+++ b/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/alsaloop.c
@@ -0,0 +1,923 @@
+/*
+ * A simple PCM loopback utility with adaptive sample rate support
+ *
+ * Author: Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sched.h>
+#include <errno.h>
+#include <getopt.h>
+#include <alsa/asoundlib.h>
+#include <sys/time.h>
+#include <math.h>
+#include <pthread.h>
+#include <syslog.h>
+#include <sys/signal.h>
+#include "alsaloop.h"
+
+struct loopback_thread {
+ int threaded;
+ pthread_t thread;
+ int exitcode;
+ struct loopback **loopbacks;
+ int loopbacks_count;
+ snd_output_t *output;
+};
+
+int quit = 0;
+int verbose = 0;
+int workarounds = 0;
+int daemonize = 0;
+int use_syslog = 0;
+struct loopback **loopbacks = NULL;
+int loopbacks_count = 0;
+char **my_argv = NULL;
+int my_argc = 0;
+struct loopback_thread *threads;
+int threads_count = 0;
+pthread_t main_job;
+int arg_default_xrun = 0;
+int arg_default_wake = 0;
+
+static void my_exit(struct loopback_thread *thread, int exitcode)
+{
+ int i;
+
+ for (i = 0; i < thread->loopbacks_count; i++)
+ pcmjob_done(thread->loopbacks[i]);
+ if (thread->threaded) {
+ thread->exitcode = exitcode;
+ pthread_exit(0);
+ }
+ exit(exitcode);
+}
+
+static int create_loopback_handle(struct loopback_handle **_handle,
+ const char *device,
+ const char *ctldev,
+ const char *id)
+{
+ char idbuf[1024];
+ struct loopback_handle *handle;
+
+ handle = calloc(1, sizeof(*handle));
+ if (handle == NULL)
+ return -ENOMEM;
+ if (device == NULL)
+ device = "hw:0,0";
+ handle->device = strdup(device);
+ if (handle->device == NULL)
+ return -ENOMEM;
+ if (ctldev) {
+ handle->ctldev = strdup(ctldev);
+ if (handle->ctldev == NULL)
+ return -ENOMEM;
+ } else {
+ handle->ctldev = NULL;
+ }
+ snprintf(idbuf, sizeof(idbuf)-1, "%s %s", id, device);
+ idbuf[sizeof(idbuf)-1] = '\0';
+ handle->id = strdup(idbuf);
+ handle->access = SND_PCM_ACCESS_RW_INTERLEAVED;
+ handle->format = SND_PCM_FORMAT_S16_LE;
+ handle->rate = handle->rate_req = 48000;
+ handle->channels = 2;
+ handle->resample = 0;
+ *_handle = handle;
+ return 0;
+}
+
+static int create_loopback(struct loopback **_handle,
+ struct loopback_handle *play,
+ struct loopback_handle *capt,
+ snd_output_t *output)
+{
+ struct loopback *handle;
+
+ handle = calloc(1, sizeof(*handle));
+ if (handle == NULL)
+ return -ENOMEM;
+ handle->play = play;
+ handle->capt = capt;
+ play->loopback = handle;
+ capt->loopback = handle;
+ handle->latency_req = 0;
+ handle->latency_reqtime = 10000;
+ handle->loop_time = ~0UL;
+ handle->loop_limit = ~0ULL;
+ handle->output = output;
+ handle->state = output;
+#ifdef USE_SAMPLERATE
+ handle->src_enable = 1;
+ handle->src_converter_type = SRC_SINC_BEST_QUALITY;
+#endif
+ *_handle = handle;
+ return 0;
+}
+
+static void set_loop_time(struct loopback *loop, unsigned long loop_time)
+{
+ loop->loop_time = loop_time;
+ loop->loop_limit = loop->capt->rate * loop_time;
+}
+
+static void setscheduler(void)
+{
+ struct sched_param sched_param;
+
+ if (sched_getparam(0, &sched_param) < 0) {
+ logit(LOG_WARNING, "Scheduler getparam failed.\n");
+ return;
+ }
+ sched_param.sched_priority = sched_get_priority_max(SCHED_RR);
+ if (!sched_setscheduler(0, SCHED_RR, &sched_param)) {
+ if (verbose)
+ logit(LOG_WARNING, "Scheduler set to Round Robin with priority %i\n", sched_param.sched_priority);
+ return;
+ }
+ if (verbose)
+ logit(LOG_INFO, "!!!Scheduler set to Round Robin with priority %i FAILED!\n", sched_param.sched_priority);
+}
+
+void help(void)
+{
+ int k;
+ printf(
+"Usage: alsaloop [OPTION]...\n\n"
+"-h,--help help\n"
+"-g,--config configuration file (one line = one job specified)\n"
+"-d,--daemonize daemonize the main process and use syslog for errors\n"
+"-P,--pdevice playback device\n"
+"-C,--cdevice capture device\n"
+"-X,--pctl playback ctl device\n"
+"-Y,--cctl capture ctl device\n"
+"-l,--latency requested latency in frames\n"
+"-t,--tlatency requested latency in usec (1/1000000sec)\n"
+"-f,--format sample format\n"
+"-c,--channels channels\n"
+"-r,--rate rate\n"
+"-n,--resample resample in alsa-lib\n"
+"-A,--samplerate use converter (0=sincbest,1=sincmedium,2=sincfastest,\n"
+" 3=zerohold,4=linear)\n"
+"-B,--buffer buffer size in frames\n"
+"-E,--period period size in frames\n"
+"-s,--seconds duration of loop in seconds\n"
+"-b,--nblock non-block mode (very early process wakeup)\n"
+"-S,--sync sync mode(0=none,1=simple,2=captshift,3=playshift,4=samplerate,\n"
+" 5=auto)\n"
+"-a,--slave stream parameters slave mode (0=auto, 1=on, 2=off)\n"
+"-T,--thread thread number (-1 = create unique)\n"
+"-m,--mixer redirect mixer, argument is:\n"
+" SRC_SLAVE_ID(PLAYBACK)[@DST_SLAVE_ID(CAPTURE)]\n"
+"-O,--ossmixer rescan and redirect oss mixer, argument is:\n"
+" ALSA_ID@OSS_ID (for example: \"Master@VOLUME\")\n"
+"-e,--effect apply an effect (bandpass filter sweep)\n"
+"-v,--verbose verbose mode (more -v means more verbose)\n"
+"-w,--workaround use workaround (serialopen)\n"
+"-U,--xrun xrun profiling\n"
+"-W,--wake process wake timeout in ms\n"
+);
+ printf("\nRecognized sample formats are:");
+ for (k = 0; k < SND_PCM_FORMAT_LAST; ++k) {
+ const char *s = snd_pcm_format_name(k);
+ if (s)
+ printf(" %s", s);
+ }
+ printf("\n\n");
+ printf(
+"Tip #1 (usable 500ms latency, good CPU usage, superb xrun prevention):\n"
+" alsaloop -t 500000\n"
+"Tip #2 (superb 1ms latency, but heavy CPU usage):\n"
+" alsaloop -t 1000\n"
+);
+}
+
+static long timediff(struct timeval t1, struct timeval t2)
+{
+ signed long l;
+
+ t1.tv_sec -= t2.tv_sec;
+ l = (signed long) t1.tv_usec - (signed long) t2.tv_usec;
+ if (l < 0) {
+ t1.tv_sec--;
+ l = 1000000 + l;
+ l %= 1000000;
+ }
+ return (t1.tv_sec * 1000000) + l;
+}
+
+static void add_loop(struct loopback *loop)
+{
+ loopbacks = realloc(loopbacks, (loopbacks_count + 1) *
+ sizeof(struct loopback *));
+ if (loopbacks == NULL) {
+ logit(LOG_CRIT, "No enough memory\n");
+ exit(EXIT_FAILURE);
+ }
+ loopbacks[loopbacks_count++] = loop;
+}
+
+static int init_mixer_control(struct loopback_control *control,
+ char *id)
+{
+ int err;
+
+ err = snd_ctl_elem_id_malloc(&control->id);
+ if (err < 0)
+ return err;
+ err = snd_ctl_elem_info_malloc(&control->info);
+ if (err < 0)
+ return err;
+ err = snd_ctl_elem_value_malloc(&control->value);
+ if (err < 0)
+ return err;
+ err = control_parse_id(id, control->id);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+static int add_mixers(struct loopback *loop,
+ char **mixers,
+ int mixers_count)
+{
+ struct loopback_mixer *mixer, *last = NULL;
+ char *str1;
+ int err;
+
+ while (mixers_count > 0) {
+ mixer = calloc(1, sizeof(*mixer));
+ if (mixer == NULL)
+ return -ENOMEM;
+ if (last)
+ last->next = mixer;
+ else
+ loop->controls = mixer;
+ last = mixer;
+ str1 = strchr(*mixers, '@');
+ if (str1)
+ *str1 = '\0';
+ err = init_mixer_control(&mixer->src, *mixers);
+ if (err < 0) {
+ logit(LOG_CRIT, "Wrong mixer control ID syntax '%s'\n", *mixers);
+ return -EINVAL;
+ }
+ err = init_mixer_control(&mixer->dst, str1 ? str1 + 1 : *mixers);
+ if (err < 0) {
+ logit(LOG_CRIT, "Wrong mixer control ID syntax '%s'\n", str1 ? str1 + 1 : *mixers);
+ return -EINVAL;
+ }
+ if (str1)
+ *str1 = '@';
+ mixers++;
+ mixers_count--;
+ }
+ return 0;
+}
+
+static int add_oss_mixers(struct loopback *loop,
+ char **mixers,
+ int mixers_count)
+{
+ struct loopback_ossmixer *mixer, *last = NULL;
+ char *str1, *str2;
+
+ while (mixers_count > 0) {
+ mixer = calloc(1, sizeof(*mixer));
+ if (mixer == NULL)
+ return -ENOMEM;
+ if (last)
+ last->next = mixer;
+ else
+ loop->oss_controls = mixer;
+ last = mixer;
+ str1 = strchr(*mixers, ',');
+ if (str1)
+ *str1 = '\0';
+ str2 = strchr(str1 ? str1 + 1 : *mixers, '@');
+ if (str2)
+ *str2 = '\0';
+ mixer->alsa_id = strdup(*mixers);
+ if (str1)
+ mixer->alsa_index = atoi(str1);
+ mixer->oss_id = strdup(str2 ? str2 + 1 : *mixers);
+ if (mixer->alsa_id == NULL || mixer->oss_id == NULL) {
+ logit(LOG_CRIT, "Not enough memory");
+ return -ENOMEM;
+ }
+ if (str1)
+ *str1 = ',';
+ if (str2)
+ *str2 = ',';
+ mixers++;
+ mixers_count--;
+ }
+ return 0;
+}
+
+static int parse_config_file(const char *file, snd_output_t *output);
+
+static int parse_config(int argc, char *argv[], snd_output_t *output,
+ int cmdline)
+{
+ struct option long_option[] =
+ {
+ {"help", 0, NULL, 'h'},
+ {"config", 1, NULL, 'g'},
+ {"daemonize", 0, NULL, 'd'},
+ {"pdevice", 1, NULL, 'P'},
+ {"cdevice", 1, NULL, 'C'},
+ {"pctl", 1, NULL, 'X'},
+ {"cctl", 1, NULL, 'Y'},
+ {"latency", 1, NULL, 'l'},
+ {"tlatency", 1, NULL, 't'},
+ {"format", 1, NULL, 'f'},
+ {"channels", 1, NULL, 'c'},
+ {"rate", 1, NULL, 'r'},
+ {"buffer", 1, NULL, 'B'},
+ {"period", 1, NULL, 'E'},
+ {"seconds", 1, NULL, 's'},
+ {"nblock", 0, NULL, 'b'},
+ {"effect", 0, NULL, 'e'},
+ {"verbose", 0, NULL, 'v'},
+ {"resample", 0, NULL, 'n'},
+ {"samplerate", 1, NULL, 'A'},
+ {"sync", 1, NULL, 'S'},
+ {"slave", 1, NULL, 'a'},
+ {"thread", 1, NULL, 'T'},
+ {"mixer", 1, NULL, 'm'},
+ {"ossmixer", 1, NULL, 'O'},
+ {"workaround", 1, NULL, 'w'},
+ {"xrun", 0, NULL, 'U'},
+ {NULL, 0, NULL, 0},
+ };
+ int err, morehelp;
+ char *arg_config = NULL;
+ char *arg_pdevice = NULL;
+ char *arg_cdevice = NULL;
+ char *arg_pctl = NULL;
+ char *arg_cctl = NULL;
+ unsigned int arg_latency_req = 0;
+ unsigned int arg_latency_reqtime = 10000;
+ snd_pcm_format_t arg_format = SND_PCM_FORMAT_S16_LE;
+ unsigned int arg_channels = 2;
+ unsigned int arg_rate = 48000;
+ snd_pcm_uframes_t arg_buffer_size = 0;
+ snd_pcm_uframes_t arg_period_size = 0;
+ unsigned long arg_loop_time = ~0UL;
+ int arg_nblock = 0;
+ int arg_effect = 0;
+ int arg_resample = 0;
+ int arg_samplerate = SRC_SINC_FASTEST + 1;
+ int arg_sync = SYNC_TYPE_AUTO;
+ int arg_slave = SLAVE_TYPE_AUTO;
+ int arg_thread = 0;
+ struct loopback *loop = NULL;
+ char *arg_mixers[MAX_MIXERS];
+ int arg_mixers_count = 0;
+ char *arg_ossmixers[MAX_MIXERS];
+ int arg_ossmixers_count = 0;
+ int arg_xrun = arg_default_xrun;
+ int arg_wake = arg_default_wake;
+
+ morehelp = 0;
+ while (1) {
+ int c;
+ if ((c = getopt_long(argc, argv,
+ "hdg:P:C:X:Y:l:t:F:f:c:r:s:benvA:S:a:m:T:O:w:UW:",
+ long_option, NULL)) < 0)
+ break;
+ switch (c) {
+ case 'h':
+ morehelp++;
+ break;
+ case 'g':
+ arg_config = strdup(optarg);
+ break;
+ case 'd':
+ daemonize = 1;
+ use_syslog = 1;
+ openlog("alsaloop", LOG_NDELAY|LOG_PID, LOG_DAEMON);
+ break;
+ case 'P':
+ arg_pdevice = strdup(optarg);
+ break;
+ case 'C':
+ arg_cdevice = strdup(optarg);
+ break;
+ case 'X':
+ arg_pctl = strdup(optarg);
+ break;
+ case 'Y':
+ arg_cctl = strdup(optarg);
+ break;
+ case 'l':
+ err = atoi(optarg);
+ arg_latency_req = err >= 4 ? err : 4;
+ break;
+ case 't':
+ err = atoi(optarg);
+ arg_latency_reqtime = err >= 500 ? err : 500;
+ break;
+ case 'f':
+ arg_format = snd_pcm_format_value(optarg);
+ if (arg_format == SND_PCM_FORMAT_UNKNOWN) {
+ logit(LOG_WARNING, "Unknown format, setting to default S16_LE\n");
+ arg_format = SND_PCM_FORMAT_S16_LE;
+ }
+ break;
+ case 'c':
+ err = atoi(optarg);
+ arg_channels = err >= 1 && err < 1024 ? err : 1;
+ break;
+ case 'r':
+ err = atoi(optarg);
+ arg_rate = err >= 4000 && err < 200000 ? err : 44100;
+ break;
+ case 'B':
+ err = atoi(optarg);
+ arg_buffer_size = err >= 32 && err < 200000 ? err : 0;
+ break;
+ case 'E':
+ err = atoi(optarg);
+ arg_period_size = err >= 32 && err < 200000 ? err : 0;
+ break;
+ case 's':
+ err = atoi(optarg);
+ arg_loop_time = err >= 1 && err <= 100000 ? err : 30;
+ break;
+ case 'b':
+ arg_nblock = 1;
+ break;
+ case 'e':
+ arg_effect = 1;
+ break;
+ case 'n':
+ arg_resample = 1;
+ break;
+ case 'A':
+ if (strcasecmp(optarg, "sincbest") == 0)
+ arg_samplerate = SRC_SINC_BEST_QUALITY;
+ else if (strcasecmp(optarg, "sincmedium") == 0)
+ arg_samplerate = SRC_SINC_MEDIUM_QUALITY;
+ else if (strcasecmp(optarg, "sincfastest") == 0)
+ arg_samplerate = SRC_SINC_FASTEST;
+ else if (strcasecmp(optarg, "zerohold") == 0)
+ arg_samplerate = SRC_ZERO_ORDER_HOLD;
+ else if (strcasecmp(optarg, "linear") == 0)
+ arg_samplerate = SRC_LINEAR;
+ else
+ arg_samplerate = atoi(optarg);
+ if (arg_samplerate < 0 || arg_samplerate > SRC_LINEAR)
+ arg_sync = SRC_SINC_FASTEST;
+ arg_samplerate += 1;
+ break;
+ case 'S':
+ if (strcasecmp(optarg, "samplerate") == 0)
+ arg_sync = SYNC_TYPE_SAMPLERATE;
+ else if (optarg[0] == 'n')
+ arg_sync = SYNC_TYPE_NONE;
+ else if (optarg[0] == 's')
+ arg_sync = SYNC_TYPE_SIMPLE;
+ else if (optarg[0] == 'c')
+ arg_sync = SYNC_TYPE_CAPTRATESHIFT;
+ else if (optarg[0] == 'p')
+ arg_sync = SYNC_TYPE_PLAYRATESHIFT;
+ else if (optarg[0] == 'r')
+ arg_sync = SYNC_TYPE_SAMPLERATE;
+ else
+ arg_sync = atoi(optarg);
+ if (arg_sync < 0 || arg_sync > SYNC_TYPE_LAST)
+ arg_sync = SYNC_TYPE_AUTO;
+ break;
+ case 'a':
+ if (optarg[0] == 'a')
+ arg_slave = SLAVE_TYPE_AUTO;
+ else if (strcasecmp(optarg, "on") == 0)
+ arg_slave = SLAVE_TYPE_ON;
+ else if (strcasecmp(optarg, "off") == 0)
+ arg_slave = SLAVE_TYPE_OFF;
+ else
+ arg_slave = atoi(optarg);
+ if (arg_slave < 0 || arg_slave > SLAVE_TYPE_LAST)
+ arg_slave = SLAVE_TYPE_AUTO;
+ break;
+ case 'T':
+ arg_thread = atoi(optarg);
+ if (arg_thread < 0)
+ arg_thread = 10000000 + loopbacks_count;
+ break;
+ case 'm':
+ if (arg_mixers_count >= MAX_MIXERS) {
+ logit(LOG_CRIT, "Maximum redirected mixer controls reached (max %i)\n", (int)MAX_MIXERS);
+ exit(EXIT_FAILURE);
+ }
+ arg_mixers[arg_mixers_count++] = optarg;
+ break;
+ case 'O':
+ if (arg_ossmixers_count >= MAX_MIXERS) {
+ logit(LOG_CRIT, "Maximum redirected mixer controls reached (max %i)\n", (int)MAX_MIXERS);
+ exit(EXIT_FAILURE);
+ }
+ arg_ossmixers[arg_ossmixers_count++] = optarg;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'w':
+ if (strcasecmp(optarg, "serialopen") == 0)
+ workarounds |= WORKAROUND_SERIALOPEN;
+ break;
+ case 'U':
+ arg_xrun = 1;
+ if (cmdline)
+ arg_default_xrun = 1;
+ break;
+ case 'W':
+ arg_wake = atoi(optarg);
+ if (cmdline)
+ arg_default_wake = arg_wake;
+ break;
+ }
+ }
+
+ if (morehelp) {
+ help();
+ exit(EXIT_SUCCESS);
+ }
+ if (arg_config == NULL) {
+ struct loopback_handle *play;
+ struct loopback_handle *capt;
+ err = create_loopback_handle(&play, arg_pdevice, arg_pctl, "playback");
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to create playback handle.\n");
+ exit(EXIT_FAILURE);
+ }
+ err = create_loopback_handle(&capt, arg_cdevice, arg_cctl, "capture");
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to create capture handle.\n");
+ exit(EXIT_FAILURE);
+ }
+ err = create_loopback(&loop, play, capt, output);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to create loopback handle.\n");
+ exit(EXIT_FAILURE);
+ }
+ play->format = capt->format = arg_format;
+ play->rate = play->rate_req = capt->rate = capt->rate_req = arg_rate;
+ play->channels = capt->channels = arg_channels;
+ play->buffer_size_req = capt->buffer_size_req = arg_buffer_size;
+ play->period_size_req = capt->period_size_req = arg_period_size;
+ play->resample = capt->resample = arg_resample;
+ play->nblock = capt->nblock = arg_nblock ? 1 : 0;
+ loop->latency_req = arg_latency_req;
+ loop->latency_reqtime = arg_latency_reqtime;
+ loop->sync = arg_sync;
+ loop->slave = arg_slave;
+ loop->thread = arg_thread;
+ loop->xrun = arg_xrun;
+ loop->wake = arg_wake;
+ err = add_mixers(loop, arg_mixers, arg_mixers_count);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to add mixer controls.\n");
+ exit(EXIT_FAILURE);
+ }
+ err = add_oss_mixers(loop, arg_ossmixers, arg_ossmixers_count);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to add ossmixer controls.\n");
+ exit(EXIT_FAILURE);
+ }
+#ifdef USE_SAMPLERATE
+ loop->src_enable = arg_samplerate > 0;
+ if (loop->src_enable)
+ loop->src_converter_type = arg_samplerate - 1;
+#else
+ if (arg_samplerate > 0) {
+ logit(LOG_CRIT, "No libsamplerate support.\n");
+ exit(EXIT_FAILURE);
+ }
+#endif
+ set_loop_time(loop, arg_loop_time);
+ add_loop(loop);
+ return 0;
+ }
+
+ return parse_config_file(arg_config, output);
+}
+
+static int parse_config_file(const char *file, snd_output_t *output)
+{
+ FILE *fp;
+ char line[2048], word[2048];
+ char *str, *ptr;
+ int argc, c, err = 0;
+ char **argv;
+
+ fp = fopen(file, "r");
+ if (fp == NULL) {
+ logit(LOG_CRIT, "Unable to open file '%s': %s\n", file, strerror(errno));
+ return -EIO;
+ }
+ while (!feof(fp)) {
+ if (fgets(line, sizeof(line)-1, fp) == NULL)
+ break;
+ line[sizeof(line)-1] = '\0';
+ my_argv = realloc(my_argv, my_argc + MAX_ARGS * sizeof(char *));
+ if (my_argv == NULL)
+ return -ENOMEM;
+ argv = my_argv + my_argc;
+ argc = 0;
+ argv[argc++] = strdup("<prog>");
+ my_argc++;
+ str = line;
+ while (*str) {
+ ptr = word;
+ while (*str && (*str == ' ' || *str < ' '))
+ str++;
+ if (*str == '#')
+ goto __next;
+ if (*str == '\'' || *str == '\"') {
+ c = *str++;
+ while (*str && *str != c)
+ *ptr++ = *str++;
+ if (*str == c)
+ str++;
+ } else {
+ while (*str && *str != ' ' && *str != '\t')
+ *ptr++ = *str++;
+ }
+ if (ptr != word) {
+ if (*(ptr-1) == '\n')
+ ptr--;
+ *ptr = '\0';
+ if (argc >= MAX_ARGS) {
+ logit(LOG_CRIT, "Too many arguments.");
+ goto __error;
+ }
+ argv[argc++] = strdup(word);
+ my_argc++;
+ }
+ }
+ /* erase runtime variables for getopt */
+ optarg = NULL;
+ optind = opterr = 1;
+ optopt = '?';
+
+ err = parse_config(argc, argv, output, 0);
+ __next:
+ if (err < 0)
+ break;
+ err = 0;
+ }
+ __error:
+ fclose(fp);
+
+ return err;
+}
+
+static void thread_job1(void *_data)
+{
+ struct loopback_thread *thread = _data;
+ snd_output_t *output = thread->output;
+ struct pollfd *pfds = NULL;
+ int pfds_count = 0;
+ int i, j, err, wake = 1000000;
+
+ setscheduler();
+
+ for (i = 0; i < thread->loopbacks_count; i++) {
+ err = pcmjob_init(thread->loopbacks[i]);
+ if (err < 0) {
+ logit(LOG_CRIT, "Loopback initialization failure.\n");
+ my_exit(thread, EXIT_FAILURE);
+ }
+ }
+ for (i = 0; i < thread->loopbacks_count; i++) {
+ err = pcmjob_start(thread->loopbacks[i]);
+ if (err < 0) {
+ logit(LOG_CRIT, "Loopback start failure.\n");
+ my_exit(thread, EXIT_FAILURE);
+ }
+ pfds_count += thread->loopbacks[i]->pollfd_count;
+ j = thread->loopbacks[i]->wake;
+ if (j > 0 && j < wake)
+ wake = j;
+ }
+ if (wake >= 1000000)
+ wake = -1;
+ pfds = calloc(pfds_count, sizeof(struct pollfd));
+ if (pfds == NULL || pfds_count <= 0) {
+ logit(LOG_CRIT, "Poll FDs allocation failed.\n");
+ my_exit(thread, EXIT_FAILURE);
+ }
+ while (!quit) {
+ struct timeval tv1, tv2;
+ for (i = j = 0; i < thread->loopbacks_count; i++) {
+ err = pcmjob_pollfds_init(thread->loopbacks[i], &pfds[j]);
+ if (err < 0) {
+ logit(LOG_CRIT, "Poll FD initialization failed.\n");
+ my_exit(thread, EXIT_FAILURE);
+ }
+ j += err;
+ }
+ if (verbose > 10)
+ gettimeofday(&tv1, NULL);
+ err = poll(pfds, j, wake);
+ if (err < 0)
+ err = -errno;
+ if (verbose > 10) {
+ gettimeofday(&tv2, NULL);
+ snd_output_printf(output, "pool took %lius\n", timediff(tv2, tv1));
+ }
+ if (err < 0) {
+ if (err == -EINTR || err == -ERESTART)
+ continue;
+ logit(LOG_CRIT, "Poll failed: %s\n", strerror(-err));
+ my_exit(thread, EXIT_FAILURE);
+ }
+ for (i = j = 0; i < thread->loopbacks_count; i++) {
+ struct loopback *loop = thread->loopbacks[i];
+ if (j < loop->active_pollfd_count) {
+ err = pcmjob_pollfds_handle(loop, &pfds[j]);
+ if (err < 0) {
+ logit(LOG_CRIT, "pcmjob failed.\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+ j += loop->active_pollfd_count;
+ }
+ }
+
+ my_exit(thread, EXIT_SUCCESS);
+}
+
+static void thread_job(struct loopback_thread *thread)
+{
+ if (!thread->threaded) {
+ thread_job1(thread);
+ return;
+ }
+ pthread_create(&thread->thread, NULL, (void *) &thread_job1,
+ (void *) thread);
+}
+
+static void send_to_all(int sig)
+{
+ struct loopback_thread *thread;
+ int i;
+
+ for (i = 0; i < threads_count; i++) {
+ thread = &threads[i];
+ if (thread->threaded)
+ pthread_kill(thread->thread, sig);
+ }
+}
+
+static void signal_handler(int sig)
+{
+ quit = 1;
+ send_to_all(SIGUSR2);
+}
+
+static void signal_handler_state(int sig)
+{
+ pthread_t self = pthread_self();
+ struct loopback_thread *thread;
+ int i, j;
+
+ if (pthread_equal(main_job, self))
+ send_to_all(SIGUSR1);
+ for (i = 0; i < threads_count; i++) {
+ thread = &threads[i];
+ if (thread->thread == self) {
+ for (j = 0; j < thread->loopbacks_count; j++)
+ pcmjob_state(thread->loopbacks[j]);
+ }
+ }
+ signal(sig, signal_handler_state);
+}
+
+static void signal_handler_ignore(int sig)
+{
+ signal(sig, signal_handler_ignore);
+}
+
+int main(int argc, char *argv[])
+{
+ snd_output_t *output;
+ int i, j, k, l, err;
+
+ err = snd_output_stdio_attach(&output, stdout, 0);
+ if (err < 0) {
+ logit(LOG_CRIT, "Output failed: %s\n", snd_strerror(err));
+ exit(EXIT_FAILURE);
+ }
+ err = parse_config(argc, argv, output, 1);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to parse arguments or configuration...\n");
+ exit(EXIT_FAILURE);
+ }
+ while (my_argc > 0)
+ free(my_argv[--my_argc]);
+ free(my_argv);
+
+ if (loopbacks_count <= 0) {
+ logit(LOG_CRIT, "No loopback defined...\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (daemonize) {
+ if (daemon(0, 0) < 0) {
+ logit(LOG_CRIT, "daemon() failed: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ i = fork();
+ if (i < 0) {
+ logit(LOG_CRIT, "fork() failed: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ if (i > 0) {
+ /* wait(&i); */
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+ /* we must sort thread IDs */
+ j = -1;
+ do {
+ k = 0x7fffffff;
+ for (i = 0; i < loopbacks_count; i++) {
+ if (loopbacks[i]->thread < k &&
+ loopbacks[i]->thread > j)
+ k = loopbacks[i]->thread;
+ }
+ j++;
+ for (i = 0; i < loopbacks_count; i++) {
+ if (loopbacks[i]->thread == k)
+ loopbacks[i]->thread = j;
+ }
+ } while (k != 0x7fffffff);
+ /* fix maximum thread id */
+ for (i = 0, j = -1; i < loopbacks_count; i++) {
+ if (loopbacks[i]->thread > j)
+ j = loopbacks[i]->thread;
+ }
+ j += 1;
+ threads = calloc(1, sizeof(struct loopback_thread) * j);
+ if (threads == NULL) {
+ logit(LOG_CRIT, "No enough memory\n");
+ exit(EXIT_FAILURE);
+ }
+ /* sort all threads */
+ for (k = 0; k < j; k++) {
+ for (i = l = 0; i < loopbacks_count; i++)
+ if (loopbacks[i]->thread == k)
+ l++;
+ threads[k].loopbacks = malloc(l * sizeof(struct loopback *));
+ threads[k].loopbacks_count = l;
+ threads[k].output = output;
+ threads[k].threaded = j > 1;
+ for (i = l = 0; i < loopbacks_count; i++)
+ if (loopbacks[i]->thread == k)
+ threads[k].loopbacks[l++] = loopbacks[i];
+ }
+ threads_count = j;
+ main_job = pthread_self();
+
+ signal(SIGINT, signal_handler);
+ signal(SIGTERM, signal_handler);
+ signal(SIGABRT, signal_handler);
+ signal(SIGUSR1, signal_handler_state);
+ signal(SIGUSR2, signal_handler_ignore);
+
+ for (k = 0; k < threads_count; k++)
+ thread_job(&threads[k]);
+
+ if (j > 1) {
+ for (k = 0; k < threads_count; k++)
+ pthread_join(threads[k].thread, NULL);
+ }
+
+ if (use_syslog)
+ closelog();
+ exit(EXIT_SUCCESS);
+}
diff --git a/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/alsaloop.h b/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/alsaloop.h
new file mode 100644
index 00000000..8dc445ae
--- /dev/null
+++ b/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/alsaloop.h
@@ -0,0 +1,218 @@
+/*
+ * A simple PCM loopback utility
+ * Copyright (c) 2010 by Jaroslav Kysela <perex@perex.cz>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "aconfig.h"
+#ifdef HAVE_SAMPLERATE_H
+#define USE_SAMPLERATE
+#include <samplerate.h>
+#else
+enum {
+ SRC_SINC_BEST_QUALITY = 0,
+ SRC_SINC_MEDIUM_QUALITY = 1,
+ SRC_SINC_FASTEST = 2,
+ SRC_ZERO_ORDER_HOLD = 3,
+ SRC_LINEAR = 4
+};
+#endif
+
+#define MAX_ARGS 128
+#define MAX_MIXERS 64
+
+#if 0
+#define FILE_PWRITE "/tmp/alsaloop.praw"
+#define FILE_CWRITE "/tmp/alsaloop.craw"
+#endif
+
+#define WORKAROUND_SERIALOPEN (1<<0)
+
+typedef enum _sync_type {
+ SYNC_TYPE_NONE = 0,
+ SYNC_TYPE_SIMPLE, /* add or remove samples */
+ SYNC_TYPE_CAPTRATESHIFT,
+ SYNC_TYPE_PLAYRATESHIFT,
+ SYNC_TYPE_SAMPLERATE,
+ SYNC_TYPE_AUTO, /* order: CAPTRATESHIFT, PLAYRATESHIFT, */
+ /* SAMPLERATE, SIMPLE */
+ SYNC_TYPE_LAST = SYNC_TYPE_AUTO
+} sync_type_t;
+
+typedef enum _slave_type {
+ SLAVE_TYPE_AUTO = 0,
+ SLAVE_TYPE_ON = 1,
+ SLAVE_TYPE_OFF = 2,
+ SLAVE_TYPE_LAST = SLAVE_TYPE_OFF
+} slave_type_t;
+
+struct loopback_control {
+ snd_ctl_elem_id_t *id;
+ snd_ctl_elem_info_t *info;
+ snd_ctl_elem_value_t *value;
+};
+
+struct loopback_mixer {
+ unsigned int skip:1;
+ struct loopback_control src;
+ struct loopback_control dst;
+ struct loopback_mixer *next;
+};
+
+struct loopback_ossmixer {
+ unsigned int skip:1;
+ const char *alsa_id;
+ int alsa_index;
+ const char *oss_id;
+ struct loopback_ossmixer *next;
+};
+
+struct loopback_handle {
+ struct loopback *loopback;
+ char *device;
+ char *ctldev;
+ char *id;
+ int card_number;
+ snd_pcm_t *handle;
+ snd_pcm_access_t access;
+ snd_pcm_format_t format;
+ unsigned int rate;
+ unsigned int rate_req;
+ unsigned int channels;
+ unsigned int buffer_size;
+ unsigned int period_size;
+ snd_pcm_uframes_t avail_min;
+ unsigned int buffer_size_req;
+ unsigned int period_size_req;
+ unsigned int frame_size;
+ unsigned int resample:1; /* do resample */
+ unsigned int nblock:1; /* do block (period size) transfers */
+ unsigned int xrun_pending:1;
+ unsigned int pollfd_count;
+ /* I/O job */
+ char *buf; /* I/O buffer */
+ snd_pcm_uframes_t buf_pos; /* I/O position */
+ snd_pcm_uframes_t buf_count; /* filled samples */
+ snd_pcm_uframes_t buf_size; /* buffer size in frames */
+ snd_pcm_uframes_t buf_over; /* capture buffer overflow */
+ /* statistics */
+ snd_pcm_uframes_t max;
+ unsigned long long counter;
+ unsigned long sync_point; /* in samples */
+ snd_pcm_sframes_t last_delay;
+ double pitch;
+ snd_pcm_uframes_t total_queued;
+ /* control */
+ snd_ctl_t *ctl;
+ unsigned int ctl_pollfd_count;
+ snd_ctl_elem_value_t *ctl_notify;
+ snd_ctl_elem_value_t *ctl_rate_shift;
+ snd_ctl_elem_value_t *ctl_active;
+ snd_ctl_elem_value_t *ctl_format;
+ snd_ctl_elem_value_t *ctl_rate;
+ snd_ctl_elem_value_t *ctl_channels;
+};
+
+struct loopback {
+ char *id;
+ struct loopback_handle *capt;
+ struct loopback_handle *play;
+ snd_pcm_uframes_t latency; /* final latency in frames */
+ unsigned int latency_req; /* in frames */
+ unsigned int latency_reqtime; /* in us */
+ unsigned long loop_time; /* ~0 = unlimited (in seconds) */
+ unsigned long long loop_limit; /* ~0 = unlimited (in frames) */
+ snd_output_t *output;
+ snd_output_t *state;
+ int pollfd_count;
+ int active_pollfd_count;
+ unsigned int linked:1; /* linked streams */
+ unsigned int reinit:1;
+ unsigned int running:1;
+ unsigned int stop_pending:1;
+ snd_pcm_uframes_t stop_count;
+ sync_type_t sync; /* type of sync */
+ slave_type_t slave;
+ int thread; /* thread number */
+ unsigned int wake;
+ /* statistics */
+ double pitch;
+ double pitch_delta;
+ snd_pcm_sframes_t pitch_diff;
+ snd_pcm_sframes_t pitch_diff_min;
+ snd_pcm_sframes_t pitch_diff_max;
+ unsigned int total_queued_count;
+ snd_timestamp_t tstamp_start;
+ snd_timestamp_t tstamp_end;
+ /* xrun profiling */
+ unsigned int xrun:1; /* xrun profiling */
+ snd_timestamp_t xrun_last_update;
+ snd_timestamp_t xrun_last_wake0;
+ snd_timestamp_t xrun_last_wake;
+ snd_timestamp_t xrun_last_check0;
+ snd_timestamp_t xrun_last_check;
+ snd_pcm_sframes_t xrun_last_pdelay;
+ snd_pcm_sframes_t xrun_last_cdelay;
+ snd_pcm_uframes_t xrun_buf_pcount;
+ snd_pcm_uframes_t xrun_buf_ccount;
+ unsigned int xrun_out_frames;
+ long xrun_max_proctime;
+ double xrun_max_missing;
+ /* control mixer */
+ struct loopback_mixer *controls;
+ struct loopback_ossmixer *oss_controls;
+ /* sample rate */
+ unsigned int use_samplerate:1;
+#ifdef USE_SAMPLERATE
+ unsigned int src_enable:1;
+ int src_converter_type;
+ SRC_STATE *src_state;
+ SRC_DATA src_data;
+ unsigned int src_out_frames;
+#endif
+#ifdef FILE_CWRITE
+ FILE *cfile;
+#endif
+#ifdef FILE_PWRITE
+ FILE *pfile;
+#endif
+};
+
+extern int verbose;
+extern int workarounds;
+extern int use_syslog;
+
+#define logit(priority, fmt, args...) do { \
+ if (use_syslog) \
+ syslog(priority, fmt, ##args); \
+ else \
+ fprintf(stderr, fmt, ##args); \
+} while (0)
+
+int pcmjob_init(struct loopback *loop);
+int pcmjob_done(struct loopback *loop);
+int pcmjob_start(struct loopback *loop);
+int pcmjob_stop(struct loopback *loop);
+int pcmjob_pollfds_init(struct loopback *loop, struct pollfd *fds);
+int pcmjob_pollfds_handle(struct loopback *loop, struct pollfd *fds);
+void pcmjob_state(struct loopback *loop);
+
+int control_parse_id(const char *str, snd_ctl_elem_id_t *id);
+int control_id_match(snd_ctl_elem_id_t *id1, snd_ctl_elem_id_t *id2);
+int control_init(struct loopback *loop);
+int control_done(struct loopback *loop);
+int control_event(struct loopback_handle *lhandle, snd_ctl_event_t *ev);
diff --git a/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/control.c b/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/control.c
new file mode 100644
index 00000000..8383d799
--- /dev/null
+++ b/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/control.c
@@ -0,0 +1,424 @@
+/*
+ * A simple PCM loopback utility
+ * Copyright (c) 2010 by Jaroslav Kysela <perex@perex.cz>
+ *
+ * Author: Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <ctype.h>
+#include <syslog.h>
+#include <alsa/asoundlib.h>
+#include "alsaloop.h"
+
+static char *id_str(snd_ctl_elem_id_t *id)
+{
+ static char str[128];
+
+ sprintf(str, "%i,%s,%i,%i,%s,%i",
+ snd_ctl_elem_id_get_numid(id),
+ snd_ctl_elem_iface_name(snd_ctl_elem_id_get_interface(id)),
+ snd_ctl_elem_id_get_device(id),
+ snd_ctl_elem_id_get_subdevice(id),
+ snd_ctl_elem_id_get_name(id),
+ snd_ctl_elem_id_get_index(id));
+ return str;
+}
+
+int control_parse_id(const char *str, snd_ctl_elem_id_t *id)
+{
+ int c, size, numid;
+ char *ptr;
+
+ while (*str == ' ' || *str == '\t')
+ str++;
+ if (!(*str))
+ return -EINVAL;
+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); /* default */
+ while (*str) {
+ if (!strncasecmp(str, "numid=", 6)) {
+ str += 6;
+ numid = atoi(str);
+ if (numid <= 0) {
+ logit(LOG_CRIT, "Invalid numid %d\n", numid);
+ return -EINVAL;
+ }
+ snd_ctl_elem_id_set_numid(id, atoi(str));
+ while (isdigit(*str))
+ str++;
+ } else if (!strncasecmp(str, "iface=", 6)) {
+ str += 6;
+ if (!strncasecmp(str, "card", 4)) {
+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD);
+ str += 4;
+ } else if (!strncasecmp(str, "mixer", 5)) {
+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+ str += 5;
+ } else if (!strncasecmp(str, "pcm", 3)) {
+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_PCM);
+ str += 3;
+ } else if (!strncasecmp(str, "rawmidi", 7)) {
+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_RAWMIDI);
+ str += 7;
+ } else if (!strncasecmp(str, "timer", 5)) {
+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_TIMER);
+ str += 5;
+ } else if (!strncasecmp(str, "sequencer", 9)) {
+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_SEQUENCER);
+ str += 9;
+ } else {
+ return -EINVAL;
+ }
+ } else if (!strncasecmp(str, "name=", 5)) {
+ char buf[64];
+ str += 5;
+ ptr = buf;
+ size = 0;
+ if (*str == '\'' || *str == '\"') {
+ c = *str++;
+ while (*str && *str != c) {
+ if (size < (int)sizeof(buf)) {
+ *ptr++ = *str;
+ size++;
+ }
+ str++;
+ }
+ if (*str == c)
+ str++;
+ } else {
+ while (*str && *str != ',') {
+ if (size < (int)sizeof(buf)) {
+ *ptr++ = *str;
+ size++;
+ }
+ str++;
+ }
+ }
+ *ptr = '\0';
+ snd_ctl_elem_id_set_name(id, buf);
+ } else if (!strncasecmp(str, "index=", 6)) {
+ str += 6;
+ snd_ctl_elem_id_set_index(id, atoi(str));
+ while (isdigit(*str))
+ str++;
+ } else if (!strncasecmp(str, "device=", 7)) {
+ str += 7;
+ snd_ctl_elem_id_set_device(id, atoi(str));
+ while (isdigit(*str))
+ str++;
+ } else if (!strncasecmp(str, "subdevice=", 10)) {
+ str += 10;
+ snd_ctl_elem_id_set_subdevice(id, atoi(str));
+ while (isdigit(*str))
+ str++;
+ }
+ if (*str == ',') {
+ str++;
+ } else {
+ if (*str)
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+int control_id_match(snd_ctl_elem_id_t *id1, snd_ctl_elem_id_t *id2)
+{
+ if (snd_ctl_elem_id_get_interface(id1) !=
+ snd_ctl_elem_id_get_interface(id2))
+ return 0;
+ if (snd_ctl_elem_id_get_device(id1) !=
+ snd_ctl_elem_id_get_device(id2))
+ return 0;
+ if (snd_ctl_elem_id_get_subdevice(id1) !=
+ snd_ctl_elem_id_get_subdevice(id2))
+ return 0;
+ if (strcmp(snd_ctl_elem_id_get_name(id1),
+ snd_ctl_elem_id_get_name(id2)) != 0)
+ return 0;
+ if (snd_ctl_elem_id_get_index(id1) !=
+ snd_ctl_elem_id_get_index(id2))
+ return 0;
+ return 1;
+}
+
+static int control_init1(struct loopback_handle *lhandle,
+ struct loopback_control *ctl)
+{
+ int err;
+
+ snd_ctl_elem_info_set_id(ctl->info, ctl->id);
+ snd_ctl_elem_value_set_id(ctl->value, ctl->id);
+ if (lhandle->ctl == NULL) {
+ logit(LOG_WARNING, "Unable to read control info for '%s'\n", id_str(ctl->id));
+ return -EIO;
+ }
+ err = snd_ctl_elem_info(lhandle->ctl, ctl->info);
+ if (err < 0) {
+ logit(LOG_WARNING, "Unable to read control info '%s': %s\n", id_str(ctl->id), snd_strerror(err));
+ return err;
+ }
+ err = snd_ctl_elem_read(lhandle->ctl, ctl->value);
+ if (err < 0) {
+ logit(LOG_WARNING, "Unable to read control value (init1) '%s': %s\n", id_str(ctl->id), snd_strerror(err));
+ return err;
+ }
+ return 0;
+}
+
+static int copy_value(struct loopback_control *dst,
+ struct loopback_control *src)
+{
+ snd_ctl_elem_type_t type;
+ unsigned int count;
+ int i;
+
+ type = snd_ctl_elem_info_get_type(dst->info);
+ count = snd_ctl_elem_info_get_count(dst->info);
+ switch (type) {
+ case SND_CTL_ELEM_TYPE_BOOLEAN:
+ for (i = 0; i < count; i++)
+ snd_ctl_elem_value_set_boolean(dst->value,
+ i, snd_ctl_elem_value_get_boolean(src->value, i));
+ break;
+ case SND_CTL_ELEM_TYPE_INTEGER:
+ for (i = 0; i < count; i++) {
+ snd_ctl_elem_value_set_integer(dst->value,
+ i, snd_ctl_elem_value_get_integer(src->value, i));
+ }
+ break;
+ default:
+ logit(LOG_CRIT, "Unable to copy control value for type %s\n", snd_ctl_elem_type_name(type));
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int oss_set(struct loopback *loop,
+ struct loopback_ossmixer *ossmix,
+ int enable)
+{
+ char buf[128], file[128];
+ int fd;
+
+ if (loop->capt->card_number < 0)
+ return 0;
+ if (!enable) {
+ sprintf(buf, "%s \"\" 0\n", ossmix->oss_id);
+ } else {
+ sprintf(buf, "%s \"%s\" %i\n", ossmix->oss_id, ossmix->alsa_id, ossmix->alsa_index);
+ }
+ sprintf(file, "/proc/asound/card%i/oss_mixer", loop->capt->card_number);
+ if (verbose)
+ snd_output_printf(loop->output, "%s: Initialize OSS volume %s: %s", loop->id, file, buf);
+ fd = open(file, O_WRONLY);
+ if (fd >= 0 && write(fd, buf, strlen(buf)) == strlen(buf)) {
+ close(fd);
+ return 0;
+ }
+ if (fd >= 0)
+ close(fd);
+ logit(LOG_INFO, "%s: Unable to initialize OSS Mixer ID '%s'\n", loop->id, ossmix->oss_id);
+ return -1;
+}
+
+static int control_init2(struct loopback *loop,
+ struct loopback_mixer *mix)
+{
+ snd_ctl_elem_type_t type;
+ unsigned int count;
+ int err;
+
+ snd_ctl_elem_info_copy(mix->dst.info, mix->src.info);
+ snd_ctl_elem_info_set_id(mix->dst.info, mix->dst.id);
+ snd_ctl_elem_value_clear(mix->dst.value);
+ snd_ctl_elem_value_set_id(mix->dst.value, mix->dst.id);
+ type = snd_ctl_elem_info_get_type(mix->dst.info);
+ count = snd_ctl_elem_info_get_count(mix->dst.info);
+ snd_ctl_elem_remove(loop->capt->ctl, mix->dst.id);
+ switch (type) {
+ case SND_CTL_ELEM_TYPE_BOOLEAN:
+ err = snd_ctl_elem_add_boolean(loop->capt->ctl,
+ mix->dst.id, count);
+ copy_value(&mix->dst, &mix->src);
+ break;
+ case SND_CTL_ELEM_TYPE_INTEGER:
+ err = snd_ctl_elem_add_integer(loop->capt->ctl,
+ mix->dst.id, count,
+ snd_ctl_elem_info_get_min(mix->dst.info),
+ snd_ctl_elem_info_get_max(mix->dst.info),
+ snd_ctl_elem_info_get_step(mix->dst.info));
+ copy_value(&mix->dst, &mix->src);
+ break;
+ default:
+ logit(LOG_CRIT, "Unable to handle control type %s\n", snd_ctl_elem_type_name(type));
+ err = -EINVAL;
+ break;
+ }
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to create control '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
+ return err;
+ }
+ err = snd_ctl_elem_unlock(loop->capt->ctl, mix->dst.id);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to unlock control info '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
+ return err;
+ }
+ err = snd_ctl_elem_info(loop->capt->ctl, mix->dst.info);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to read control info '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
+ return err;
+ }
+ if (snd_ctl_elem_info_is_tlv_writable(mix->dst.info)) {
+ unsigned int tlv[64];
+ err = snd_ctl_elem_tlv_read(loop->play->ctl,
+ mix->src.id,
+ tlv, sizeof(tlv));
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to read TLV for '%s': %s\n", id_str(mix->src.id), snd_strerror(err));
+ tlv[0] = tlv[1] = 0;
+ }
+ err = snd_ctl_elem_tlv_write(loop->capt->ctl,
+ mix->dst.id,
+ tlv);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to write TLV for '%s': %s\n", id_str(mix->src.id), snd_strerror(err));
+ return err;
+ }
+ }
+ err = snd_ctl_elem_write(loop->capt->ctl, mix->dst.value);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to write control value '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
+ return err;
+ }
+ return 0;
+}
+
+int control_init(struct loopback *loop)
+{
+ struct loopback_mixer *mix;
+ struct loopback_ossmixer *ossmix;
+ int err;
+
+ for (ossmix = loop->oss_controls; ossmix; ossmix = ossmix->next)
+ oss_set(loop, ossmix, 0);
+ for (mix = loop->controls; mix; mix = mix->next) {
+ err = control_init1(loop->play, &mix->src);
+ if (err < 0) {
+ logit(LOG_WARNING, "%s: Disabling playback control '%s'\n", loop->id, id_str(mix->src.id));
+ mix->skip = 1;
+ continue;
+ }
+ err = control_init2(loop, mix);
+ if (err < 0)
+ return err;
+ }
+ for (ossmix = loop->oss_controls; ossmix; ossmix = ossmix->next) {
+ err = oss_set(loop, ossmix, 1);
+ if (err < 0) {
+ ossmix->skip = 1;
+ logit(LOG_WARNING, "%s: Disabling OSS mixer ID '%s'\n", loop->id, ossmix->oss_id);
+ }
+ }
+ return 0;
+}
+
+int control_done(struct loopback *loop)
+{
+ struct loopback_mixer *mix;
+ struct loopback_ossmixer *ossmix;
+ int err;
+
+ if (loop->capt->ctl == NULL)
+ return 0;
+ for (ossmix = loop->oss_controls; ossmix; ossmix = ossmix->next) {
+ err = oss_set(loop, ossmix, 0);
+ if (err < 0)
+ logit(LOG_WARNING, "%s: Unable to remove OSS control '%s'\n", loop->id, ossmix->oss_id);
+ }
+ for (mix = loop->controls; mix; mix = mix->next) {
+ if (mix->skip)
+ continue;
+ err = snd_ctl_elem_remove(loop->capt->ctl, mix->dst.id);
+ if (err < 0)
+ logit(LOG_WARNING, "%s: Unable to remove control '%s': %s\n", loop->id, id_str(mix->dst.id), snd_strerror(err));
+ }
+ return 0;
+}
+
+static int control_event1(struct loopback *loop,
+ struct loopback_mixer *mix,
+ snd_ctl_event_t *ev,
+ int capture)
+{
+ unsigned int mask = snd_ctl_event_elem_get_mask(ev);
+ int err;
+
+ if (mask == SND_CTL_EVENT_MASK_REMOVE)
+ return 0;
+ if ((mask & SND_CTL_EVENT_MASK_VALUE) == 0)
+ return 0;
+ if (!capture) {
+ snd_ctl_elem_value_set_id(mix->src.value, mix->src.id);
+ err = snd_ctl_elem_read(loop->play->ctl, mix->src.value);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to read control value (event1) '%s': %s\n", id_str(mix->src.id), snd_strerror(err));
+ return err;
+ }
+ copy_value(&mix->dst, &mix->src);
+ err = snd_ctl_elem_write(loop->capt->ctl, mix->dst.value);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to write control value (event1) '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
+ return err;
+ }
+ } else {
+ err = snd_ctl_elem_read(loop->capt->ctl, mix->dst.value);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to read control value (event2) '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
+ return err;
+ }
+ copy_value(&mix->src, &mix->dst);
+ err = snd_ctl_elem_write(loop->play->ctl, mix->src.value);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to write control value (event2) '%s': %s\n", id_str(mix->src.id), snd_strerror(err));
+ return err;
+ }
+ }
+ return 0;
+}
+
+int control_event(struct loopback_handle *lhandle, snd_ctl_event_t *ev)
+{
+ snd_ctl_elem_id_t *id2;
+ struct loopback_mixer *mix;
+ int capt = lhandle == lhandle->loopback->capt;
+ int err;
+
+ snd_ctl_elem_id_alloca(&id2);
+ snd_ctl_event_elem_get_id(ev, id2);
+ for (mix = lhandle->loopback->controls; mix; mix = mix->next) {
+ if (mix->skip)
+ continue;
+ if (control_id_match(id2, capt ? mix->dst.id : mix->src.id)) {
+ err = control_event1(lhandle->loopback, mix, ev, capt);
+ if (err < 0)
+ return err;
+ }
+ }
+ return 0;
+}
diff --git a/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/pcmjob.c b/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/pcmjob.c
new file mode 100644
index 00000000..0b84803d
--- /dev/null
+++ b/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/pcmjob.c
@@ -0,0 +1,1925 @@
+/*
+ * A simple PCM loopback utility
+ * Copyright (c) 2010 by Jaroslav Kysela <perex@perex.cz>
+ *
+ * Author: Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sched.h>
+#include <errno.h>
+#include <getopt.h>
+#include <alsa/asoundlib.h>
+#include <sys/time.h>
+#include <math.h>
+#include <syslog.h>
+#include <pthread.h>
+#include "alsaloop.h"
+
+#define XRUN_PROFILE_UNKNOWN (-10000000)
+
+static int set_rate_shift(struct loopback_handle *lhandle, double pitch);
+static int get_rate(struct loopback_handle *lhandle);
+
+#define SYNCTYPE(v) [SYNC_TYPE_##v] = #v
+
+static const char *sync_types[] = {
+ SYNCTYPE(NONE),
+ SYNCTYPE(SIMPLE),
+ SYNCTYPE(CAPTRATESHIFT),
+ SYNCTYPE(PLAYRATESHIFT),
+ SYNCTYPE(SAMPLERATE),
+ SYNCTYPE(AUTO)
+};
+
+#define SRCTYPE(v) [SRC_##v] = "SRC_" #v
+
+#ifdef USE_SAMPLERATE
+static const char *src_types[] = {
+ SRCTYPE(SINC_BEST_QUALITY),
+ SRCTYPE(SINC_MEDIUM_QUALITY),
+ SRCTYPE(SINC_FASTEST),
+ SRCTYPE(ZERO_ORDER_HOLD),
+ SRCTYPE(LINEAR)
+};
+#endif
+
+static pthread_mutex_t pcm_open_mutex =
+ PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
+
+static inline void pcm_open_lock(void)
+{
+ if (workarounds & WORKAROUND_SERIALOPEN)
+ pthread_mutex_lock(&pcm_open_mutex);
+}
+
+static inline void pcm_open_unlock(void)
+{
+ if (workarounds & WORKAROUND_SERIALOPEN)
+ pthread_mutex_unlock(&pcm_open_mutex);
+}
+
+static inline snd_pcm_uframes_t get_whole_latency(struct loopback *loop)
+{
+ return loop->latency;
+}
+
+static inline unsigned long long
+ frames_to_time(unsigned int rate,
+ snd_pcm_uframes_t frames)
+{
+ return (frames * 1000000ULL) / rate;
+}
+
+static inline snd_pcm_uframes_t time_to_frames(unsigned int rate,
+ unsigned long long time)
+{
+ return (time * rate) / 1000000ULL;
+}
+
+static int setparams_stream(struct loopback_handle *lhandle,
+ snd_pcm_hw_params_t *params)
+{
+ snd_pcm_t *handle = lhandle->handle;
+ int err;
+ unsigned int rrate;
+
+ err = snd_pcm_hw_params_any(handle, params);
+ if (err < 0) {
+ logit(LOG_CRIT, "Broken configuration for %s PCM: no configurations available: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ err = snd_pcm_hw_params_set_rate_resample(handle, params, lhandle->resample);
+ if (err < 0) {
+ logit(LOG_CRIT, "Resample setup failed for %s (val %i): %s\n", lhandle->id, lhandle->resample, snd_strerror(err));
+ return err;
+ }
+ err = snd_pcm_hw_params_set_access(handle, params, lhandle->access);
+ if (err < 0) {
+ logit(LOG_CRIT, "Access type not available for %s: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ err = snd_pcm_hw_params_set_format(handle, params, lhandle->format);
+ if (err < 0) {
+ logit(LOG_CRIT, "Sample format not available for %s: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ err = snd_pcm_hw_params_set_channels(handle, params, lhandle->channels);
+ if (err < 0) {
+ logit(LOG_CRIT, "Channels count (%i) not available for %s: %s\n", lhandle->channels, lhandle->id, snd_strerror(err));
+ return err;
+ }
+ rrate = lhandle->rate_req;
+ err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0);
+ if (err < 0) {
+ logit(LOG_CRIT, "Rate %iHz not available for %s: %s\n", lhandle->rate_req, lhandle->id, snd_strerror(err));
+ return err;
+ }
+ rrate = 0;
+ snd_pcm_hw_params_get_rate(params, &rrate, 0);
+ lhandle->rate = rrate;
+ if (
+#ifdef USE_SAMPLERATE
+ !lhandle->loopback->src_enable &&
+#endif
+ (int)rrate != lhandle->rate) {
+ logit(LOG_CRIT, "Rate does not match (requested %iHz, got %iHz, resample %i)\n", lhandle->rate, rrate, lhandle->resample);
+ return -EINVAL;
+ }
+ lhandle->pitch = (double)lhandle->rate_req / (double)lhandle->rate;
+ return 0;
+}
+
+static int setparams_bufsize(struct loopback_handle *lhandle,
+ snd_pcm_hw_params_t *params,
+ snd_pcm_hw_params_t *tparams,
+ snd_pcm_uframes_t bufsize)
+{
+ snd_pcm_t *handle = lhandle->handle;
+ int err;
+ snd_pcm_uframes_t periodsize;
+ snd_pcm_uframes_t buffersize;
+ snd_pcm_uframes_t last_bufsize = 0;
+
+ if (lhandle->buffer_size_req > 0) {
+ bufsize = lhandle->buffer_size_req;
+ last_bufsize = bufsize;
+ goto __set_it;
+ }
+ __again:
+ if (lhandle->buffer_size_req > 0) {
+ logit(LOG_CRIT, "Unable to set buffer size %li for %s\n", (long)lhandle->buffer_size, lhandle->id);
+ return -EIO;
+ }
+ if (last_bufsize == bufsize)
+ bufsize += 4;
+ last_bufsize = bufsize;
+ if (bufsize > 10*1024*1024) {
+ logit(LOG_CRIT, "Buffer size too big\n");
+ return -EIO;
+ }
+ __set_it:
+ snd_pcm_hw_params_copy(params, tparams);
+ periodsize = bufsize * 8;
+ err = snd_pcm_hw_params_set_buffer_size_near(handle, params, &periodsize);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to set buffer size %li for %s: %s\n", periodsize, lhandle->id, snd_strerror(err));
+ goto __again;
+ }
+ snd_pcm_hw_params_get_buffer_size(params, &periodsize);
+ if (verbose > 6)
+ snd_output_printf(lhandle->loopback->output, "%s: buffer_size=%li\n", lhandle->id, periodsize);
+ if (lhandle->period_size_req > 0)
+ periodsize = lhandle->period_size_req;
+ else
+ periodsize /= 8;
+ err = snd_pcm_hw_params_set_period_size_near(handle, params, &periodsize, 0);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to set period size %li for %s: %s\n", periodsize, lhandle->id, snd_strerror(err));
+ goto __again;
+ }
+ snd_pcm_hw_params_get_period_size(params, &periodsize, NULL);
+ if (verbose > 6)
+ snd_output_printf(lhandle->loopback->output, "%s: period_size=%li\n", lhandle->id, periodsize);
+ if (periodsize != bufsize)
+ bufsize = periodsize;
+ snd_pcm_hw_params_get_buffer_size(params, &buffersize);
+ if (periodsize * 2 > buffersize)
+ goto __again;
+ lhandle->period_size = periodsize;
+ lhandle->buffer_size = buffersize;
+ return 0;
+}
+
+static int setparams_set(struct loopback_handle *lhandle,
+ snd_pcm_hw_params_t *params,
+ snd_pcm_sw_params_t *swparams,
+ snd_pcm_uframes_t bufsize)
+{
+ snd_pcm_t *handle = lhandle->handle;
+ int err;
+ snd_pcm_uframes_t val, period_size, buffer_size;
+
+ err = snd_pcm_hw_params(handle, params);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to set hw params for %s: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ err = snd_pcm_sw_params_current(handle, swparams);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to determine current swparams for %s: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 0x7fffffff);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to set start threshold mode for %s: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ snd_pcm_hw_params_get_period_size(params, &period_size, NULL);
+ snd_pcm_hw_params_get_buffer_size(params, &buffer_size);
+ if (lhandle->nblock) {
+ if (lhandle == lhandle->loopback->play) {
+ val = buffer_size - (2 * period_size - 4);
+ } else {
+ val = 4;
+ }
+ if (verbose > 6)
+ snd_output_printf(lhandle->loopback->output, "%s: avail_min1=%li\n", lhandle->id, val);
+ } else {
+ if (lhandle == lhandle->loopback->play) {
+ val = bufsize + bufsize / 2;
+ if (val < (period_size * 3) / 4)
+ val = (period_size * 3) / 4;
+ if (val > (buffer_size * 3) / 4)
+ val = (buffer_size * 3) / 4;
+ val = buffer_size - val;
+ } else {
+ val = bufsize / 2;
+ if (val < period_size / 2)
+ val = period_size / 2;
+ if (val > buffer_size / 4)
+ val = buffer_size / 4;
+ }
+ if (verbose > 6)
+ snd_output_printf(lhandle->loopback->output, "%s: avail_min2=%li\n", lhandle->id, val);
+ }
+ err = snd_pcm_sw_params_set_avail_min(handle, swparams, val);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to set avail min for %s: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ snd_pcm_sw_params_get_avail_min(swparams, &lhandle->avail_min);
+ err = snd_pcm_sw_params(handle, swparams);
+ if (err < 0) {
+ logit(LOG_CRIT, "Unable to set sw params for %s: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ return 0;
+}
+
+static int setparams(struct loopback *loop, snd_pcm_uframes_t bufsize)
+{
+ int err;
+ snd_pcm_hw_params_t *pt_params, *ct_params; /* templates with rate, format and channels */
+ snd_pcm_hw_params_t *p_params, *c_params;
+ snd_pcm_sw_params_t *p_swparams, *c_swparams;
+
+ snd_pcm_hw_params_alloca(&p_params);
+ snd_pcm_hw_params_alloca(&c_params);
+ snd_pcm_hw_params_alloca(&pt_params);
+ snd_pcm_hw_params_alloca(&ct_params);
+ snd_pcm_sw_params_alloca(&p_swparams);
+ snd_pcm_sw_params_alloca(&c_swparams);
+ if ((err = setparams_stream(loop->play, pt_params)) < 0) {
+ logit(LOG_CRIT, "Unable to set parameters for %s stream: %s\n", loop->play->id, snd_strerror(err));
+ return err;
+ }
+ if ((err = setparams_stream(loop->capt, ct_params)) < 0) {
+ logit(LOG_CRIT, "Unable to set parameters for %s stream: %s\n", loop->capt->id, snd_strerror(err));
+ return err;
+ }
+
+ if ((err = setparams_bufsize(loop->play, p_params, pt_params, bufsize / loop->play->pitch)) < 0) {
+ logit(LOG_CRIT, "Unable to set buffer parameters for %s stream: %s\n", loop->play->id, snd_strerror(err));
+ return err;
+ }
+ if ((err = setparams_bufsize(loop->capt, c_params, ct_params, bufsize / loop->capt->pitch)) < 0) {
+ logit(LOG_CRIT, "Unable to set buffer parameters for %s stream: %s\n", loop->capt->id, snd_strerror(err));
+ return err;
+ }
+
+ if ((err = setparams_set(loop->play, p_params, p_swparams, bufsize / loop->play->pitch)) < 0) {
+ logit(LOG_CRIT, "Unable to set sw parameters for %s stream: %s\n", loop->play->id, snd_strerror(err));
+ return err;
+ }
+ if ((err = setparams_set(loop->capt, c_params, c_swparams, bufsize / loop->capt->pitch)) < 0) {
+ logit(LOG_CRIT, "Unable to set sw parameters for %s stream: %s\n", loop->capt->id, snd_strerror(err));
+ return err;
+ }
+
+#if 0
+ if (!loop->linked)
+ if (snd_pcm_link(loop->capt->handle, loop->play->handle) >= 0)
+ loop->linked = 1;
+#endif
+ if ((err = snd_pcm_prepare(loop->play->handle)) < 0) {
+ logit(LOG_CRIT, "Prepare %s error: %s\n", loop->play->id, snd_strerror(err));
+ return err;
+ }
+ if (!loop->linked && (err = snd_pcm_prepare(loop->capt->handle)) < 0) {
+ logit(LOG_CRIT, "Prepare %s error: %s\n", loop->capt->id, snd_strerror(err));
+ return err;
+ }
+
+ if (verbose) {
+ snd_pcm_dump(loop->play->handle, loop->output);
+ snd_pcm_dump(loop->capt->handle, loop->output);
+ }
+ return 0;
+}
+
+static void showlatency(snd_output_t *out, size_t latency, unsigned int rate,
+ char *prefix)
+{
+ double d;
+ d = (double)latency / (double)rate;
+ snd_output_printf(out, "%s %li frames, %.3fus, %.6fms (%.4fHz)\n", prefix, (long)latency, d * 1000000, d * 1000, (double)1 / d);
+}
+
+static long timediff(snd_timestamp_t t1, snd_timestamp_t t2)
+{
+ signed long l;
+
+ t1.tv_sec -= t2.tv_sec;
+ if (t1.tv_usec < t2.tv_usec) {
+ l = ((t1.tv_usec + 1000000) - t2.tv_usec) % 1000000;
+ t1.tv_sec--;
+ } else {
+ l = t1.tv_usec - t2.tv_usec;
+ }
+ return (t1.tv_sec * 1000000) + l;
+}
+
+static int getcurtimestamp(snd_timestamp_t *ts)
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ ts->tv_sec = tv.tv_sec;
+ ts->tv_usec = tv.tv_usec;
+ return 0;
+}
+
+static void xrun_profile0(struct loopback *loop)
+{
+ snd_pcm_sframes_t pdelay, cdelay;
+
+ if (snd_pcm_delay(loop->play->handle, &pdelay) >= 0 &&
+ snd_pcm_delay(loop->capt->handle, &cdelay) >= 0) {
+ getcurtimestamp(&loop->xrun_last_update);
+ loop->xrun_last_pdelay = pdelay;
+ loop->xrun_last_cdelay = cdelay;
+ loop->xrun_buf_pcount = loop->play->buf_count;
+ loop->xrun_buf_ccount = loop->capt->buf_count;
+#ifdef USE_SAMPLERATE
+ loop->xrun_out_frames = loop->src_out_frames;
+#endif
+ }
+}
+
+static inline void xrun_profile(struct loopback *loop)
+{
+ if (loop->xrun)
+ xrun_profile0(loop);
+}
+
+static void xrun_stats0(struct loopback *loop)
+{
+ snd_timestamp_t t;
+ double expected, last, wake, check, queued = -1, proc, missing = -1;
+ double maxbuf, pfilled, cfilled, cqueued = -1, avail_min;
+ double sincejob;
+
+ expected = ((double)loop->latency /
+ (double)loop->play->rate_req) * 1000;
+ getcurtimestamp(&t);
+ last = (double)timediff(t, loop->xrun_last_update) / 1000;
+ wake = (double)timediff(t, loop->xrun_last_wake) / 1000;
+ check = (double)timediff(t, loop->xrun_last_check) / 1000;
+ sincejob = (double)timediff(t, loop->tstamp_start) / 1000;
+ if (loop->xrun_last_pdelay != XRUN_PROFILE_UNKNOWN)
+ queued = ((double)loop->xrun_last_pdelay /
+ (double)loop->play->rate) * 1000;
+ if (loop->xrun_last_cdelay != XRUN_PROFILE_UNKNOWN)
+ cqueued = ((double)loop->xrun_last_cdelay /
+ (double)loop->capt->rate) * 1000;
+ maxbuf = ((double)loop->play->buffer_size /
+ (double)loop->play->rate) * 1000;
+ proc = (double)loop->xrun_max_proctime / 1000;
+ pfilled = ((double)(loop->xrun_buf_pcount + loop->xrun_out_frames) /
+ (double)loop->play->rate) * 1000;
+ cfilled = ((double)loop->xrun_buf_ccount /
+ (double)loop->capt->rate) * 1000;
+ avail_min = (((double)loop->play->buffer_size -
+ (double)loop->play->avail_min ) /
+ (double)loop->play->rate) * 1000;
+ avail_min = expected - avail_min;
+ if (queued >= 0)
+ missing = last - queued;
+ if (missing >= 0 && loop->xrun_max_missing < missing)
+ loop->xrun_max_missing = missing;
+ loop->xrun_max_proctime = 0;
+ getcurtimestamp(&t);
+ logit(LOG_INFO, " last write before %.4fms, queued %.4fms/%.4fms -> missing %.4fms\n", last, queued, cqueued, missing);
+ logit(LOG_INFO, " expected %.4fms, processing %.4fms, max missing %.4fms\n", expected, proc, loop->xrun_max_missing);
+ logit(LOG_INFO, " last wake %.4fms, last check %.4fms, avail_min %.4fms\n", wake, check, avail_min);
+ logit(LOG_INFO, " max buf %.4fms, pfilled %.4fms, cfilled %.4fms\n", maxbuf, pfilled, cfilled);
+ logit(LOG_INFO, " job started before %.4fms\n", sincejob);
+}
+
+static inline void xrun_stats(struct loopback *loop)
+{
+ if (loop->xrun)
+ xrun_stats0(loop);
+}
+
+static inline snd_pcm_uframes_t buf_avail(struct loopback_handle *lhandle)
+{
+ return lhandle->buf_size - lhandle->buf_count;
+}
+
+static void buf_remove(struct loopback *loop, snd_pcm_uframes_t count)
+{
+ /* remove samples from the capture buffer */
+ if (count <= 0)
+ return;
+ if (loop->play->buf == loop->capt->buf) {
+ if (count < loop->capt->buf_count)
+ loop->capt->buf_count -= count;
+ else
+ loop->capt->buf_count = 0;
+ }
+}
+
+#if 0
+static void buf_add_copy(struct loopback *loop)
+{
+ struct loopback_handle *capt = loop->capt;
+ struct loopback_handle *play = loop->play;
+ snd_pcm_uframes_t count, count1, cpos, ppos;
+
+ count = capt->buf_count;
+ cpos = capt->buf_pos - count;
+ if (cpos > capt->buf_size)
+ cpos += capt->buf_size;
+ ppos = (play->buf_pos + play->buf_count) % play->buf_size;
+ while (count > 0) {
+ count1 = count;
+ if (count1 + cpos > capt->buf_size)
+ count1 = capt->buf_size - cpos;
+ if (count1 > buf_avail(play))
+ count1 = buf_avail(play);
+ if (count1 + ppos > play->buf_size)
+ count1 = play->buf_size - ppos;
+ if (count1 == 0)
+ break;
+ memcpy(play->buf + ppos * play->frame_size,
+ capt->buf + cpos * capt->frame_size,
+ count1 * capt->frame_size);
+ play->buf_count += count1;
+ capt->buf_count -= count1;
+ ppos += count1;
+ ppos %= play->buf_size;
+ cpos += count1;
+ cpos %= capt->buf_size;
+ count -= count1;
+ }
+}
+#endif
+
+#ifdef USE_SAMPLERATE
+static void buf_add_src(struct loopback *loop)
+{
+ struct loopback_handle *capt = loop->capt;
+ struct loopback_handle *play = loop->play;
+ float *old_data_out;
+ snd_pcm_uframes_t count, pos, count1, pos1;
+ count = capt->buf_count;
+ pos = 0;
+ pos1 = capt->buf_pos - count;
+ if (pos1 > capt->buf_size)
+ pos1 += capt->buf_size;
+ while (count > 0) {
+ count1 = count;
+ if (count1 + pos1 > capt->buf_size)
+ count1 = capt->buf_size - pos1;
+ if (capt->format == SND_PCM_FORMAT_S32)
+ src_int_to_float_array((int *)(capt->buf +
+ pos1 * capt->frame_size),
+ loop->src_data.data_in +
+ pos * capt->channels,
+ count1 * capt->channels);
+ else
+ src_short_to_float_array((short *)(capt->buf +
+ pos1 * capt->frame_size),
+ loop->src_data.data_in +
+ pos * capt->channels,
+ count1 * capt->channels);
+ count -= count1;
+ pos += count1;
+ pos1 += count1;
+ pos1 %= capt->buf_size;
+ }
+ loop->src_data.input_frames = pos;
+ loop->src_data.output_frames = play->buf_size -
+ loop->src_out_frames;
+ loop->src_data.end_of_input = 0;
+ old_data_out = loop->src_data.data_out;
+ loop->src_data.data_out = old_data_out + loop->src_out_frames;
+ src_process(loop->src_state, &loop->src_data);
+ loop->src_data.data_out = old_data_out;
+ capt->buf_count -= loop->src_data.input_frames_used;
+ count = loop->src_data.output_frames_gen +
+ loop->src_out_frames;
+ pos = 0;
+ pos1 = (play->buf_pos + play->buf_count) % play->buf_size;
+ while (count > 0) {
+ count1 = count;
+ if (count1 + pos1 > play->buf_size)
+ count1 = play->buf_size - pos1;
+ if (count1 > buf_avail(play))
+ count1 = buf_avail(play);
+ if (count1 == 0)
+ break;
+ if (capt->format == SND_PCM_FORMAT_S32)
+ src_float_to_int_array(loop->src_data.data_out +
+ pos * play->channels,
+ (int *)(play->buf +
+ pos1 * play->frame_size),
+ count1 * play->channels);
+ else
+ src_float_to_short_array(loop->src_data.data_out +
+ pos * play->channels,
+ (short *)(play->buf +
+ pos1 * play->frame_size),
+ count1 * play->channels);
+ play->buf_count += count1;
+ count -= count1;
+ pos += count1;
+ pos1 += count1;
+ pos1 %= play->buf_size;
+ }
+#if 0
+ printf("src: pos = %li, gen = %li, out = %li, count = %li\n",
+ (long)pos, (long)loop->src_data.output_frames_gen,
+ (long)loop->src_out_frames, play->buf_count);
+#endif
+ loop->src_out_frames = (loop->src_data.output_frames_gen +
+ loop->src_out_frames) - pos;
+ if (loop->src_out_frames > 0) {
+ memmove(loop->src_data.data_out,
+ loop->src_data.data_out + pos * play->channels,
+ loop->src_out_frames * play->channels * sizeof(float));
+ }
+}
+#else
+static void buf_add_src(struct loopback *loop)
+{
+}
+#endif
+
+static void buf_add(struct loopback *loop, snd_pcm_uframes_t count)
+{
+ /* copy samples from capture to playback buffer */
+ if (count <= 0)
+ return;
+ if (loop->play->buf == loop->capt->buf) {
+ loop->play->buf_count += count;
+ } else {
+ buf_add_src(loop);
+ }
+}
+
+static int xrun(struct loopback_handle *lhandle)
+{
+ int err;
+
+ if (lhandle == lhandle->loopback->play) {
+ logit(LOG_DEBUG, "underrun for %s\n", lhandle->id);
+ xrun_stats(lhandle->loopback);
+ if ((err = snd_pcm_prepare(lhandle->handle)) < 0)
+ return err;
+ lhandle->xrun_pending = 1;
+ } else {
+ logit(LOG_DEBUG, "overrun for %s\n", lhandle->id);
+ xrun_stats(lhandle->loopback);
+ if ((err = snd_pcm_prepare(lhandle->handle)) < 0)
+ return err;
+ lhandle->xrun_pending = 1;
+ }
+ return 0;
+}
+
+static int suspend(struct loopback_handle *lhandle)
+{
+ int err;
+
+ while ((err = snd_pcm_resume(lhandle->handle)) == -EAGAIN)
+ usleep(1);
+ if (err < 0)
+ return xrun(lhandle);
+ return 0;
+}
+
+static int readit(struct loopback_handle *lhandle)
+{
+ snd_pcm_sframes_t r, res = 0;
+ snd_pcm_sframes_t avail;
+ int err;
+
+ avail = snd_pcm_avail_update(lhandle->handle);
+ if (avail == -EPIPE) {
+ return xrun(lhandle);
+ } else if (avail == -ESTRPIPE) {
+ if ((err = suspend(lhandle)) < 0)
+ return err;
+ }
+ if (avail > buf_avail(lhandle)) {
+ lhandle->buf_over += avail - buf_avail(lhandle);
+ avail = buf_avail(lhandle);
+ } else if (avail == 0) {
+ if (snd_pcm_state(lhandle->handle) == SND_PCM_STATE_DRAINING) {
+ lhandle->loopback->reinit = 1;
+ return 0;
+ }
+ }
+ while (avail > 0) {
+ r = buf_avail(lhandle);
+ if (r + lhandle->buf_pos > lhandle->buf_size)
+ r = lhandle->buf_size - lhandle->buf_pos;
+ if (r > avail)
+ r = avail;
+ r = snd_pcm_readi(lhandle->handle,
+ lhandle->buf +
+ lhandle->buf_pos *
+ lhandle->frame_size, r);
+ if (r == 0)
+ return res;
+ if (r < 0) {
+ if (r == -EPIPE) {
+ err = xrun(lhandle);
+ return res > 0 ? res : err;
+ } else if (r == -ESTRPIPE) {
+ if ((err = suspend(lhandle)) < 0)
+ return res > 0 ? res : err;
+ r = 0;
+ } else {
+ return res > 0 ? res : r;
+ }
+ }
+#ifdef FILE_CWRITE
+ if (lhandle->loopback->cfile)
+ fwrite(lhandle->buf + lhandle->buf_pos * lhandle->frame_size,
+ r, lhandle->frame_size, lhandle->loopback->cfile);
+#endif
+ res += r;
+ if (lhandle->max < res)
+ lhandle->max = res;
+ lhandle->counter += r;
+ lhandle->buf_count += r;
+ lhandle->buf_pos += r;
+ lhandle->buf_pos %= lhandle->buf_size;
+ avail -= r;
+ }
+ return res;
+}
+
+static int writeit(struct loopback_handle *lhandle)
+{
+ snd_pcm_sframes_t avail;
+ snd_pcm_sframes_t r, res = 0;
+ int err;
+
+ __again:
+ avail = snd_pcm_avail_update(lhandle->handle);
+ if (avail == -EPIPE) {
+ if ((err = xrun(lhandle)) < 0)
+ return err;
+ return res;
+ } else if (avail == -ESTRPIPE) {
+ if ((err = suspend(lhandle)) < 0)
+ return err;
+ goto __again;
+ }
+ while (avail > 0 && lhandle->buf_count > 0) {
+ r = lhandle->buf_count;
+ if (r + lhandle->buf_pos > lhandle->buf_size)
+ r = lhandle->buf_size - lhandle->buf_pos;
+ if (r > avail)
+ r = avail;
+ r = snd_pcm_writei(lhandle->handle,
+ lhandle->buf +
+ lhandle->buf_pos *
+ lhandle->frame_size, r);
+ if (r <= 0) {
+ if (r == -EPIPE) {
+ if ((err = xrun(lhandle)) < 0)
+ return err;
+ return res;
+ } else if (r == -ESTRPIPE) {
+ }
+ return res > 0 ? res : r;
+ }
+#ifdef FILE_PWRITE
+ if (lhandle->loopback->pfile)
+ fwrite(lhandle->buf + lhandle->buf_pos * lhandle->frame_size,
+ r, lhandle->frame_size, lhandle->loopback->pfile);
+#endif
+ res += r;
+ lhandle->counter += r;
+ lhandle->buf_count -= r;
+ lhandle->buf_pos += r;
+ lhandle->buf_pos %= lhandle->buf_size;
+ xrun_profile(lhandle->loopback);
+ if (lhandle->loopback->stop_pending) {
+ lhandle->loopback->stop_count += r;
+ if (lhandle->loopback->stop_count * lhandle->pitch >
+ lhandle->loopback->latency * 3) {
+ lhandle->loopback->stop_pending = 0;
+ lhandle->loopback->reinit = 1;
+ break;
+ }
+ }
+ }
+ return res;
+}
+
+static snd_pcm_sframes_t remove_samples(struct loopback *loop,
+ int capture_preferred,
+ snd_pcm_sframes_t count)
+{
+ struct loopback_handle *play = loop->play;
+ struct loopback_handle *capt = loop->capt;
+
+ if (loop->play->buf == loop->capt->buf) {
+ if (count > loop->play->buf_count)
+ count = loop->play->buf_count;
+ if (count > loop->capt->buf_count)
+ count = loop->capt->buf_count;
+ capt->buf_count -= count;
+ play->buf_pos += count;
+ play->buf_pos %= play->buf_size;
+ play->buf_count -= count;
+ return count;
+ }
+ if (capture_preferred) {
+ if (count > capt->buf_count)
+ count = capt->buf_count;
+ capt->buf_count -= count;
+ } else {
+ if (count > play->buf_count)
+ count = play->buf_count;
+ play->buf_count -= count;
+ }
+ return count;
+}
+
+static int xrun_sync(struct loopback *loop)
+{
+ struct loopback_handle *play = loop->play;
+ struct loopback_handle *capt = loop->capt;
+ snd_pcm_uframes_t fill = get_whole_latency(loop);
+ snd_pcm_sframes_t pdelay, cdelay, delay1, pdelay1, cdelay1, diff;
+ int err;
+
+ __again:
+ if (verbose > 5)
+ snd_output_printf(loop->output, "%s: xrun sync %i %i\n", loop->id, capt->xrun_pending, play->xrun_pending);
+ if (capt->xrun_pending) {
+ __pagain:
+ capt->xrun_pending = 0;
+ if ((err = snd_pcm_prepare(capt->handle)) < 0) {
+ logit(LOG_CRIT, "%s prepare failed: %s\n", capt->id, snd_strerror(err));
+ return err;
+ }
+ if ((err = snd_pcm_start(capt->handle)) < 0) {
+ logit(LOG_CRIT, "%s start failed: %s\n", capt->id, snd_strerror(err));
+ return err;
+ }
+ } else {
+ diff = readit(capt);
+ buf_add(loop, diff);
+ if (capt->xrun_pending)
+ goto __pagain;
+ }
+ /* skip additional playback samples */
+ if ((err = snd_pcm_delay(capt->handle, &cdelay)) < 0) {
+ if (err == -EPIPE) {
+ capt->xrun_pending = 1;
+ goto __again;
+ }
+ if (err == -ESTRPIPE) {
+ err = suspend(capt);
+ if (err < 0)
+ return err;
+ goto __again;
+ }
+ logit(LOG_CRIT, "%s capture delay failed: %s\n", capt->id, snd_strerror(err));
+ return err;
+ }
+ if ((err = snd_pcm_delay(play->handle, &pdelay)) < 0) {
+ if (err == -EPIPE) {
+ pdelay = 0;
+ play->xrun_pending = 1;
+ } else if (err == -ESTRPIPE) {
+ err = suspend(play);
+ if (err < 0)
+ return err;
+ goto __again;
+ } else {
+ logit(LOG_CRIT, "%s playback delay failed: %s\n", play->id, snd_strerror(err));
+ return err;
+ }
+ }
+ capt->counter = cdelay;
+ play->counter = pdelay;
+ if (play->buf != capt->buf)
+ cdelay += capt->buf_count;
+ pdelay += play->buf_count;
+#ifdef USE_SAMPLERATE
+ pdelay += loop->src_out_frames;
+#endif
+ cdelay1 = cdelay * capt->pitch;
+ pdelay1 = pdelay * play->pitch;
+ delay1 = cdelay1 + pdelay1;
+ capt->total_queued = 0;
+ play->total_queued = 0;
+ loop->total_queued_count = 0;
+ loop->pitch_diff = loop->pitch_diff_min = loop->pitch_diff_max = 0;
+ if (verbose > 6) {
+ snd_output_printf(loop->output,
+ "sync: cdelay=%li(%li), pdelay=%li(%li), fill=%li (delay=%li)"
+#ifdef USE_SAMPLERATE
+ ", src_out=%li"
+#endif
+ "\n",
+ (long)cdelay, (long)cdelay1, (long)pdelay, (long)pdelay1,
+ (long)fill, (long)delay1
+#ifdef USE_SAMPLERATE
+ , (long)loop->src_out_frames
+#endif
+ );
+ snd_output_printf(loop->output,
+ "sync: cbufcount=%li, pbufcount=%li\n",
+ (long)capt->buf_count, (long)play->buf_count);
+ }
+ if (delay1 > fill && capt->counter > 0) {
+ if ((err = snd_pcm_drop(capt->handle)) < 0)
+ return err;
+ if ((err = snd_pcm_prepare(capt->handle)) < 0)
+ return err;
+ if ((err = snd_pcm_start(capt->handle)) < 0)
+ return err;
+ diff = remove_samples(loop, 1, (delay1 - fill) / capt->pitch);
+ if (verbose > 6)
+ snd_output_printf(loop->output,
+ "sync: capt stop removed %li samples\n", (long)diff);
+ goto __again;
+ }
+ if (delay1 > fill) {
+ diff = (delay1 - fill) / play->pitch;
+ if (diff > play->buf_count)
+ diff = play->buf_count;
+ if (verbose > 6)
+ snd_output_printf(loop->output,
+ "sync: removing %li playback samples, delay1=%li\n", (long)diff, (long)delay1);
+ diff = remove_samples(loop, 0, diff);
+ pdelay -= diff;
+ pdelay1 = pdelay * play->pitch;
+ delay1 = cdelay1 + pdelay1;
+ if (verbose > 6)
+ snd_output_printf(loop->output,
+ "sync: removed %li playback samples, delay1=%li\n", (long)diff, (long)delay1);
+ }
+ if (delay1 > fill) {
+ diff = (delay1 - fill) / capt->pitch;
+ if (diff > capt->buf_count)
+ diff = capt->buf_count;
+ if (verbose > 6)
+ snd_output_printf(loop->output,
+ "sync: removing %li captured samples, delay1=%li\n", (long)diff, (long)delay1);
+ diff -= remove_samples(loop, 1, diff);
+ cdelay -= diff;
+ cdelay1 = cdelay * capt->pitch;
+ delay1 = cdelay1 + pdelay1;
+ if (verbose > 6)
+ snd_output_printf(loop->output,
+ "sync: removed %li captured samples, delay1=%li\n", (long)diff, (long)delay1);
+ }
+ if (play->xrun_pending) {
+ play->xrun_pending = 0;
+ diff = (fill - delay1) / play->pitch;
+ if (verbose > 6)
+ snd_output_printf(loop->output,
+ "sync: xrun_pending, silence filling %li / buf_count=%li\n", (long)diff, play->buf_count);
+ if (fill > delay1 && play->buf_count < diff) {
+ diff = diff - play->buf_count;
+ if (verbose > 6)
+ snd_output_printf(loop->output,
+ "sync: playback silence added %li samples\n", (long)diff);
+ play->buf_pos -= diff;
+ play->buf_pos %= play->buf_size;
+ if ((err = snd_pcm_format_set_silence(play->format, play->buf + play->buf_pos * play->channels, diff)) < 0)
+ return err;
+ play->buf_count += diff;
+ }
+ if ((err = snd_pcm_prepare(play->handle)) < 0) {
+ logit(LOG_CRIT, "%s prepare failed: %s\n", play->id, snd_strerror(err));
+
+ return err;
+ }
+ delay1 = writeit(play);
+ if (verbose > 6)
+ snd_output_printf(loop->output,
+ "sync: playback wrote %li samples\n", (long)delay1);
+ if (delay1 > diff) {
+ buf_remove(loop, delay1 - diff);
+ if (verbose > 6)
+ snd_output_printf(loop->output,
+ "sync: playback buf_remove %li samples\n", (long)(delay1 - diff));
+ }
+ if ((err = snd_pcm_start(play->handle)) < 0) {
+ logit(LOG_CRIT, "%s start failed: %s\n", play->id, snd_strerror(err));
+ return err;
+ }
+ }
+ if (verbose > 5) {
+ snd_output_printf(loop->output, "%s: xrun sync ok\n", loop->id);
+ if (verbose > 6) {
+ if (snd_pcm_delay(capt->handle, &cdelay) < 0)
+ cdelay = -1;
+ if (snd_pcm_delay(play->handle, &pdelay) < 0)
+ pdelay = -1;
+ if (play->buf != capt->buf)
+ cdelay += capt->buf_count;
+ pdelay += play->buf_count;
+#ifdef USE_SAMPLERATE
+ pdelay += loop->src_out_frames;
+#endif
+ cdelay1 = cdelay * capt->pitch;
+ pdelay1 = pdelay * play->pitch;
+ delay1 = cdelay1 + pdelay1;
+ snd_output_printf(loop->output, "%s: sync verify: %li\n", loop->id, delay1);
+ }
+ }
+ loop->xrun_max_proctime = 0;
+ return 0;
+}
+
+static int set_notify(struct loopback_handle *lhandle, int enable)
+{
+ int err;
+
+ if (lhandle->ctl_notify == NULL)
+ return 0;
+ snd_ctl_elem_value_set_boolean(lhandle->ctl_notify, 0, enable);
+ err = snd_ctl_elem_write(lhandle->ctl, lhandle->ctl_notify);
+ if (err < 0) {
+ logit(LOG_CRIT, "Cannot set PCM Notify element for %s: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ err = snd_ctl_elem_read(lhandle->ctl, lhandle->ctl_notify);
+ if (err < 0) {
+ logit(LOG_CRIT, "Cannot get PCM Notify element for %s: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ return 0;
+}
+
+static int set_rate_shift(struct loopback_handle *lhandle, double pitch)
+{
+ int err;
+
+ if (lhandle->ctl_rate_shift == NULL)
+ return 0;
+ snd_ctl_elem_value_set_integer(lhandle->ctl_rate_shift, 0, pitch * 100000);
+ err = snd_ctl_elem_write(lhandle->ctl, lhandle->ctl_rate_shift);
+ if (err < 0) {
+ logit(LOG_CRIT, "Cannot set PCM Rate Shift element for %s: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ return 0;
+}
+
+void update_pitch(struct loopback *loop)
+{
+ double pitch = loop->pitch;
+
+#ifdef USE_SAMPLERATE
+ if (loop->sync == SYNC_TYPE_SAMPLERATE) {
+ loop->src_data.src_ratio = (double)1.0 / (pitch *
+ loop->play->pitch * loop->capt->pitch);
+ if (verbose > 2)
+ snd_output_printf(loop->output, "%s: Samplerate src_ratio update1: %.8f\n", loop->id, loop->src_data.src_ratio);
+ } else
+#endif
+ if (loop->sync == SYNC_TYPE_CAPTRATESHIFT) {
+ set_rate_shift(loop->capt, pitch);
+#ifdef USE_SAMPLERATE
+ if (loop->use_samplerate) {
+ loop->src_data.src_ratio =
+ (double)1.0 /
+ (loop->play->pitch * loop->capt->pitch);
+ if (verbose > 2)
+ snd_output_printf(loop->output, "%s: Samplerate src_ratio update2: %.8f\n", loop->id, loop->src_data.src_ratio);
+ }
+#endif
+ }
+ else if (loop->sync == SYNC_TYPE_PLAYRATESHIFT) {
+ set_rate_shift(loop->play, pitch);
+#ifdef USE_SAMPLERATE
+ if (loop->use_samplerate) {
+ loop->src_data.src_ratio =
+ (double)1.0 /
+ (loop->play->pitch * loop->capt->pitch);
+ if (verbose > 2)
+ snd_output_printf(loop->output, "%s: Samplerate src_ratio update3: %.8f\n", loop->id, loop->src_data.src_ratio);
+ }
+#endif
+ }
+ if (verbose)
+ snd_output_printf(loop->output, "New pitch for %s: %.8f (min/max samples = %li/%li)\n", loop->id, pitch, loop->pitch_diff_min, loop->pitch_diff_max);
+}
+
+static int get_active(struct loopback_handle *lhandle)
+{
+ int err;
+
+ if (lhandle->ctl_active == NULL)
+ return 0;
+ err = snd_ctl_elem_read(lhandle->ctl, lhandle->ctl_active);
+ if (err < 0) {
+ logit(LOG_CRIT, "Cannot get PCM Slave Active element for %s: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ return snd_ctl_elem_value_get_boolean(lhandle->ctl_active, 0);
+}
+
+static int get_format(struct loopback_handle *lhandle)
+{
+ int err;
+
+ if (lhandle->ctl_format == NULL)
+ return 0;
+ err = snd_ctl_elem_read(lhandle->ctl, lhandle->ctl_format);
+ if (err < 0) {
+ logit(LOG_CRIT, "Cannot get PCM Format element for %s: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ return snd_ctl_elem_value_get_integer(lhandle->ctl_format, 0);
+}
+
+static int get_rate(struct loopback_handle *lhandle)
+{
+ int err;
+
+ if (lhandle->ctl_rate == NULL)
+ return 0;
+ err = snd_ctl_elem_read(lhandle->ctl, lhandle->ctl_rate);
+ if (err < 0) {
+ logit(LOG_CRIT, "Cannot get PCM Rate element for %s: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ return snd_ctl_elem_value_get_integer(lhandle->ctl_rate, 0);
+}
+
+static int get_channels(struct loopback_handle *lhandle)
+{
+ int err;
+
+ if (lhandle->ctl_channels == NULL)
+ return 0;
+ err = snd_ctl_elem_read(lhandle->ctl, lhandle->ctl_channels);
+ if (err < 0) {
+ logit(LOG_CRIT, "Cannot get PCM Channels element for %s: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ return snd_ctl_elem_value_get_integer(lhandle->ctl_channels, 0);
+}
+
+static void openctl_elem(struct loopback_handle *lhandle,
+ int device, int subdevice,
+ const char *name,
+ snd_ctl_elem_value_t **elem)
+{
+ int err;
+
+ if (snd_ctl_elem_value_malloc(elem) < 0) {
+ *elem = NULL;
+ } else {
+ snd_ctl_elem_value_set_interface(*elem,
+ SND_CTL_ELEM_IFACE_PCM);
+ snd_ctl_elem_value_set_device(*elem, device);
+ snd_ctl_elem_value_set_subdevice(*elem, subdevice);
+ snd_ctl_elem_value_set_name(*elem, name);
+ err = snd_ctl_elem_read(lhandle->ctl, *elem);
+ if (err < 0) {
+ snd_ctl_elem_value_free(*elem);
+ *elem = NULL;
+ }
+ }
+}
+
+static int openctl(struct loopback_handle *lhandle, int device, int subdevice)
+{
+ int err;
+
+ lhandle->ctl_rate_shift = NULL;
+ if (lhandle->loopback->play == lhandle) {
+ if (lhandle->loopback->controls)
+ goto __events;
+ return 0;
+ }
+ openctl_elem(lhandle, device, subdevice, "PCM Notify",
+ &lhandle->ctl_notify);
+ openctl_elem(lhandle, device, subdevice, "PCM Rate Shift 100000",
+ &lhandle->ctl_rate_shift);
+ set_rate_shift(lhandle, 1);
+ openctl_elem(lhandle, device, subdevice, "PCM Slave Active",
+ &lhandle->ctl_active);
+ openctl_elem(lhandle, device, subdevice, "PCM Slave Format",
+ &lhandle->ctl_format);
+ openctl_elem(lhandle, device, subdevice, "PCM Slave Rate",
+ &lhandle->ctl_rate);
+ openctl_elem(lhandle, device, subdevice, "PCM Slave Channels",
+ &lhandle->ctl_channels);
+ if ((lhandle->ctl_active &&
+ lhandle->ctl_format &&
+ lhandle->ctl_rate &&
+ lhandle->ctl_channels) ||
+ lhandle->loopback->controls) {
+ __events:
+ if ((err = snd_ctl_poll_descriptors_count(lhandle->ctl)) < 0)
+ lhandle->ctl_pollfd_count = 0;
+ else
+ lhandle->ctl_pollfd_count = err;
+ if (snd_ctl_subscribe_events(lhandle->ctl, 1) < 0)
+ lhandle->ctl_pollfd_count = 0;
+ }
+ return 0;
+}
+
+static int openit(struct loopback_handle *lhandle)
+{
+ snd_pcm_info_t *info;
+ int stream = lhandle == lhandle->loopback->play ?
+ SND_PCM_STREAM_PLAYBACK :
+ SND_PCM_STREAM_CAPTURE;
+ int err, card, device, subdevice;
+ pcm_open_lock();
+ err = snd_pcm_open(&lhandle->handle, lhandle->device, stream, SND_PCM_NONBLOCK);
+ pcm_open_unlock();
+ if (err < 0) {
+ logit(LOG_CRIT, "%s open error: %s\n", lhandle->id, snd_strerror(err));
+ return err;
+ }
+ if ((err = snd_pcm_info_malloc(&info)) < 0)
+ return err;
+ if ((err = snd_pcm_info(lhandle->handle, info)) < 0) {
+ snd_pcm_info_free(info);
+ return err;
+ }
+ card = snd_pcm_info_get_card(info);
+ device = snd_pcm_info_get_device(info);
+ subdevice = snd_pcm_info_get_subdevice(info);
+ snd_pcm_info_free(info);
+ lhandle->card_number = card;
+ lhandle->ctl = NULL;
+ if (card >= 0 || lhandle->ctldev) {
+ char name[16], *dev = lhandle->ctldev;
+ if (dev == NULL) {
+ sprintf(name, "hw:%i", card);
+ dev = name;
+ }
+ pcm_open_lock();
+ err = snd_ctl_open(&lhandle->ctl, dev, SND_CTL_NONBLOCK);
+ pcm_open_unlock();
+ if (err < 0) {
+ logit(LOG_CRIT, "%s [%s] ctl open error: %s\n", lhandle->id, dev, snd_strerror(err));
+ lhandle->ctl = NULL;
+ }
+ if (lhandle->ctl)
+ openctl(lhandle, device, subdevice);
+ }
+ return 0;
+}
+
+static int freeit(struct loopback_handle *lhandle)
+{
+ free(lhandle->buf);
+ lhandle->buf = NULL;
+ return 0;
+}
+
+static int closeit(struct loopback_handle *lhandle)
+{
+ int err = 0;
+
+ set_rate_shift(lhandle, 1);
+ if (lhandle->ctl_rate_shift)
+ snd_ctl_elem_value_free(lhandle->ctl_rate_shift);
+ lhandle->ctl_rate_shift = NULL;
+ if (lhandle->ctl)
+ err = snd_ctl_close(lhandle->ctl);
+ lhandle->ctl = NULL;
+ if (lhandle->handle)
+ err = snd_pcm_close(lhandle->handle);
+ lhandle->handle = NULL;
+ return err;
+}
+
+static int init_handle(struct loopback_handle *lhandle, int alloc)
+{
+ snd_pcm_uframes_t lat;
+ lhandle->frame_size = (snd_pcm_format_width(lhandle->format) / 8) *
+ lhandle->channels;
+ lhandle->sync_point = lhandle->rate * 15; /* every 15 seconds */
+ lat = lhandle->loopback->latency;
+ if (lhandle->buffer_size > lat)
+ lat = lhandle->buffer_size;
+ lhandle->buf_size = lat * 2;
+ if (alloc) {
+ lhandle->buf = calloc(1, lhandle->buf_size * lhandle->frame_size);
+ if (lhandle->buf == NULL)
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+int pcmjob_init(struct loopback *loop)
+{
+ int err;
+ char id[128];
+
+#ifdef FILE_CWRITE
+ loop->cfile = fopen(FILE_CWRITE, "w+");
+#endif
+#ifdef FILE_PWRITE
+ loop->pfile = fopen(FILE_PWRITE, "w+");
+#endif
+ if ((err = openit(loop->play)) < 0)
+ goto __error;
+ if ((err = openit(loop->capt)) < 0)
+ goto __error;
+ snprintf(id, sizeof(id), "%s/%s", loop->play->id, loop->capt->id);
+ id[sizeof(id)-1] = '\0';
+ loop->id = strdup(id);
+ if (loop->sync == SYNC_TYPE_AUTO && loop->capt->ctl_rate_shift)
+ loop->sync = SYNC_TYPE_CAPTRATESHIFT;
+ if (loop->sync == SYNC_TYPE_AUTO && loop->play->ctl_rate_shift)
+ loop->sync = SYNC_TYPE_PLAYRATESHIFT;
+#ifdef USE_SAMPLERATE
+ if (loop->sync == SYNC_TYPE_AUTO && loop->src_enable)
+ loop->sync = SYNC_TYPE_SAMPLERATE;
+#endif
+ if (loop->sync == SYNC_TYPE_AUTO)
+ loop->sync = SYNC_TYPE_SIMPLE;
+ if (loop->slave == SLAVE_TYPE_AUTO &&
+ loop->capt->ctl_notify &&
+ loop->capt->ctl_active &&
+ loop->capt->ctl_format &&
+ loop->capt->ctl_rate &&
+ loop->capt->ctl_channels)
+ loop->slave = SLAVE_TYPE_ON;
+ if (loop->slave == SLAVE_TYPE_ON) {
+ err = set_notify(loop->capt, 1);
+ if (err < 0)
+ goto __error;
+ if (loop->capt->ctl_notify == NULL ||
+ snd_ctl_elem_value_get_boolean(loop->capt->ctl_notify, 0) == 0) {
+ logit(LOG_CRIT, "unable to enable slave mode for %s\n", loop->id);
+ err = -EINVAL;
+ goto __error;
+ }
+ }
+ err = control_init(loop);
+ if (err < 0)
+ goto __error;
+ return 0;
+ __error:
+ pcmjob_done(loop);
+ return err;
+}
+
+static void freeloop(struct loopback *loop)
+{
+#ifdef USE_SAMPLERATE
+ if (loop->use_samplerate) {
+ if (loop->src_state)
+ src_delete(loop->src_state);
+ loop->src_state = NULL;
+ free(loop->src_data.data_in);
+ loop->src_data.data_in = NULL;
+ free(loop->src_data.data_out);
+ loop->src_data.data_out = NULL;
+ }
+#endif
+ if (loop->play->buf == loop->capt->buf)
+ loop->play->buf = NULL;
+ freeit(loop->play);
+ freeit(loop->capt);
+}
+
+int pcmjob_done(struct loopback *loop)
+{
+ control_done(loop);
+ closeit(loop->play);
+ closeit(loop->capt);
+ freeloop(loop);
+ free(loop->id);
+ loop->id = NULL;
+#ifdef FILE_PWRITE
+ if (loop->pfile) {
+ fclose(loop->pfile);
+ loop->pfile = NULL;
+ }
+#endif
+#ifdef FILE_CWRITE
+ if (loop->cfile) {
+ fclose(loop->cfile);
+ loop->cfile = NULL;
+ }
+#endif
+ return 0;
+}
+
+static void lhandle_start(struct loopback_handle *lhandle)
+{
+ lhandle->buf_pos = 0;
+ lhandle->buf_count = 0;
+ lhandle->counter = 0;
+ lhandle->total_queued = 0;
+}
+
+int pcmjob_start(struct loopback *loop)
+{
+ snd_pcm_uframes_t count;
+ int err;
+
+ loop->pollfd_count = loop->play->ctl_pollfd_count +
+ loop->capt->ctl_pollfd_count;
+ if ((err = snd_pcm_poll_descriptors_count(loop->play->handle)) < 0)
+ goto __error;
+ loop->play->pollfd_count = err;
+ loop->pollfd_count += err;
+ if ((err = snd_pcm_poll_descriptors_count(loop->capt->handle)) < 0)
+ goto __error;
+ loop->capt->pollfd_count = err;
+ loop->pollfd_count += err;
+ if (loop->slave == SLAVE_TYPE_ON) {
+ err = get_active(loop->capt);
+ if (err < 0)
+ goto __error;
+ if (err == 0) /* stream is not active */
+ return 0;
+ err = get_format(loop->capt);
+ if (err < 0)
+ goto __error;
+ loop->play->format = loop->capt->format = err;
+ err = get_rate(loop->capt);
+ if (err < 0)
+ goto __error;
+ loop->play->rate_req = loop->capt->rate_req = err;
+ err = get_channels(loop->capt);
+ if (err < 0)
+ goto __error;
+ loop->play->channels = loop->capt->channels = err;
+ }
+ loop->reinit = 0;
+ loop->use_samplerate = 0;
+ if (loop->latency_req) {
+ loop->latency_reqtime = frames_to_time(loop->play->rate_req,
+ loop->latency_req);
+ loop->latency_req = 0;
+ }
+ loop->latency = time_to_frames(loop->play->rate_req, loop->latency_reqtime);
+ if ((err = setparams(loop, loop->latency/2)) < 0)
+ goto __error;
+ if (verbose)
+ showlatency(loop->output, loop->latency, loop->play->rate_req, "Latency");
+ if (loop->play->access == loop->capt->access &&
+ loop->play->format == loop->capt->format &&
+ loop->play->rate == loop->capt->rate &&
+ loop->play->channels == loop->play->channels &&
+ loop->sync != SYNC_TYPE_SAMPLERATE) {
+ if (verbose > 1)
+ snd_output_printf(loop->output, "shared buffer!!!\n");
+ if ((err = init_handle(loop->play, 1)) < 0)
+ goto __error;
+ if ((err = init_handle(loop->capt, 0)) < 0)
+ goto __error;
+ if (loop->play->buf_size < loop->capt->buf_size) {
+ char *nbuf = realloc(loop->play->buf,
+ loop->capt->buf_size *
+ loop->capt->frame_size);
+ if (nbuf == NULL) {
+ err = -ENOMEM;
+ goto __error;
+ }
+ loop->play->buf = nbuf;
+ loop->play->buf_size = loop->capt->buf_size;
+ } else if (loop->capt->buf_size < loop->play->buf_size) {
+ char *nbuf = realloc(loop->capt->buf,
+ loop->play->buf_size *
+ loop->play->frame_size);
+ if (nbuf == NULL) {
+ err = -ENOMEM;
+ goto __error;
+ }
+ loop->capt->buf = nbuf;
+ loop->capt->buf_size = loop->play->buf_size;
+ }
+ loop->capt->buf = loop->play->buf;
+ } else {
+ if ((err = init_handle(loop->play, 1)) < 0)
+ goto __error;
+ if ((err = init_handle(loop->capt, 1)) < 0)
+ goto __error;
+ if (loop->play->rate_req != loop->play->rate)
+ loop->use_samplerate = 1;
+ if (loop->capt->rate_req != loop->capt->rate)
+ loop->use_samplerate = 1;
+ }
+#ifdef USE_SAMPLERATE
+ if (loop->sync == SYNC_TYPE_SAMPLERATE)
+ loop->use_samplerate = 1;
+ if (loop->use_samplerate && !loop->src_enable) {
+ logit(LOG_CRIT, "samplerate conversion required but disabled\n");
+ loop->use_samplerate = 0;
+ err = -EIO;
+ goto __error;
+ }
+ if (loop->use_samplerate) {
+ if ((loop->capt->format != SND_PCM_FORMAT_S16 ||
+ loop->play->format != SND_PCM_FORMAT_S16) &&
+ (loop->capt->format != SND_PCM_FORMAT_S32 ||
+ loop->play->format != SND_PCM_FORMAT_S32)) {
+ logit(LOG_CRIT, "samplerate conversion supports only %s or %s formats (play=%s, capt=%s)\n", snd_pcm_format_name(SND_PCM_FORMAT_S16), snd_pcm_format_name(SND_PCM_FORMAT_S32), snd_pcm_format_name(loop->play->format), snd_pcm_format_name(loop->capt->format));
+ loop->use_samplerate = 0;
+ err = -EIO;
+ goto __error;
+ }
+ loop->src_state = src_new(loop->src_converter_type,
+ loop->play->channels, &err);
+ loop->src_data.data_in = calloc(1, sizeof(float)*loop->capt->channels*loop->capt->buf_size);
+ if (loop->src_data.data_in == NULL) {
+ err = -ENOMEM;
+ goto __error;
+ }
+ loop->src_data.data_out = calloc(1, sizeof(float)*loop->play->channels*loop->play->buf_size);
+ if (loop->src_data.data_out == NULL) {
+ err = -ENOMEM;
+ goto __error;
+ }
+ loop->src_data.src_ratio = (double)loop->play->rate /
+ (double)loop->capt->rate;
+ loop->src_data.end_of_input = 0;
+ loop->src_out_frames = 0;
+ } else {
+ loop->src_state = NULL;
+ }
+#else
+ if (loop->sync == SYNC_TYPE_SAMPLERATE || loop->use_samplerate) {
+ logit(LOG_CRIT, "alsaloop is compiled without libsamplerate support\n");
+ err = -EIO;
+ goto __error;
+ }
+#endif
+ if (verbose) {
+ snd_output_printf(loop->output, "%s sync type: %s", loop->id, sync_types[loop->sync]);
+#ifdef USE_SAMPLERATE
+ if (loop->sync == SYNC_TYPE_SAMPLERATE)
+ snd_output_printf(loop->output, " (%s)", src_types[loop->src_converter_type]);
+#endif
+ snd_output_printf(loop->output, "\n");
+ }
+ lhandle_start(loop->play);
+ lhandle_start(loop->capt);
+ if ((err = snd_pcm_format_set_silence(loop->play->format,
+ loop->play->buf,
+ loop->play->buf_size * loop->play->channels)) < 0) {
+ logit(LOG_CRIT, "%s: silence error\n", loop->id);
+ goto __error;
+ }
+ if (verbose > 4)
+ snd_output_printf(loop->output, "%s: capt->buffer_size = %li, play->buffer_size = %li\n", loop->id, loop->capt->buf_size, loop->play->buf_size);
+ loop->pitch = 1.0;
+ update_pitch(loop);
+ loop->pitch_delta = 1.0 / ((double)loop->capt->rate * 4);
+ loop->total_queued_count = 0;
+ loop->pitch_diff = 0;
+ count = get_whole_latency(loop) / loop->play->pitch;
+ loop->play->buf_count = count;
+ if (loop->play->buf == loop->capt->buf)
+ loop->capt->buf_pos = count;
+ err = writeit(loop->play);
+ if (verbose > 4)
+ snd_output_printf(loop->output, "%s: silence queued %i samples\n", loop->id, err);
+ if (count > loop->play->buffer_size)
+ count = loop->play->buffer_size;
+ if (err != count) {
+ logit(LOG_CRIT, "%s: initial playback fill error (%i/%i/%i)\n", loop->id, err, (int)count, loop->play->buffer_size);
+ err = -EIO;
+ goto __error;
+ }
+ loop->running = 1;
+ loop->stop_pending = 0;
+ if (loop->xrun) {
+ getcurtimestamp(&loop->xrun_last_update);
+ loop->xrun_last_pdelay = XRUN_PROFILE_UNKNOWN;
+ loop->xrun_last_cdelay = XRUN_PROFILE_UNKNOWN;
+ loop->xrun_max_proctime = 0;
+ }
+ if ((err = snd_pcm_start(loop->capt->handle)) < 0) {
+ logit(LOG_CRIT, "pcm start %s error: %s\n", loop->capt->id, snd_strerror(err));
+ goto __error;
+ }
+ if (!loop->linked) {
+ if ((err = snd_pcm_start(loop->play->handle)) < 0) {
+ logit(LOG_CRIT, "pcm start %s error: %s\n", loop->play->id, snd_strerror(err));
+ goto __error;
+ }
+ }
+ return 0;
+ __error:
+ pcmjob_stop(loop);
+ return err;
+}
+
+int pcmjob_stop(struct loopback *loop)
+{
+ int err;
+
+ if (loop->running) {
+ if ((err = snd_pcm_drop(loop->capt->handle)) < 0)
+ logit(LOG_WARNING, "pcm drop %s error: %s\n", loop->capt->id, snd_strerror(err));
+ if ((err = snd_pcm_drop(loop->play->handle)) < 0)
+ logit(LOG_WARNING, "pcm drop %s error: %s\n", loop->play->id, snd_strerror(err));
+ if ((err = snd_pcm_hw_free(loop->capt->handle)) < 0)
+ logit(LOG_WARNING, "pcm hw_free %s error: %s\n", loop->capt->id, snd_strerror(err));
+ if ((err = snd_pcm_hw_free(loop->play->handle)) < 0)
+ logit(LOG_WARNING, "pcm hw_free %s error: %s\n", loop->play->id, snd_strerror(err));
+ loop->running = 0;
+ }
+ freeloop(loop);
+ return 0;
+}
+
+int pcmjob_pollfds_init(struct loopback *loop, struct pollfd *fds)
+{
+ int err, idx = 0;
+
+ if (loop->running) {
+ err = snd_pcm_poll_descriptors(loop->play->handle, fds + idx, loop->play->pollfd_count);
+ if (err < 0)
+ return err;
+ idx += loop->play->pollfd_count;
+ err = snd_pcm_poll_descriptors(loop->capt->handle, fds + idx, loop->capt->pollfd_count);
+ if (err < 0)
+ return err;
+ idx += loop->capt->pollfd_count;
+ }
+ if (loop->play->ctl_pollfd_count > 0 &&
+ (loop->slave == SLAVE_TYPE_ON || loop->controls)) {
+ err = snd_ctl_poll_descriptors(loop->play->ctl, fds + idx, loop->play->ctl_pollfd_count);
+ if (err < 0)
+ return err;
+ idx += loop->play->ctl_pollfd_count;
+ }
+ if (loop->capt->ctl_pollfd_count > 0 &&
+ (loop->slave == SLAVE_TYPE_ON || loop->controls)) {
+ err = snd_ctl_poll_descriptors(loop->capt->ctl, fds + idx, loop->capt->ctl_pollfd_count);
+ if (err < 0)
+ return err;
+ idx += loop->capt->ctl_pollfd_count;
+ }
+ loop->active_pollfd_count = idx;
+ return idx;
+}
+
+static snd_pcm_sframes_t get_queued_playback_samples(struct loopback *loop)
+{
+ snd_pcm_sframes_t delay;
+ int err;
+
+ if ((err = snd_pcm_delay(loop->play->handle, &delay)) < 0)
+ return 0;
+ loop->play->last_delay = delay;
+ delay += loop->play->buf_count;
+#ifdef USE_SAMPLERATE
+ delay += loop->src_out_frames;
+#endif
+ return delay;
+}
+
+static snd_pcm_sframes_t get_queued_capture_samples(struct loopback *loop)
+{
+ snd_pcm_sframes_t delay;
+ int err;
+
+ if ((err = snd_pcm_delay(loop->capt->handle, &delay)) < 0)
+ return 0;
+ loop->capt->last_delay = delay;
+ delay += loop->capt->buf_count;
+ return delay;
+}
+
+static int ctl_event_check(snd_ctl_elem_value_t *val, snd_ctl_event_t *ev)
+{
+ snd_ctl_elem_id_t *id1, *id2;
+ snd_ctl_elem_id_alloca(&id1);
+ snd_ctl_elem_id_alloca(&id2);
+ snd_ctl_elem_value_get_id(val, id1);
+ snd_ctl_event_elem_get_id(ev, id2);
+ if (snd_ctl_event_elem_get_mask(ev) == SND_CTL_EVENT_MASK_REMOVE)
+ return 0;
+ if ((snd_ctl_event_elem_get_mask(ev) & SND_CTL_EVENT_MASK_VALUE) == 0)
+ return 0;
+ return control_id_match(id1, id2);
+}
+
+static int handle_ctl_events(struct loopback_handle *lhandle,
+ unsigned short events)
+{
+ struct loopback *loop = lhandle->loopback;
+ snd_ctl_event_t *ev;
+ int err, restart = 0;
+
+ snd_ctl_event_alloca(&ev);
+ while ((err = snd_ctl_read(lhandle->ctl, ev)) != 0 && err != -EAGAIN) {
+ if (err < 0)
+ break;
+ if (snd_ctl_event_get_type(ev) != SND_CTL_EVENT_ELEM)
+ continue;
+ if (lhandle == loop->play)
+ goto __ctl_check;
+ if (verbose > 6)
+ snd_output_printf(loop->output, "%s: ctl event!!!! %s\n", lhandle->id, snd_ctl_event_elem_get_name(ev));
+ if (ctl_event_check(lhandle->ctl_active, ev)) {
+ continue;
+ } else if (ctl_event_check(lhandle->ctl_format, ev)) {
+ err = get_format(lhandle);
+ if (lhandle->format != err)
+ restart = 1;
+ continue;
+ } else if (ctl_event_check(lhandle->ctl_rate, ev)) {
+ err = get_rate(lhandle);
+ if (lhandle->rate != err)
+ restart = 1;
+ continue;
+ } else if (ctl_event_check(lhandle->ctl_channels, ev)) {
+ err = get_channels(lhandle);
+ if (lhandle->channels != err)
+ restart = 1;
+ continue;
+ }
+ __ctl_check:
+ control_event(lhandle, ev);
+ }
+ err = get_active(lhandle);
+ if (verbose > 7)
+ snd_output_printf(loop->output, "%s: ctl event active %i\n", lhandle->id, err);
+ if (!err) {
+ if (lhandle->loopback->running) {
+ loop->stop_pending = 1;
+ loop->stop_count = 0;
+ }
+ } else {
+ loop->stop_pending = 0;
+ if (loop->running == 0)
+ restart = 1;
+ }
+ if (restart) {
+ pcmjob_stop(loop);
+ err = pcmjob_start(loop);
+ if (err < 0)
+ return err;
+ }
+ return 1;
+}
+
+int pcmjob_pollfds_handle(struct loopback *loop, struct pollfd *fds)
+{
+ struct loopback_handle *play = loop->play;
+ struct loopback_handle *capt = loop->capt;
+ unsigned short prevents, crevents, events;
+ snd_pcm_uframes_t ccount, pcount;
+ int err, loopcount = 10, idx;
+
+ if (verbose > 11)
+ snd_output_printf(loop->output, "%s: pollfds handle\n", loop->id);
+ if (verbose > 13 || loop->xrun)
+ getcurtimestamp(&loop->tstamp_start);
+ if (verbose > 12) {
+ snd_pcm_sframes_t pdelay, cdelay;
+ if ((err = snd_pcm_delay(play->handle, &pdelay)) < 0)
+ snd_output_printf(loop->output, "%s: delay error: %s / %li / %li\n", play->id, snd_strerror(err), play->buf_size, play->buf_count);
+ else
+ snd_output_printf(loop->output, "%s: delay %li / %li / %li\n", play->id, pdelay, play->buf_size, play->buf_count);
+ if ((err = snd_pcm_delay(capt->handle, &cdelay)) < 0)
+ snd_output_printf(loop->output, "%s: delay error: %s / %li / %li\n", capt->id, snd_strerror(err), capt->buf_size, capt->buf_count);
+ else
+ snd_output_printf(loop->output, "%s: delay %li / %li / %li\n", capt->id, cdelay, capt->buf_size, capt->buf_count);
+ }
+ idx = 0;
+ if (loop->running) {
+ err = snd_pcm_poll_descriptors_revents(play->handle, fds,
+ play->pollfd_count,
+ &prevents);
+ if (err < 0)
+ return err;
+ idx += play->pollfd_count;
+ err = snd_pcm_poll_descriptors_revents(capt->handle, fds + idx,
+ capt->pollfd_count,
+ &crevents);
+ if (err < 0)
+ return err;
+ idx += capt->pollfd_count;
+ if (loop->xrun) {
+ if (prevents || crevents) {
+ loop->xrun_last_wake = loop->xrun_last_wake0;
+ loop->xrun_last_wake0 = loop->tstamp_start;
+ }
+ loop->xrun_last_check = loop->xrun_last_check0;
+ loop->xrun_last_check0 = loop->tstamp_start;
+ }
+ } else {
+ prevents = crevents = 0;
+ }
+ if (play->ctl_pollfd_count > 0 &&
+ (loop->slave == SLAVE_TYPE_ON || loop->controls)) {
+ err = snd_ctl_poll_descriptors_revents(play->ctl, fds + idx,
+ play->ctl_pollfd_count,
+ &events);
+ if (err < 0)
+ return err;
+ if (events) {
+ err = handle_ctl_events(play, events);
+ if (err == 1)
+ return 0;
+ if (err < 0)
+ return err;
+ }
+ idx += play->ctl_pollfd_count;
+ }
+ if (capt->ctl_pollfd_count > 0 &&
+ (loop->slave == SLAVE_TYPE_ON || loop->controls)) {
+ err = snd_ctl_poll_descriptors_revents(capt->ctl, fds + idx,
+ capt->ctl_pollfd_count,
+ &events);
+ if (err < 0)
+ return err;
+ if (events) {
+ err = handle_ctl_events(capt, events);
+ if (err == 1)
+ return 0;
+ if (err < 0)
+ return err;
+ }
+ idx += capt->ctl_pollfd_count;
+ }
+ if (verbose > 9)
+ snd_output_printf(loop->output, "%s: prevents = 0x%x, crevents = 0x%x\n", loop->id, prevents, crevents);
+ if (!loop->running)
+ goto __pcm_end;
+ do {
+ ccount = readit(capt);
+ buf_add(loop, ccount);
+ if (capt->xrun_pending || loop->reinit)
+ break;
+ /* we read new samples, if we have a room in the playback
+ buffer, feed them there */
+ pcount = writeit(play);
+ buf_remove(loop, pcount);
+ if (play->xrun_pending || loop->reinit)
+ break;
+ loopcount--;
+ } while ((ccount > 0 || pcount > 0) && loopcount > 0);
+ if (play->xrun_pending || capt->xrun_pending) {
+ if ((err = xrun_sync(loop)) < 0)
+ return err;
+ }
+ if (loop->reinit) {
+ err = pcmjob_stop(loop);
+ if (err < 0)
+ return err;
+ err = pcmjob_start(loop);
+ if (err < 0)
+ return err;
+ }
+ if (loop->sync != SYNC_TYPE_NONE &&
+ play->counter >= play->sync_point &&
+ capt->counter >= play->sync_point) {
+ snd_pcm_sframes_t diff, lat = get_whole_latency(loop);
+ diff = ((double)(((double)play->total_queued * play->pitch) +
+ ((double)capt->total_queued * capt->pitch)) /
+ (double)loop->total_queued_count) - lat;
+ /* FIXME: this algorithm may be slightly better */
+ if (verbose > 3)
+ snd_output_printf(loop->output, "%s: sync diff %li old diff %li\n", loop->id, diff, loop->pitch_diff);
+ if (diff > 0) {
+ if (diff == loop->pitch_diff)
+ loop->pitch += loop->pitch_delta;
+ else if (diff > loop->pitch_diff)
+ loop->pitch += loop->pitch_delta*2;
+ } else if (diff < 0) {
+ if (diff == loop->pitch_diff)
+ loop->pitch -= loop->pitch_delta;
+ else if (diff < loop->pitch_diff)
+ loop->pitch -= loop->pitch_delta*2;
+ }
+ loop->pitch_diff = diff;
+ if (loop->pitch_diff_min > diff)
+ loop->pitch_diff_min = diff;
+ if (loop->pitch_diff_max < diff)
+ loop->pitch_diff_max = diff;
+ update_pitch(loop);
+ play->counter -= play->sync_point;
+ capt->counter -= play->sync_point;
+ play->total_queued = 0;
+ capt->total_queued = 0;
+ loop->total_queued_count = 0;
+ }
+ if (loop->sync != SYNC_TYPE_NONE) {
+ snd_pcm_sframes_t pqueued, cqueued;
+ pqueued = get_queued_playback_samples(loop);
+ cqueued = get_queued_capture_samples(loop);
+ if (verbose > 4)
+ snd_output_printf(loop->output, "%s: queued %li/%li samples\n", loop->id, pqueued, cqueued);
+ if (pqueued > 0)
+ play->total_queued += pqueued;
+ if (cqueued > 0)
+ capt->total_queued += cqueued;
+ if (pqueued > 0 || cqueued > 0)
+ loop->total_queued_count += 1;
+ }
+ if (verbose > 12) {
+ snd_pcm_sframes_t pdelay, cdelay;
+ if ((err = snd_pcm_delay(play->handle, &pdelay)) < 0)
+ snd_output_printf(loop->output, "%s: end delay error: %s / %li / %li\n", play->id, snd_strerror(err), play->buf_size, play->buf_count);
+ else
+ snd_output_printf(loop->output, "%s: end delay %li / %li / %li\n", play->id, pdelay, play->buf_size, play->buf_count);
+ if ((err = snd_pcm_delay(capt->handle, &cdelay)) < 0)
+ snd_output_printf(loop->output, "%s: end delay error: %s / %li / %li\n", capt->id, snd_strerror(err), capt->buf_size, capt->buf_count);
+ else
+ snd_output_printf(loop->output, "%s: end delay %li / %li / %li\n", capt->id, cdelay, capt->buf_size, capt->buf_count);
+ }
+ __pcm_end:
+ if (verbose > 13 || loop->xrun) {
+ long diff;
+ getcurtimestamp(&loop->tstamp_end);
+ diff = timediff(loop->tstamp_end, loop->tstamp_start);
+ if (verbose > 13)
+ snd_output_printf(loop->output, "%s: processing time %lius\n", loop->id, diff);
+ if (loop->xrun && loop->xrun_max_proctime < diff)
+ loop->xrun_max_proctime = diff;
+ }
+ return 0;
+}
+
+#define OUT(args...) \
+ snd_output_printf(loop->state, ##args)
+
+static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static void show_handle(struct loopback_handle *lhandle, const char *id)
+{
+ struct loopback *loop = lhandle->loopback;
+
+ OUT(" %s: %s:\n", id, lhandle->id);
+ OUT(" device = '%s', ctldev '%s'\n", lhandle->device, lhandle->ctldev);
+ OUT(" card_number = %i\n", lhandle->card_number);
+ if (!loop->running)
+ return;
+ OUT(" access = %s, format = %s, rate = %u, channels = %u\n", snd_pcm_access_name(lhandle->access), snd_pcm_format_name(lhandle->format), lhandle->rate, lhandle->channels);
+ OUT(" buffer_size = %u, period_size = %u, avail_min = %li\n", lhandle->buffer_size, lhandle->period_size, lhandle->avail_min);
+ OUT(" xrun_pending = %i\n", lhandle->xrun_pending);
+ OUT(" buf_size = %li, buf_pos = %li, buf_count = %li, buf_over = %li\n", lhandle->buf_size, lhandle->buf_pos, lhandle->buf_count, lhandle->buf_over);
+ OUT(" pitch = %.8f\n", lhandle->pitch);
+}
+
+void pcmjob_state(struct loopback *loop)
+{
+ pthread_t self = pthread_self();
+ pthread_mutex_lock(&state_mutex);
+ OUT("State dump for thread %p job %i: %s:\n", (void *)self, loop->thread, loop->id);
+ OUT(" running = %i\n", loop->running);
+ OUT(" sync = %i\n", loop->sync);
+ OUT(" slave = %i\n", loop->slave);
+ if (!loop->running)
+ goto __skip;
+ OUT(" pollfd_count = %i\n", loop->pollfd_count);
+ OUT(" pitch = %.8f, delta = %.8f, diff = %li, min = %li, max = %li\n", loop->pitch, loop->pitch_delta, loop->pitch_diff, loop->pitch_diff_min, loop->pitch_diff_max);
+ OUT(" use_samplerate = %i\n", loop->use_samplerate);
+ __skip:
+ show_handle(loop->play, "playback");
+ show_handle(loop->capt, "capture");
+ pthread_mutex_unlock(&state_mutex);
+}
diff --git a/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/plugin.vala b/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/plugin.vala
new file mode 100644
index 00000000..2ea14b66
--- /dev/null
+++ b/fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/plugin.vala
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2011 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+ * Copyright (C) 2012 Denis 'GNUtoo' Carikli <GNUtoo@no-log.org>
+
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+namespace FsoAudio.GsmVoiceForwarder
+{
+ public const string MODULE_NAME = "fsoaudio.gsmvoice_alsa_forwarder";
+}
+
+class FsoAudio.GsmVoiceForwarder.Plugin : FsoFramework.AbstractObject
+{
+ private FsoFramework.Subsystem subsystem;
+ private FreeSmartphone.GSM.Call gsmcallproxy;
+ /* TODO: configure the values */
+ private PlaybackFromModem modemSourceCodecSink = new PlaybackFromModem(8000,2);
+
+ //
+ // Private API
+ //
+ private void onCallStatusSignal( int id, FreeSmartphone.GSM.CallStatus status, GLib.HashTable<string,Variant> properties )
+ {
+ assert( logger.debug( @"onCallStatusSignal $id w/ status $status" ) );
+ switch ( status )
+ {
+ case FreeSmartphone.GSM.CallStatus.OUTGOING:
+ case FreeSmartphone.GSM.CallStatus.ACTIVE:
+ this.modemSourceCodecSink.setAudioStatus(true);
+ break;
+
+ case FreeSmartphone.GSM.CallStatus.RELEASE:
+ this.modemSourceCodecSink.setAudioStatus(false);
+ break;
+
+ default:
+ assert( logger.debug( @"Unhandled call status $status" ) );
+ break;
+ }
+ }
+
+ //
+ // Public API
+ //
+ public Plugin( FsoFramework.Subsystem subsystem )
+ {
+ this.subsystem = subsystem;
+
+ try
+ {
+ gsmcallproxy = Bus.get_proxy_sync<FreeSmartphone.GSM.Call>( BusType.SYSTEM, "org.freesmartphone.ogsmd", "/org/freesmartphone/GSM/Device", DBusProxyFlags.DO_NOT_AUTO_START );
+ gsmcallproxy.call_status.connect( onCallStatusSignal );
+ }
+ catch ( Error e )
+ {
+ logger.error( @"Could not hook to fsogsmd: $(e.message)" );
+ }
+ }
+
+ public override string repr()
+ {
+ return "<>";
+ }
+}
+
+internal FsoAudio.GsmVoiceForwarder.Plugin instance;
+
+/**
+ * This function gets called on plugin initialization time.
+ * @return the name of your plugin here
+ * @note that it needs to be a name in the format <subsystem>.<plugin>
+ * else your module will be unloaded immediately.
+ **/
+public static string fso_factory_function( FsoFramework.Subsystem subsystem ) throws Error
+{
+ instance = new FsoAudio.GsmVoiceForwarder.Plugin( subsystem );
+ return FsoAudio.GsmVoiceForwarder.MODULE_NAME;
+}
+
+[ModuleInit]
+public static void fso_register_function( TypeModule module )
+{
+ FsoFramework.theLogger.debug( "fsoaudio.gsmvoice_alsa_forwarder fso_register_function" );
+}