From 1e75673035ffc0971719974715211d6a0a48e61a Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 18 Aug 2010 08:29:03 +0200 Subject: [PATCH] Introduce alsaloop utility alsaloop allows create a PCM loopback between a PCM capture device and a PCM playback device. alsaloop supports multiple soundcards, adaptive clock synchronization, adaptive rate resampling using the samplerate library (if available in the system). Also, mixer controls can be redirected from one card to another (for example Master and PCM). Signed-off-by: Jaroslav Kysela --- .gitignore | 1 + Makefile.am | 3 + alsaloop/Makefile.am | 13 + alsaloop/alsaloop.1 | 170 +++++ alsaloop/alsaloop.c | 740 +++++++++++++++++++ alsaloop/alsaloop.h | 181 +++++ alsaloop/control.c | 376 ++++++++++ alsaloop/effect-sweep.c | 128 ++++ alsaloop/pcmjob.c | 1528 +++++++++++++++++++++++++++++++++++++++ alsaloop/test.sh | 34 + configure.in | 16 +- 11 files changed, 3189 insertions(+), 1 deletion(-) create mode 100644 alsaloop/Makefile.am create mode 100644 alsaloop/alsaloop.1 create mode 100644 alsaloop/alsaloop.c create mode 100644 alsaloop/alsaloop.h create mode 100644 alsaloop/control.c create mode 100644 alsaloop/effect-sweep.c create mode 100644 alsaloop/pcmjob.c create mode 100755 alsaloop/test.sh diff --git a/.gitignore b/.gitignore index 0319f44..d17c89a 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ seq/aplaymidi/arecordmidi seq/aseqdump/aseqdump seq/aseqnet/aseqnet speaker-test/speaker-test +alsaloop/alsaloop include/aconfig.h* include/stamp-* diff --git a/Makefile.am b/Makefile.am index 5296977..9951c46 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,6 +15,9 @@ SUBDIRS += alsaconf endif if HAVE_PCM SUBDIRS += aplay iecset speaker-test +if ALSALOOP +SUBDIRS += alsaloop +endif endif if HAVE_SEQ SUBDIRS += seq diff --git a/alsaloop/Makefile.am b/alsaloop/Makefile.am new file mode 100644 index 0000000..97d2e6f --- /dev/null +++ b/alsaloop/Makefile.am @@ -0,0 +1,13 @@ +INCLUDES = -I$(top_srcdir)/include +LDADD = -lm +if HAVE_SAMPLERATE +LDADD += -lsamplerate +endif +# LDFLAGS = -static +# CFLAGS += -g -Wall + +bin_PROGRAMS = alsaloop +alsaloop_SOURCES = alsaloop.c pcmjob.c control.c +noinst_HEADERS = alsaloop.h +man_MANS = alsaloop.1 +EXTRA_DIST = alsaloop.1 diff --git a/alsaloop/alsaloop.1 b/alsaloop/alsaloop.1 new file mode 100644 index 0000000..66be499 --- /dev/null +++ b/alsaloop/alsaloop.1 @@ -0,0 +1,170 @@ +.TH ALSALOOP 1 "5 Aug 2010" +.SH NAME +alsaloop \- command-line PCM loopback +.SH SYNOPSIS +\fBalsaloop\fP [\fI\-option\fP] [\fIcmd\fP] +.SH DESCRIPTION + +\fBalsaloop\fP allows create a PCM loopback between a PCM capture device +and a PCM playback device. + +\fBalsaloop\fP supports multiple soundcards, adaptive clock synchronization, +adaptive rate resampling using the samplerate library (if available in +the system). Also, mixer controls can be redirected from one card to +another (for example Master and PCM). + +.SH OPTIONS + +.TP +\fI\-h\fP | \fI\-\-help\fP + +Prints the help information. + +.TP +\fI\-g \fP | \fI\-\-config=\fP + +Use given configuration file. The syntax of this file is simple: one line +contains the command line options for one job. The '#' means comment and +rest of line is ignored. Example: + + # First line - comment, second line - first job + -C hw:1,0 -P hw:0,0 -t 50000 -T 1 + # Third line - comment, fourth line - second job + -C hw:1,1 -P hw:0,1 -t 40000 -T 2 + +.TP +\fI\-d\fP | \fI\-\-daemonize\fP + +Daemonize the main process and use syslog for messages. + +.TP +\fI\-P \fP | \fI\-\-pdevice=\fP + +Use given playback device. + +.TP +\fI\-C \fP | \fI\-\-cdevice=\fP + +Use given capture device. + +.TP +\fI\-l \fP | \fI\-\-latency=\fP + +Requested latency in frames. + +.TP +\fI\-t \fP | \fI\-\-tlatency=\fP + +Requested latency in usec (1/1000000sec). + +.TP +\fI\-f \fP | \fI\-\-format=\fP + +Format specification (usually S16_LE S32_LE). Use -h to list all formats. +Default format is S16_LE. + +.TP +\fI\-c \fP | \fI\-\-channels=\fP + +Channel count specification. Default value is 2. + +.TP +\fI\-c \fP | \fI\-\-rate=\fP + +Rate specification. Default value is 48000 (Hz). + +.TP +\fI\-n\fP | \fI\-\-resample\fP + +Allow rate resampling using alsa-lib. + +.TP +\fI\-A \fP | \fI\-\-samplerate=\fP + +Use libsamplerate and choose a converter: + + 0 or sincbest - best quality + 1 or sincmedium - medium quality + 2 or sincfastest - lowest quality + 3 or zerohold - hold zero samples + 4 or linear - worst quality - linear resampling + 5 or auto - choose best method + +.TP +\fI\-B \fP | \fI\-\-buffer=\fP + +Buffer size in frames. + +.TP +\fI\-E \fP | \fI\-\-period=\fP + +Period size in frames. + +.TP +\fI\-s \fP | \fI\-\-seconds=\fP + +Duration of loop in seconds. + +.TP +\fI\-b\fP | \fI\-\-nblock\fP + +Non-block mode (very early process wakeup). Eats more CPU. + +.TP +\fI\-S \fP | \fI\-\-sync=\fP + +Sync mode specification for capture to playback stream: + 0 or none - do not touch the stream + 1 or simple - add or remove samples to keep + both streams synchronized + 2 or captshift - use driver for the capture device + (if supported) to compensate + the rate shift + 3 or playshift - use driver for the playback device + (if supported) to compensate + the rate shift + 4 or samplerate - use samplerate library to do rate resampling + 5 or auto - automatically selects the best method + in this order: captshift, playshift, + samplerate, simple + +.TP +\fI\-T \fP | \fI\-\-thread=\fP + +Thread number (-1 means create a unique thread). All jobs with same +thread numbers are run within one thread. + +.TP +\fI\-m \fP | \fI\-\-mixer=\fP + +Redirect mixer control from the playback card to the capture card. Format of +\fImixid\fP is SRCID(PLAYBACK)[@DSTID(PLAYBACK)]: + + "name='Master Playback Switch'@name='Another Switch'" + "name='PCM Playback Volume'" + +Known attributes: + + name - control ID name + index - control ID index + device - control ID device + subdevice - control ID subdevice + iface - control ID interface + numid - control ID numid + +.TP +\fI\-v\fP | \fI\-\-verbose\fP + +Verbose mode. Use multiple times to increase verbosity. + + +.SH EXAMPLES + +.TP +\fBalsaloop \-C hw:0,0 \-P hw:1,0 \-t 50000\fR + +.SH BUGS +None known. +.SH AUTHOR +\fBalsaloop\fP is by Jaroslav Kysela . +This document is by Jaroslav Kysela . diff --git a/alsaloop/alsaloop.c b/alsaloop/alsaloop.c new file mode 100644 index 0000000..4ba5203 --- /dev/null +++ b/alsaloop/alsaloop.c @@ -0,0 +1,740 @@ +/* + * A simple PCM loopback utility with adaptive sample rate support + * + * Author: Jaroslav Kysela + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "alsaloop.h" + +struct loopback_thread { + int threaded; + pthread_t thread; + int exitcode; + struct loopback **loopbacks; + int loopbacks_count; + snd_output_t *output; +}; + +int verbose = 0; +int daemonize = 0; +int use_syslog = 0; +struct loopback **loopbacks; +int loopbacks_count = 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 *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); + 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 = 48000; + handle->channels = 2; + handle->resample = 1; + *_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; +#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" +"-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" +"-e,--effect apply an effect (bandpass filter sweep)\n" +"-v,--verbose verbose mode (more -v means more verbose)\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 * 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 parse_config_file(const char *file, snd_output_t *output); + +static int parse_config(int argc, char *argv[], snd_output_t *output) +{ + struct option long_option[] = + { + {"help", 0, NULL, 'h'}, + {"config", 1, NULL, 'g'}, + {"daemonize", 0, NULL, 'd'}, + {"pdevice", 1, NULL, 'P'}, + {"cdevice", 1, NULL, 'C'}, + {"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'}, + {NULL, 0, NULL, 0}, + }; + int err, morehelp; + char *arg_config = NULL; + char *arg_pdevice = NULL; + char *arg_cdevice = 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 = 0; + 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; + + morehelp = 0; + while (1) { + int c; + if ((c = getopt_long(argc, argv, "hdg:P:C:l:t:F:f:c:r:s:benvA:S:a:m:", 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 '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 = 0; + 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, "off")) + arg_slave = SLAVE_TYPE_ON; + else if (strcasecmp(optarg, "on")) + 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 'v': + verbose++; + 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, "playback"); + if (err < 0) { + logit(LOG_CRIT, "Unable to create playback handle.\n"); + exit(EXIT_FAILURE); + } + err = create_loopback_handle(&capt, arg_cdevice, "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 = capt->rate = 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; + err = add_mixers(loop, arg_mixers, arg_mixers_count); + if (err < 0) { + logit(LOG_CRIT, "Unable to add mixer 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; + + argv = malloc(sizeof(char *) * MAX_ARGS); + if (argv == NULL) + return -ENOMEM; + 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'; + argc = 0; + argv[argc++] = strdup(""); + 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) { + *ptr = '\0'; + argv[argc++] = strdup(word); + } + } + /* erase runtime variables for getopt */ + optarg = NULL; + optind = opterr = 1; + optopt = 63; + + err = parse_config(argc, argv, output); + __next: + while (argc > 0) + free(argv[--argc]); + if (err < 0) + break; + err = 0; + } + fclose(fp); + free(argv); + + 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; + + 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; + } + pfds = calloc(pfds_count, sizeof(struct pollfd)); + if (pfds == NULL) { + logit(LOG_CRIT, "Poll FDs allocation failed.\n"); + my_exit(thread, EXIT_FAILURE); + } + while (1) { + 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, -1); + if (verbose > 10) { + gettimeofday(&tv2, NULL); + snd_output_printf(output, "pool took %lius\n", timediff(tv2, tv1)); + } + if (err < 0) { + logit(LOG_CRIT, "Poll failed.\n"); + 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); +} + +int main(int argc, char *argv[]) +{ + snd_output_t *output; + int i, j, k, l, err; + struct loopback_thread *threads; + + 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); + if (err < 0) { + logit(LOG_CRIT, "Unable to parse arguments or configuration...\n"); + exit(EXIT_FAILURE); + } + + 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 = 0; + do { + k = 0x7fffffff; + for (i = 0; i < loopbacks_count; i++) { + if (loopbacks[i]->thread < k && + loopbacks[i]->thread > j) + k = loopbacks[i]->thread; + } + for (i = 0; i < loopbacks_count; i++) { + if (loopbacks[i]->thread == k) + loopbacks[i]->thread = j; + } + 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]; + } + + for (k = 0; k < j; k++) + thread_job(&threads[k]); + + logit(LOG_CRIT, "threads = %i %i\n", j, loopbacks_count); + if (j > 1) { + for (k = 0; k < j; k++) + pthread_join(threads[k].thread, NULL); + } + + if (use_syslog) + closelog(); + exit(EXIT_SUCCESS); +} diff --git a/alsaloop/alsaloop.h b/alsaloop/alsaloop.h new file mode 100644 index 0000000..4b357de --- /dev/null +++ b/alsaloop/alsaloop.h @@ -0,0 +1,181 @@ +/* + * A simple PCM loopback utility + * Copyright (c) 2010 by Jaroslav Kysela + * + * 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 +#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 + +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_handle { + struct loopback *loopback; + char *device; + char *id; + snd_pcm_t *handle; + snd_pcm_access_t access; + snd_pcm_format_t format; + unsigned int rate; + unsigned int channels; + unsigned int buffer_size; + unsigned int period_size; + 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; + /* 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 */ + unsigned int latency_req; /* in frames / 2 */ + unsigned int latency_reqtime; /* in us / 2 */ + unsigned long loop_time; /* ~0 = unlimited (in seconds) */ + unsigned long long loop_limit; /* ~0 = unlimited (in frames) */ + snd_output_t *output; + int pollfd_count; + int active_pollfd_count; + unsigned int linked:1; /* linked streams */ + unsigned int reinit:1; + unsigned int running:1; + sync_type_t sync; /* type of sync */ + slave_type_t slave; + int thread; /* thread number */ + /* 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; + snd_pcm_uframes_t total_queued; + unsigned int total_queued_count; + snd_timestamp_t tstamp_start; + snd_timestamp_t tstamp_end; + /* control mixer */ + struct loopback_mixer *controls; + /* sample rate */ +#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 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); + +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/alsaloop/control.c b/alsaloop/control.c new file mode 100644 index 0000000..ade7733 --- /dev/null +++ b/alsaloop/control.c @@ -0,0 +1,376 @@ +/* + * A simple PCM loopback utility + * Copyright (c) 2010 by Jaroslav Kysela + * + * Author: Jaroslav Kysela + * + * + * 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 +#include +#include +#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); + 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 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; + int err; + + for (mix = loop->controls; mix; mix = mix->next) { + err = control_init1(loop->play, &mix->src); + if (err < 0) { + logit(LOG_WARNING, "Disabling playback control '%s'\n", id_str(mix->src.id)); + mix->skip = 1; + continue; + } + err = control_init2(loop, mix); + if (err < 0) + return err; + } + return 0; +} + +int control_done(struct loopback *loop) +{ + struct loopback_mixer *mix; + int err; + + if (loop->capt->ctl == NULL) + return 0; + 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, "Unable to remove control '%s': %s\n", 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/alsaloop/effect-sweep.c b/alsaloop/effect-sweep.c new file mode 100644 index 0000000..4a0903d --- /dev/null +++ b/alsaloop/effect-sweep.c @@ -0,0 +1,128 @@ +/* + * Bandpass filter sweep effect + * Copyright (c) Maarten de Boer + * + * 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 +#include + +struct effect_private { + /* filter the sweep variables */ + float lfo,dlfo,fs,fc,BW,C,D,a0,a1,a2,b1,b2,*x[3],*y[3]; + float lfo_depth, lfo_center; + unsigned int channels; +}; + +static int effect_init(struct lookback *loopback, + void *private_data, + snd_pcm_access_t access, + unsigned int channels, + unsigned int rate, + snd_pcm_format_t format) +{ + struct effect_private *priv = private_data; + int i; + +#if __BYTE_ORDER == __LITTLE_ENDIAN + if (format != SND_PCM_FORMAT_S16_LE) + return -EIO; +#elif __BYTE_ORDER == __BIG_ENDIAN + if (format != SND_PCM_FORMAT_S16_BE) + return -EIO; +#else + return -EIO; +#endif + priv->fs = (float) rate; + priv->channels = channels; + for (i = 0; i < 3; i++) { + priv->x[i] = calloc(channels * sizeof(float)); + priv->y[i] = calloc(channels * sizeof(float)); + } + return 0; +} + +static int effect_done(struct loopback *loopback, + void *private_data) +{ + struct effect_private *priv = private_data; + int i; + + for (i = 0; i < 3; i++) { + free(priv->x[i]); + free(priv->y[i]); + } + return 0; +} + +static int effect_apply(struct loopback *loopback, + void *private_data, + const snd_pcm_channel_area_t *areas, + snd_uframes_t offset, + snd_uframes_t frames) +{ + struct effect_private *priv = private_data; + short *samples = (short*)areas[0].addr + offset*priv->channels; + snd_uframes_t i; + + for (i=0; i < frames; i++) { + int chn; + + fc = sin(priv->lfo)*priv->lfo_depth+priv->lfo_center; + priv->lfo += priv->dlfo; + if (priv->lfo>2.*M_PI) priv->lfo -= 2.*M_PI; + priv->C = 1./tan(M_PI*priv->BW/priv->fs); + priv->D = 2.*cos(2*M_PI*fc/fs); + priv->a0 = 1./(1.+priv->C); + priv->a1 = 0; + priv->a2 = -priv->a0; + priv->b1 = -priv->C*priv->D*a0; + priv->b2 = (priv->C-1)*priv->a0; + + for (chn=0; chn < priv->channels; chn++) + { + priv->x[chn][2] = priv->x[chn][1]; + priv->x[chn][1] = priv->x[chn][0]; + + priv->y[chn][2] = priv->y[chn][1]; + priv->y[chn][1] = priv->y[chn][0]; + + priv->x[chn][0] = samples[i*channels+chn]; + priv->y[chn][0] = priv->a0*priv->x[0][chn] + + priv->a1*priv->x[1][chn] + priv->a2*x[2][chn] + - priv->b1*priv->y[1][chn] - priv->b2*y[2][chn]; + samples[i*channels+chn] = priv->y[chn][0]; + } + } + return 0; +} + +void effect_init_sweep(void) +{ + struct effect_private *priv; + + priv = register_effect(effect_init, + effect_apply, + effect_done, + sizeof(struct effectprivate)); + if (priv) { + priv->lfo_center = 2000.; + priv->lfo_depth = 1800.; + priv->lfo_freq = 0.2; + priv->BW = 50; + } +} diff --git a/alsaloop/pcmjob.c b/alsaloop/pcmjob.c new file mode 100644 index 0000000..47256e0 --- /dev/null +++ b/alsaloop/pcmjob.c @@ -0,0 +1,1528 @@ +/* + * A simple PCM loopback utility + * Copyright (c) 2010 by Jaroslav Kysela + * + * Author: Jaroslav Kysela + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "alsaloop.h" + +static int set_rate_shift(struct loopback_handle *lhandle, double pitch); + +#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 + +static const char *src_types[] = { + SRCTYPE(SINC_BEST_QUALITY), + SRCTYPE(SINC_MEDIUM_QUALITY), + SRCTYPE(SINC_FASTEST), + SRCTYPE(ZERO_ORDER_HOLD), + SRCTYPE(LINEAR) +}; + +static inline snd_pcm_uframes_t get_whole_latency(struct loopback *loop) +{ + return loop->latency; +} + +static inline snd_pcm_uframes_t time_to_frames(struct loopback_handle *lhandle, + unsigned long long time) +{ + return (time * lhandle->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", snd_strerror(err), lhandle->id); + 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; + 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, lhandle->id, snd_strerror(err)); + return err; + } + rrate = 0; + snd_pcm_hw_params_get_rate(params, &rrate, 0); + if ((int)rrate != lhandle->rate) { + logit(LOG_CRIT, "Rate does not match (requested %iHz, get %iHz)\n", lhandle->rate, err); + return -EINVAL; + } + 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 (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 (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_t *handle = lhandle->handle; + int err; + snd_pcm_uframes_t val, val1; + + 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, &val, NULL); + snd_pcm_hw_params_get_buffer_size(params, &val1); + if (lhandle->nblock) { + if (lhandle == lhandle->loopback->play) { + val = val1 - (2 * val - 4); + } else { + val = 4; + } + } else { + if (lhandle == lhandle->loopback->play) { + snd_pcm_hw_params_get_buffer_size(params, &val1); + val = val1 - val - val / 2; + } else { + val /= 2; + } + } + 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; + } + 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)) < 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)) < 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)) < 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)) < 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(struct loopback *loop, size_t latency, unsigned int rate) +{ + double d; + d = (double)latency / (double)rate; + snd_output_printf(loop->output, "Latency %li frames, %.3fus, %.6fms (%.4fHz)\n", (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; + l = (signed long) t1.tv_usec - (signed long) t2.tv_usec; + if (l < 0) { + t1.tv_sec--; + l = -l; + l %= 1000000; + } + 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 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; + 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; + 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); + if ((err = snd_pcm_prepare(lhandle->handle)) < 0) + return err; + lhandle->xrun_pending = 1; + } else { + logit(LOG_DEBUG, "overrun for %s\n", lhandle->id); + 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 > 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; + } + 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; + } + 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 delay, delayi, pdelay, cdelay, 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; + loop->total_queued = 0; + loop->total_queued_count = 0; + loop->pitch_diff = loop->pitch_diff_min = loop->pitch_diff_max = 0; + delay = delayi = pdelay + cdelay; + if (play->buf != capt->buf) + delay += capt->buf_count; + delay += play->buf_count; +#ifdef USE_SAMPLERATE + delay += loop->src_out_frames; + delayi += loop->src_out_frames; +#endif +#if 0 + printf("s: cdelay = %li, pdelay = %li, delay = %li, delayi = %li, fill = %li, src_out = %li\n", + (long)cdelay, (long)pdelay, (long)delay, + (long)delayi, (long)fill, (long)loop->src_out_frames); + printf("s: cbufcount = %li, pbufcount = %li\n", (long)capt->buf_count, (long)play->buf_count); +#endif + if (delayi > fill) { + 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; + remove_samples(loop, 1, delayi - fill); + goto __again; + } + if (delay > fill) { + diff = fill > delayi ? play->buf_count - (fill - delayi) : 0; + delay -= remove_samples(loop, 0, diff); + } + if (delay > fill) { + diff = fill > delayi ? capt->buf_count - (fill - delayi) : 0; + delay -= remove_samples(loop, 1, diff); + } + if (play->xrun_pending) { + play->xrun_pending = 0; + if (fill > delay && play->buf_count < fill - delay) { + diff = fill - delay - play->buf_count; + 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; + } + delayi = writeit(play); + if (delayi > diff) + buf_remove(loop, delayi - 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); + 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; +} + +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; + if ((err = snd_pcm_open(&lhandle->handle, lhandle->device, stream, SND_PCM_NONBLOCK)) < 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->ctl = NULL; + if (card >= 0) { + char name[16]; + sprintf(name, "hw:%i", card); + err = snd_ctl_open(&lhandle->ctl, name, SND_CTL_NONBLOCK); + if (err < 0) { + logit(LOG_CRIT, "%s [%s] ctl open error: %s\n", lhandle->id, name, 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_req; + if (lat == 0) + lat = time_to_frames(lhandle, + lhandle->loopback->latency_reqtime); + 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->src_enable) { + 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; +} + +int pcmjob_start(struct loopback *loop) +{ + snd_pcm_uframes_t count; + int 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 = loop->capt->rate = err; + err = get_channels(loop->capt); + if (err < 0) + goto __error; + loop->play->channels = loop->capt->channels = err; + } + loop->pollfd_count = loop->play->ctl_pollfd_count + + loop->capt->ctl_pollfd_count; + loop->reinit = 0; + loop->latency = loop->latency_req; + if (loop->latency == 0) + loop->latency = time_to_frames(loop->play, + loop->latency_reqtime); + if ((err = setparams(loop, loop->latency/2)) < 0) + goto __error; + if (verbose) + showlatency(loop, loop->latency, loop->play->rate); + 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->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 ((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; +#ifdef USE_SAMPLERATE + if (loop->sync == SYNC_TYPE_SAMPLERATE) { + 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) { + 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; + } + loop->pitch = 1; + loop->pitch_delta = 1.0 / ((double)loop->capt->rate * 4); + loop->total_queued = 0; + loop->total_queued_count = 0; + loop->pitch_diff = 0; + count = get_whole_latency(loop); + loop->play->buf_count = count; + if (loop->play->buf == loop->capt->buf) + loop->capt->buf_pos = count; + if (writeit(loop->play) != count) { + logit(LOG_CRIT, "%s: initial playback fill error\n", loop->id); + err = -EIO; + goto __error; + } + loop->running = 1; + 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_samples(struct loopback *loop) +{ + snd_pcm_sframes_t pdelay, cdelay, delay; + int err; + + if ((err = snd_pcm_delay(loop->play->handle, &pdelay)) < 0) + return 0; + if ((err = snd_pcm_delay(loop->capt->handle, &cdelay)) < 0) + return 0; + loop->play->last_delay = pdelay; + loop->capt->last_delay = cdelay; + delay = pdelay + cdelay; + delay += loop->capt->buf_count; + delay += loop->play->buf_count; +#ifdef USE_SAMPLERATE + delay += loop->src_out_frames; +#endif + 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) +{ + snd_ctl_event_t *ev; + int err; + + 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 == lhandle->loopback->play) + goto __ctl_check; + if (verbose > 6) + snd_output_printf(lhandle->loopback->output, "ctl event!!!! %s\n", snd_ctl_event_elem_get_name(ev)); + if (ctl_event_check(lhandle->ctl_active, ev)) { + err = get_active(lhandle); + if (err != lhandle->loopback->running) + goto __restart; + } else if (ctl_event_check(lhandle->ctl_format, ev)) { + err = get_format(lhandle); + if (lhandle->format != err) + goto __restart; + } else if (ctl_event_check(lhandle->ctl_rate, ev)) { + err = get_rate(lhandle); + if (lhandle->rate != err) + goto __restart; + } else if (ctl_event_check(lhandle->ctl_channels, ev)) { + err = get_channels(lhandle); + if (lhandle->channels != err) + goto __restart; + } + __ctl_check: + control_event(lhandle, ev); + } + return 0; + + __restart: + pcmjob_stop(lhandle->loopback); + err = pcmjob_start(lhandle->loopback); + 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; + snd_pcm_sframes_t queued; + int err, loopcount = 10, idx; + + if (verbose > 11) + snd_output_printf(loop->output, "%s: pollfds handle\n", loop->id); + if (verbose > 13) + 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\n", play->id, snd_strerror(err)); + else + snd_output_printf(loop->output, "%s: delay %li\n", play->id, pdelay); + if ((err = snd_pcm_delay(capt->handle, &cdelay)) < 0) + snd_output_printf(loop->output, "%s: delay error: %s\n", capt->id, snd_strerror(err)); + else + snd_output_printf(loop->output, "%s: delay %li\n", capt->id, cdelay); + } + 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; + } 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); + 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); + double pitch; + diff = ((double)loop->total_queued / + (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; + 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; + 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; + pitch = loop->pitch; +#ifdef USE_SAMPLERATE + if (loop->sync == SYNC_TYPE_SAMPLERATE) + loop->src_data.src_ratio = (double)1.0 / pitch; + else +#endif + if (loop->sync == SYNC_TYPE_CAPTRATESHIFT) + set_rate_shift(capt, pitch); + if (loop->sync == SYNC_TYPE_PLAYRATESHIFT) + set_rate_shift(play, pitch); + 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); + play->counter -= play->sync_point; + capt->counter -= play->sync_point; + loop->total_queued = 0; + loop->total_queued_count = 0; + } + if (loop->sync != SYNC_TYPE_NONE) { + queued = get_queued_samples(loop); + if (verbose > 4) + snd_output_printf(loop->output, "%s: queued %li samples\n", loop->id, queued); + if (queued > 0) { + loop->total_queued += queued; + 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\n", play->id, snd_strerror(err)); + else + snd_output_printf(loop->output, "%s: end delay %li\n", play->id, pdelay); + if ((err = snd_pcm_delay(capt->handle, &cdelay)) < 0) + snd_output_printf(loop->output, "%s: end delay error: %s\n", capt->id, snd_strerror(err)); + else + snd_output_printf(loop->output, "%s: end delay %li\n", capt->id, cdelay); + } + if (verbose > 13) { + getcurtimestamp(&loop->tstamp_end); + snd_output_printf(loop->output, "%s: processing time %lius\n", capt->id, timediff(loop->tstamp_end, loop->tstamp_start)); + } + return 0; +} diff --git a/alsaloop/test.sh b/alsaloop/test.sh new file mode 100755 index 0000000..2033add --- /dev/null +++ b/alsaloop/test.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +#DBG="gdb --args " +#DBG="strace" +CFGFILE="/tmp/alsaloop.test.cfg" + +test1() { + echo "TEST1" + $DBG ./alsaloop -C hw:1,0 -P hw:0,0 \ + --tlatency 50000 \ + --mixer "name='Master Playback Volume'@name='Master Playback Volume'" \ + --mixer "name='Master Playback Switch'@name='Master Playback Switch'" \ + --mixer "name='PCM Playback Volume'" +} + +test2() { + echo "TEST2" +cat > $CFGFILE <]) AC_CHECK_HEADERS([alsa/seq.h], [have_seq="yes"], [have_seq="no"], [#include ]) +AC_CHECK_HEADERS([samplerate.h], [have_samplerate="yes"], [have_samplerate="no"], + [#include ]) AM_CONDITIONAL(HAVE_PCM, test "$have_pcm" = "yes") AM_CONDITIONAL(HAVE_MIXER, test "$have_mixer" = "yes") AM_CONDITIONAL(HAVE_RAWMIDI, test "$have_rawmidi" = "yes") AM_CONDITIONAL(HAVE_SEQ, test "$have_seq" = "yes") +AM_CONDITIONAL(HAVE_SAMPLERATE, test "$have_samplerate" = "yes") dnl Check for librt LIBRT="" @@ -87,6 +90,16 @@ AC_ARG_ENABLE(alsaconf, esac],[alsaconf=true]) AM_CONDITIONAL(ALSACONF, test x$alsaconf = xtrue) +dnl Disable alsaloop +AC_ARG_ENABLE(alsaloop, + [ --disable-alsaloop Disable alsaloop packaging], + [case "${enableval}" in + yes) alsaloop=true ;; + no) alsaloop=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-alsaloop) ;; + esac],[alsaloop=true]) +AM_CONDITIONAL(ALSALOOP, test x$alsaloop = xtrue) + xmlto="" if test x"$alsaconf" = xtrue; then AC_ARG_ENABLE(xmlto, @@ -273,4 +286,5 @@ AC_OUTPUT(Makefile alsactl/Makefile alsactl/init/Makefile \ aplay/Makefile include/Makefile iecset/Makefile utils/Makefile \ utils/alsa-utils.spec seq/Makefile seq/aconnect/Makefile \ seq/aplaymidi/Makefile seq/aseqdump/Makefile seq/aseqnet/Makefile \ - speaker-test/Makefile speaker-test/samples/Makefile) + speaker-test/Makefile speaker-test/samples/Makefile \ + alsaloop/Makefile)