diff options
author | Denis 'GNUtoo' Carikli <GNUtoo@no-log.org> | 2012-01-09 21:17:38 +0100 |
---|---|---|
committer | Denis 'GNUtoo' Carikli <GNUtoo@no-log.org> | 2012-01-25 19:45:55 +0100 |
commit | 51a0667d1d5dd0500f77051f988bd888889c3254 (patch) | |
tree | 22bc9e8c565990294fe1a27232e88dfae22cd2f7 | |
parent | 5a6db3fa94e521dab1f1370f4274ecb19451e7f7 (diff) | |
download | cornucopia-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.ac | 1 | ||||
-rw-r--r-- | fsoaudiod/src/plugins/Makefile.am | 2 | ||||
-rw-r--r-- | fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/Makefile.am | 60 | ||||
-rw-r--r-- | fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/alsaloop.c | 923 | ||||
-rw-r--r-- | fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/alsaloop.h | 218 | ||||
-rw-r--r-- | fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/control.c | 424 | ||||
-rw-r--r-- | fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/pcmjob.c | 1925 | ||||
-rw-r--r-- | fsoaudiod/src/plugins/gsmvoice_alsa_forwarder/plugin.vala | 97 |
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" ); +} |