forked from pool/alsa-utils
Takashi Iwai
3331bcfb82
* A few alsactl init fix patches: * amixer control-id parse fix * new aloop utility * robusitfy speaker-test * misc clean up, translation updates - Use systemd for openSUSE 11.4 - Put udev rules into this package instead of alsa.rpm OBS-URL: https://build.opensuse.org/package/show/multimedia:libs/alsa-utils?expand=0&rev=23
3314 lines
93 KiB
Diff
3314 lines
93 KiB
Diff
From 1e75673035ffc0971719974715211d6a0a48e61a Mon Sep 17 00:00:00 2001
|
|
From: Jaroslav Kysela <perex@perex.cz>
|
|
Date: Wed, 18 Aug 2010 08:29:03 +0200
|
|
Subject: [PATCH 17/38] 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 <perex@perex.cz>
|
|
---
|
|
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 deletions(-)
|
|
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/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 <file>\fP | \fI\-\-config=<file>\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 <device>\fP | \fI\-\-pdevice=<device>\fP
|
|
+
|
|
+Use given playback device.
|
|
+
|
|
+.TP
|
|
+\fI\-C <device>\fP | \fI\-\-cdevice=<device>\fP
|
|
+
|
|
+Use given capture device.
|
|
+
|
|
+.TP
|
|
+\fI\-l <latency>\fP | \fI\-\-latency=<frames>\fP
|
|
+
|
|
+Requested latency in frames.
|
|
+
|
|
+.TP
|
|
+\fI\-t <usec>\fP | \fI\-\-tlatency=<usec>\fP
|
|
+
|
|
+Requested latency in usec (1/1000000sec).
|
|
+
|
|
+.TP
|
|
+\fI\-f <format>\fP | \fI\-\-format=<format>\fP
|
|
+
|
|
+Format specification (usually S16_LE S32_LE). Use -h to list all formats.
|
|
+Default format is S16_LE.
|
|
+
|
|
+.TP
|
|
+\fI\-c <channels>\fP | \fI\-\-channels=<channels>\fP
|
|
+
|
|
+Channel count specification. Default value is 2.
|
|
+
|
|
+.TP
|
|
+\fI\-c <rate>\fP | \fI\-\-rate=<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 <converter>\fP | \fI\-\-samplerate=<converter>\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 <size>\fP | \fI\-\-buffer=<size>\fP
|
|
+
|
|
+Buffer size in frames.
|
|
+
|
|
+.TP
|
|
+\fI\-E <size>\fP | \fI\-\-period=<size>\fP
|
|
+
|
|
+Period size in frames.
|
|
+
|
|
+.TP
|
|
+\fI\-s <secs>\fP | \fI\-\-seconds=<secs>\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 <mode>\fP | \fI\-\-sync=<mode>\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 <num>\fP | \fI\-\-thread=<num>\fP
|
|
+
|
|
+Thread number (-1 means create a unique thread). All jobs with same
|
|
+thread numbers are run within one thread.
|
|
+
|
|
+.TP
|
|
+\fI\-m <mixid>\fP | \fI\-\-mixer=<midid>\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 <perex@perex.cz>.
|
|
+This document is by Jaroslav Kysela <perex@perex.cz>.
|
|
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 <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 "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("<prog>");
|
|
+ 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 <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
|
|
+
|
|
+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 <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);
|
|
+ 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 <mdeboer@iua.upf.es>
|
|
+ *
|
|
+ * 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 <math.h>
|
|
+#include <alsa/asoundlib.h>
|
|
+
|
|
+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 <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 "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 <<EOF
|
|
+# first job
|
|
+-C hw:1,0,0 -P hw:0,0,0 --tlatency 50000 --thread 1 \
|
|
+ --mixer "name='Master Playback Volume'@name='Master Playback Volume'" \
|
|
+ --mixer "name='Master Playback Switch'@name='Master Playback Switch'" \
|
|
+ --mixer "name='PCM Playback Volume'"
|
|
+# next line - second job
|
|
+-C hw:1,0,1 -P hw:0,1,0 --tlatency 50000 --thread 2
|
|
+EOF
|
|
+ $DBG ./alsaloop -d --config $CFGFILE
|
|
+}
|
|
+
|
|
+case "$1" in
|
|
+test1) test1 ;;
|
|
+test2) test2 ;;
|
|
+*) test1 ;;
|
|
+esac
|
|
diff --git a/configure.in b/configure.in
|
|
index 8bae007..31fd581 100644
|
|
--- a/configure.in
|
|
+++ b/configure.in
|
|
@@ -38,11 +38,14 @@ AC_CHECK_HEADERS([alsa/rawmidi.h], [have_rawmidi="yes"], [have_rawmidi="no"],
|
|
[#include <alsa/asoundlib.h>])
|
|
AC_CHECK_HEADERS([alsa/seq.h], [have_seq="yes"], [have_seq="no"],
|
|
[#include <alsa/asoundlib.h>])
|
|
+AC_CHECK_HEADERS([samplerate.h], [have_samplerate="yes"], [have_samplerate="no"],
|
|
+ [#include <samplerate.h>])
|
|
|
|
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)
|
|
--
|
|
1.7.3.1
|
|
|