The 'd' and 'm' options were not in the same order as the long options. Fix that. No functional change, but makes it easier to ensure they are kept in sync. Closes: https://github.com/alsa-project/alsa-utils/pull/317 Signed-off-by: Peter Korsgaard <peter@korsgaard.com> Signed-off-by: Jaroslav Kysela <perex@perex.cz>
953 lines
24 KiB
C
953 lines
24 KiB
C
/*
|
|
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#include "aconfig.h"
|
|
#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 <signal.h>
|
|
#include "alsaloop.h"
|
|
#include "os_compat.h"
|
|
|
|
struct loopback_thread {
|
|
int threaded;
|
|
pthread_t thread;
|
|
int exitcode;
|
|
struct loopback **loopbacks;
|
|
int loopbacks_count;
|
|
snd_output_t *output;
|
|
};
|
|
|
|
int quit = 0;
|
|
int verbose = 0;
|
|
int workarounds = 0;
|
|
int daemonize = 0;
|
|
int use_syslog = 0;
|
|
struct loopback **loopbacks = NULL;
|
|
int loopbacks_count = 0;
|
|
char **my_argv = NULL;
|
|
int my_argc = 0;
|
|
struct loopback_thread *threads;
|
|
int threads_count = 0;
|
|
pthread_t main_job;
|
|
int arg_default_xrun = 0;
|
|
int arg_default_wake = 0;
|
|
|
|
static void my_exit(struct loopback_thread *thread, int exitcode)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < thread->loopbacks_count; i++)
|
|
pcmjob_done(thread->loopbacks[i]);
|
|
if (thread->threaded) {
|
|
thread->exitcode = exitcode;
|
|
pthread_exit(0);
|
|
}
|
|
exit(exitcode);
|
|
}
|
|
|
|
static int create_loopback_handle(struct loopback_handle **_handle,
|
|
const char *device,
|
|
const char *ctldev,
|
|
const char *id)
|
|
{
|
|
char idbuf[1024];
|
|
struct loopback_handle *handle;
|
|
|
|
handle = calloc(1, sizeof(*handle));
|
|
if (handle == NULL)
|
|
return -ENOMEM;
|
|
if (device == NULL)
|
|
device = "hw:0,0";
|
|
handle->device = strdup(device);
|
|
if (handle->device == NULL) {
|
|
free(handle);
|
|
return -ENOMEM;
|
|
}
|
|
if (ctldev) {
|
|
handle->ctldev = strdup(ctldev);
|
|
if (handle->ctldev == NULL) {
|
|
free(handle->device);
|
|
free(handle);
|
|
return -ENOMEM;
|
|
}
|
|
} else {
|
|
handle->ctldev = NULL;
|
|
}
|
|
snprintf(idbuf, sizeof(idbuf)-1, "%s %s", id, device);
|
|
idbuf[sizeof(idbuf)-1] = '\0';
|
|
handle->id = strdup(idbuf);
|
|
handle->access = SND_PCM_ACCESS_RW_INTERLEAVED;
|
|
handle->format = SND_PCM_FORMAT_S16_LE;
|
|
handle->rate = handle->rate_req = 48000;
|
|
handle->channels = 2;
|
|
handle->resample = 0;
|
|
*_handle = handle;
|
|
return 0;
|
|
}
|
|
|
|
static int create_loopback(struct loopback **_handle,
|
|
struct loopback_handle *play,
|
|
struct loopback_handle *capt,
|
|
snd_output_t *output)
|
|
{
|
|
struct loopback *handle;
|
|
|
|
handle = calloc(1, sizeof(*handle));
|
|
if (handle == NULL)
|
|
return -ENOMEM;
|
|
handle->play = play;
|
|
handle->capt = capt;
|
|
play->loopback = handle;
|
|
capt->loopback = handle;
|
|
handle->latency_req = 0;
|
|
handle->latency_reqtime = 10000;
|
|
handle->loop_time = ~0UL;
|
|
handle->loop_limit = ~0ULL;
|
|
handle->output = output;
|
|
handle->state = output;
|
|
#ifdef USE_SAMPLERATE
|
|
handle->src_enable = 1;
|
|
handle->src_converter_type = SRC_SINC_BEST_QUALITY;
|
|
#endif
|
|
*_handle = handle;
|
|
return 0;
|
|
}
|
|
|
|
static void set_loop_time(struct loopback *loop, unsigned long loop_time)
|
|
{
|
|
loop->loop_time = loop_time;
|
|
loop->loop_limit = loop->capt->rate * loop_time;
|
|
}
|
|
|
|
static void setscheduler(void)
|
|
{
|
|
struct sched_param sched_param;
|
|
|
|
if (sched_getparam(0, &sched_param) < 0) {
|
|
logit(LOG_WARNING, "Scheduler getparam failed.\n");
|
|
return;
|
|
}
|
|
sched_param.sched_priority = sched_get_priority_max(SCHED_RR);
|
|
if (!sched_setscheduler(0, SCHED_RR, &sched_param)) {
|
|
if (verbose)
|
|
logit(LOG_WARNING, "Scheduler set to Round Robin with priority %i\n", sched_param.sched_priority);
|
|
return;
|
|
}
|
|
if (verbose)
|
|
logit(LOG_INFO, "!!!Scheduler set to Round Robin with priority %i FAILED!\n", sched_param.sched_priority);
|
|
}
|
|
|
|
void help(void)
|
|
{
|
|
int k;
|
|
printf(
|
|
"Usage: alsaloop [OPTION]...\n\n"
|
|
"-h,--help help\n"
|
|
"-g,--config configuration file (one line = one job specified)\n"
|
|
"-d,--daemonize daemonize the main process and use syslog for errors\n"
|
|
"-P,--pdevice playback device\n"
|
|
"-C,--cdevice capture device\n"
|
|
"-X,--pctl playback ctl device\n"
|
|
"-Y,--cctl capture ctl device\n"
|
|
"-x,--prateshift playback 'PCM Rate Shift 100000' ascii ctl name\n"
|
|
"-l,--latency requested latency in frames\n"
|
|
"-t,--tlatency requested latency in usec (1/1000000sec)\n"
|
|
"-f,--format sample format\n"
|
|
"-c,--channels channels\n"
|
|
"-r,--rate rate\n"
|
|
"-n,--resample resample in alsa-lib\n"
|
|
"-A,--samplerate use converter (0=sincbest,1=sincmedium,2=sincfastest,\n"
|
|
" 3=zerohold,4=linear)\n"
|
|
"-B,--buffer buffer size in frames\n"
|
|
"-E,--period period size in frames\n"
|
|
"-s,--seconds duration of loop in seconds\n"
|
|
"-b,--nblock non-block mode (very early process wakeup)\n"
|
|
"-S,--sync sync mode(0=none,1=simple,2=captshift,3=playshift,4=samplerate,\n"
|
|
" 5=auto)\n"
|
|
"-a,--slave stream parameters slave mode (0=auto, 1=on, 2=off)\n"
|
|
"-T,--thread thread number (-1 = create unique)\n"
|
|
"-m,--mixer redirect mixer, argument is:\n"
|
|
" SRC_SLAVE_ID(PLAYBACK)[@DST_SLAVE_ID(CAPTURE)]\n"
|
|
"-O,--ossmixer rescan and redirect oss mixer, argument is:\n"
|
|
" ALSA_ID@OSS_ID (for example: \"Master@VOLUME\")\n"
|
|
"-e,--effect apply an effect (bandpass filter sweep)\n"
|
|
"-v,--verbose verbose mode (more -v means more verbose)\n"
|
|
"-w,--workaround use workaround (serialopen)\n"
|
|
"-U,--xrun xrun profiling\n"
|
|
"-W,--wake process wake timeout in ms\n"
|
|
"-z,--syslog use syslog for errors\n"
|
|
);
|
|
printf("\nRecognized sample formats are:");
|
|
for (k = 0; k < SND_PCM_FORMAT_LAST; ++k) {
|
|
const char *s = snd_pcm_format_name(k);
|
|
if (s)
|
|
printf(" %s", s);
|
|
}
|
|
printf("\n\n");
|
|
printf(
|
|
"Tip #1 (usable 500ms latency, good CPU usage, superb xrun prevention):\n"
|
|
" alsaloop -t 500000\n"
|
|
"Tip #2 (superb 1ms latency, but heavy CPU usage):\n"
|
|
" alsaloop -t 1000\n"
|
|
);
|
|
}
|
|
|
|
static long timediff(struct timeval t1, struct timeval t2)
|
|
{
|
|
signed long l;
|
|
|
|
t1.tv_sec -= t2.tv_sec;
|
|
l = (signed long) t1.tv_usec - (signed long) t2.tv_usec;
|
|
if (l < 0) {
|
|
t1.tv_sec--;
|
|
l = 1000000 + l;
|
|
l %= 1000000;
|
|
}
|
|
return (t1.tv_sec * 1000000) + l;
|
|
}
|
|
|
|
static void add_loop(struct loopback *loop)
|
|
{
|
|
loopbacks = realloc(loopbacks, (loopbacks_count + 1) *
|
|
sizeof(struct loopback *));
|
|
if (loopbacks == NULL) {
|
|
logit(LOG_CRIT, "No enough memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
loopbacks[loopbacks_count++] = loop;
|
|
}
|
|
|
|
static int init_mixer_control(struct loopback_control *control,
|
|
char *id)
|
|
{
|
|
int err;
|
|
|
|
err = snd_ctl_elem_id_malloc(&control->id);
|
|
if (err < 0)
|
|
return err;
|
|
err = snd_ctl_elem_info_malloc(&control->info);
|
|
if (err < 0)
|
|
return err;
|
|
err = snd_ctl_elem_value_malloc(&control->value);
|
|
if (err < 0)
|
|
return err;
|
|
err = control_parse_id(id, control->id);
|
|
if (err < 0)
|
|
return err;
|
|
return 0;
|
|
}
|
|
|
|
static int add_mixers(struct loopback *loop,
|
|
char **mixers,
|
|
int mixers_count)
|
|
{
|
|
struct loopback_mixer *mixer, *last = NULL;
|
|
char *str1;
|
|
int err;
|
|
|
|
while (mixers_count > 0) {
|
|
mixer = calloc(1, sizeof(*mixer));
|
|
if (mixer == NULL)
|
|
return -ENOMEM;
|
|
if (last)
|
|
last->next = mixer;
|
|
else
|
|
loop->controls = mixer;
|
|
last = mixer;
|
|
str1 = strchr(*mixers, '@');
|
|
if (str1)
|
|
*str1 = '\0';
|
|
err = init_mixer_control(&mixer->src, *mixers);
|
|
if (err < 0) {
|
|
logit(LOG_CRIT, "Wrong mixer control ID syntax '%s'\n", *mixers);
|
|
return -EINVAL;
|
|
}
|
|
err = init_mixer_control(&mixer->dst, str1 ? str1 + 1 : *mixers);
|
|
if (err < 0) {
|
|
logit(LOG_CRIT, "Wrong mixer control ID syntax '%s'\n", str1 ? str1 + 1 : *mixers);
|
|
return -EINVAL;
|
|
}
|
|
if (str1)
|
|
*str1 = '@';
|
|
mixers++;
|
|
mixers_count--;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int add_oss_mixers(struct loopback *loop,
|
|
char **mixers,
|
|
int mixers_count)
|
|
{
|
|
struct loopback_ossmixer *mixer, *last = NULL;
|
|
char *str1, *str2;
|
|
|
|
while (mixers_count > 0) {
|
|
mixer = calloc(1, sizeof(*mixer));
|
|
if (mixer == NULL)
|
|
return -ENOMEM;
|
|
if (last)
|
|
last->next = mixer;
|
|
else
|
|
loop->oss_controls = mixer;
|
|
last = mixer;
|
|
str1 = strchr(*mixers, ',');
|
|
if (str1)
|
|
*str1 = '\0';
|
|
str2 = strchr(str1 ? str1 + 1 : *mixers, '@');
|
|
if (str2)
|
|
*str2 = '\0';
|
|
mixer->alsa_id = strdup(*mixers);
|
|
if (str1)
|
|
mixer->alsa_index = atoi(str1);
|
|
mixer->oss_id = strdup(str2 ? str2 + 1 : *mixers);
|
|
if (mixer->alsa_id == NULL || mixer->oss_id == NULL) {
|
|
logit(LOG_CRIT, "Not enough memory");
|
|
return -ENOMEM;
|
|
}
|
|
if (str1)
|
|
*str1 = ',';
|
|
if (str2)
|
|
*str2 = ',';
|
|
mixers++;
|
|
mixers_count--;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void enable_syslog(void)
|
|
{
|
|
if (!use_syslog) {
|
|
use_syslog = 1;
|
|
openlog("alsaloop", LOG_NDELAY|LOG_PID, LOG_DAEMON);
|
|
}
|
|
}
|
|
|
|
static int parse_config_file(const char *file, snd_output_t *output);
|
|
|
|
static int parse_config(int argc, char *argv[], snd_output_t *output,
|
|
int cmdline)
|
|
{
|
|
struct option long_option[] =
|
|
{
|
|
{"help", 0, NULL, 'h'},
|
|
{"config", 1, NULL, 'g'},
|
|
{"daemonize", 0, NULL, 'd'},
|
|
{"pdevice", 1, NULL, 'P'},
|
|
{"cdevice", 1, NULL, 'C'},
|
|
{"pctl", 1, NULL, 'X'},
|
|
{"cctl", 1, NULL, 'Y'},
|
|
{"prateshift", 1, NULL, 'x'},
|
|
{"latency", 1, NULL, 'l'},
|
|
{"tlatency", 1, NULL, 't'},
|
|
{"format", 1, NULL, 'f'},
|
|
{"channels", 1, NULL, 'c'},
|
|
{"rate", 1, NULL, 'r'},
|
|
{"buffer", 1, NULL, 'B'},
|
|
{"period", 1, NULL, 'E'},
|
|
{"seconds", 1, NULL, 's'},
|
|
{"nblock", 0, NULL, 'b'},
|
|
{"effect", 0, NULL, 'e'},
|
|
{"verbose", 0, NULL, 'v'},
|
|
{"resample", 0, NULL, 'n'},
|
|
{"samplerate", 1, NULL, 'A'},
|
|
{"sync", 1, NULL, 'S'},
|
|
{"slave", 1, NULL, 'a'},
|
|
{"thread", 1, NULL, 'T'},
|
|
{"mixer", 1, NULL, 'm'},
|
|
{"ossmixer", 1, NULL, 'O'},
|
|
{"workaround", 1, NULL, 'w'},
|
|
{"xrun", 0, NULL, 'U'},
|
|
{"wake", 1, NULL, 'W'},
|
|
{"syslog", 0, NULL, 'z'},
|
|
{NULL, 0, NULL, 0},
|
|
};
|
|
int err, morehelp;
|
|
char *arg_config = NULL;
|
|
char *arg_pdevice = NULL;
|
|
char *arg_cdevice = NULL;
|
|
char *arg_pctl = NULL;
|
|
char *arg_cctl = NULL;
|
|
char *arg_prateshift = 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;
|
|
#ifdef USE_SAMPLERATE
|
|
int arg_samplerate = SRC_SINC_FASTEST + 1;
|
|
#endif
|
|
int arg_sync = SYNC_TYPE_AUTO;
|
|
int arg_slave = SLAVE_TYPE_AUTO;
|
|
int arg_thread = 0;
|
|
struct loopback *loop = NULL;
|
|
char *arg_mixers[MAX_MIXERS];
|
|
int arg_mixers_count = 0;
|
|
char *arg_ossmixers[MAX_MIXERS];
|
|
int arg_ossmixers_count = 0;
|
|
int arg_xrun = arg_default_xrun;
|
|
int arg_wake = arg_default_wake;
|
|
|
|
morehelp = 0;
|
|
while (1) {
|
|
int c;
|
|
if ((c = getopt_long(argc, argv,
|
|
"hg:dP:C:X:Y:x:l:t:f:c:r:B:E:s:benvA:S:a:T:m:O:w:UW:z",
|
|
long_option, NULL)) < 0)
|
|
break;
|
|
switch (c) {
|
|
case 'h':
|
|
morehelp++;
|
|
break;
|
|
case 'g':
|
|
arg_config = strdup(optarg);
|
|
break;
|
|
case 'd':
|
|
daemonize = 1;
|
|
enable_syslog();
|
|
break;
|
|
case 'P':
|
|
arg_pdevice = strdup(optarg);
|
|
break;
|
|
case 'C':
|
|
arg_cdevice = strdup(optarg);
|
|
break;
|
|
case 'X':
|
|
arg_pctl = strdup(optarg);
|
|
break;
|
|
case 'Y':
|
|
arg_cctl = strdup(optarg);
|
|
break;
|
|
case 'x':
|
|
arg_prateshift = strdup(optarg);
|
|
break;
|
|
case 'l':
|
|
err = atoi(optarg);
|
|
arg_latency_req = err >= 4 ? err : 4;
|
|
break;
|
|
case 't':
|
|
err = atoi(optarg);
|
|
arg_latency_reqtime = err >= 500 ? err : 500;
|
|
break;
|
|
case 'f':
|
|
arg_format = snd_pcm_format_value(optarg);
|
|
if (arg_format == SND_PCM_FORMAT_UNKNOWN) {
|
|
logit(LOG_WARNING, "Unknown format, setting to default S16_LE\n");
|
|
arg_format = SND_PCM_FORMAT_S16_LE;
|
|
}
|
|
break;
|
|
case 'c':
|
|
err = atoi(optarg);
|
|
arg_channels = err >= 1 && err < 1024 ? err : 1;
|
|
break;
|
|
case 'r':
|
|
err = atoi(optarg);
|
|
arg_rate = err >= 4000 && err < 200000 ? err : 44100;
|
|
break;
|
|
case 'B':
|
|
err = atoi(optarg);
|
|
arg_buffer_size = err >= 32 && err < 200000 ? err : 0;
|
|
break;
|
|
case 'E':
|
|
err = atoi(optarg);
|
|
arg_period_size = err >= 32 && err < 200000 ? err : 0;
|
|
break;
|
|
case 's':
|
|
err = atoi(optarg);
|
|
arg_loop_time = err >= 1 && err <= 100000 ? err : 30;
|
|
break;
|
|
case 'b':
|
|
arg_nblock = 1;
|
|
break;
|
|
case 'e':
|
|
// arg_effect = 1;
|
|
break;
|
|
case 'n':
|
|
arg_resample = 1;
|
|
break;
|
|
#ifdef USE_SAMPLERATE
|
|
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;
|
|
#endif
|
|
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 if (optarg[0] == 'a')
|
|
arg_sync = SYNC_TYPE_AUTO;
|
|
else
|
|
arg_sync = atoi(optarg);
|
|
if (arg_sync < 0 || arg_sync > SYNC_TYPE_LAST)
|
|
arg_sync = SYNC_TYPE_AUTO;
|
|
break;
|
|
case 'a':
|
|
if (optarg[0] == 'a')
|
|
arg_slave = SLAVE_TYPE_AUTO;
|
|
else if (strcasecmp(optarg, "on") == 0)
|
|
arg_slave = SLAVE_TYPE_ON;
|
|
else if (strcasecmp(optarg, "off") == 0)
|
|
arg_slave = SLAVE_TYPE_OFF;
|
|
else
|
|
arg_slave = atoi(optarg);
|
|
if (arg_slave < 0 || arg_slave > SLAVE_TYPE_LAST)
|
|
arg_slave = SLAVE_TYPE_AUTO;
|
|
break;
|
|
case 'T':
|
|
arg_thread = atoi(optarg);
|
|
if (arg_thread < 0)
|
|
arg_thread = 10000000 + loopbacks_count;
|
|
break;
|
|
case 'm':
|
|
if (arg_mixers_count >= MAX_MIXERS) {
|
|
logit(LOG_CRIT, "Maximum redirected mixer controls reached (max %i)\n", (int)MAX_MIXERS);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
arg_mixers[arg_mixers_count++] = optarg;
|
|
break;
|
|
case 'O':
|
|
if (arg_ossmixers_count >= MAX_MIXERS) {
|
|
logit(LOG_CRIT, "Maximum redirected mixer controls reached (max %i)\n", (int)MAX_MIXERS);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
arg_ossmixers[arg_ossmixers_count++] = optarg;
|
|
break;
|
|
case 'v':
|
|
verbose++;
|
|
break;
|
|
case 'w':
|
|
if (strcasecmp(optarg, "serialopen") == 0)
|
|
workarounds |= WORKAROUND_SERIALOPEN;
|
|
break;
|
|
case 'U':
|
|
arg_xrun = 1;
|
|
if (cmdline)
|
|
arg_default_xrun = 1;
|
|
break;
|
|
case 'W':
|
|
arg_wake = atoi(optarg);
|
|
if (cmdline)
|
|
arg_default_wake = arg_wake;
|
|
break;
|
|
case 'z':
|
|
enable_syslog();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (morehelp) {
|
|
help();
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
if (arg_config == NULL) {
|
|
struct loopback_handle *play;
|
|
struct loopback_handle *capt;
|
|
err = create_loopback_handle(&play, arg_pdevice, arg_pctl, "playback");
|
|
if (err < 0) {
|
|
logit(LOG_CRIT, "Unable to create playback handle.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
err = create_loopback_handle(&capt, arg_cdevice, arg_cctl, "capture");
|
|
if (err < 0) {
|
|
logit(LOG_CRIT, "Unable to create capture handle.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
err = create_loopback(&loop, play, capt, output);
|
|
if (err < 0) {
|
|
logit(LOG_CRIT, "Unable to create loopback handle.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
play->format = capt->format = arg_format;
|
|
play->rate = play->rate_req = capt->rate = capt->rate_req = arg_rate;
|
|
play->channels = capt->channels = arg_channels;
|
|
play->buffer_size_req = capt->buffer_size_req = arg_buffer_size;
|
|
play->period_size_req = capt->period_size_req = arg_period_size;
|
|
play->resample = capt->resample = arg_resample;
|
|
play->nblock = capt->nblock = arg_nblock ? 1 : 0;
|
|
loop->latency_req = arg_latency_req;
|
|
loop->latency_reqtime = arg_latency_reqtime;
|
|
loop->sync = arg_sync;
|
|
loop->slave = arg_slave;
|
|
loop->thread = arg_thread;
|
|
loop->xrun = arg_xrun;
|
|
loop->wake = arg_wake;
|
|
err = add_mixers(loop, arg_mixers, arg_mixers_count);
|
|
if (err < 0) {
|
|
logit(LOG_CRIT, "Unable to add mixer controls.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
err = add_oss_mixers(loop, arg_ossmixers, arg_ossmixers_count);
|
|
if (err < 0) {
|
|
logit(LOG_CRIT, "Unable to add ossmixer controls.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (arg_prateshift)
|
|
play->prateshift_name = arg_prateshift;
|
|
|
|
#ifdef USE_SAMPLERATE
|
|
loop->src_enable = arg_samplerate > 0;
|
|
if (loop->src_enable)
|
|
loop->src_converter_type = arg_samplerate - 1;
|
|
#endif
|
|
set_loop_time(loop, arg_loop_time);
|
|
add_loop(loop);
|
|
return 0;
|
|
}
|
|
|
|
return parse_config_file(arg_config, output);
|
|
}
|
|
|
|
static int parse_config_file(const char *file, snd_output_t *output)
|
|
{
|
|
FILE *fp;
|
|
char line[2048], word[2048];
|
|
char *str, *ptr;
|
|
int argc, c, err = 0;
|
|
char **argv;
|
|
|
|
fp = fopen(file, "r");
|
|
if (fp == NULL) {
|
|
logit(LOG_CRIT, "Unable to open file '%s': %s\n", file, strerror(errno));
|
|
return -EIO;
|
|
}
|
|
while (!feof(fp)) {
|
|
if (fgets(line, sizeof(line)-1, fp) == NULL)
|
|
break;
|
|
line[sizeof(line)-1] = '\0';
|
|
my_argv = realloc(my_argv, my_argc + MAX_ARGS * sizeof(char *));
|
|
if (my_argv == NULL)
|
|
return -ENOMEM;
|
|
argv = my_argv + my_argc;
|
|
argc = 0;
|
|
argv[argc++] = strdup("<prog>");
|
|
my_argc++;
|
|
str = line;
|
|
while (*str) {
|
|
ptr = word;
|
|
while (*str && (*str == ' ' || *str < ' '))
|
|
str++;
|
|
if (*str == '#')
|
|
goto __next;
|
|
if (*str == '\'' || *str == '\"') {
|
|
c = *str++;
|
|
while (*str && *str != c)
|
|
*ptr++ = *str++;
|
|
if (*str == c)
|
|
str++;
|
|
} else {
|
|
while (*str && *str != ' ' && *str != '\t')
|
|
*ptr++ = *str++;
|
|
}
|
|
if (ptr != word) {
|
|
if (*(ptr-1) == '\n')
|
|
ptr--;
|
|
*ptr = '\0';
|
|
if (argc >= MAX_ARGS) {
|
|
logit(LOG_CRIT, "Too many arguments.");
|
|
goto __error;
|
|
}
|
|
argv[argc++] = strdup(word);
|
|
my_argc++;
|
|
}
|
|
}
|
|
/* erase runtime variables for getopt */
|
|
optarg = NULL;
|
|
optind = opterr = 1;
|
|
optopt = '?';
|
|
|
|
err = parse_config(argc, argv, output, 0);
|
|
__next:
|
|
if (err < 0)
|
|
break;
|
|
err = 0;
|
|
}
|
|
__error:
|
|
fclose(fp);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void thread_job1(void *_data)
|
|
{
|
|
struct loopback_thread *thread = _data;
|
|
snd_output_t *output = thread->output;
|
|
struct pollfd *pfds = NULL;
|
|
int pfds_count = 0;
|
|
int i, j, err, wake = 1000000;
|
|
|
|
setscheduler();
|
|
|
|
for (i = 0; i < thread->loopbacks_count; i++) {
|
|
err = pcmjob_init(thread->loopbacks[i]);
|
|
if (err < 0) {
|
|
logit(LOG_CRIT, "Loopback initialization failure.\n");
|
|
my_exit(thread, EXIT_FAILURE);
|
|
}
|
|
}
|
|
for (i = 0; i < thread->loopbacks_count; i++) {
|
|
err = pcmjob_start(thread->loopbacks[i]);
|
|
if (err < 0) {
|
|
logit(LOG_CRIT, "Loopback start failure.\n");
|
|
my_exit(thread, EXIT_FAILURE);
|
|
}
|
|
pfds_count += thread->loopbacks[i]->pollfd_count;
|
|
j = thread->loopbacks[i]->wake;
|
|
if (j > 0 && j < wake)
|
|
wake = j;
|
|
}
|
|
if (wake >= 1000000)
|
|
wake = -1;
|
|
pfds = calloc(pfds_count, sizeof(struct pollfd));
|
|
if (pfds == NULL || pfds_count <= 0) {
|
|
logit(LOG_CRIT, "Poll FDs allocation failed.\n");
|
|
my_exit(thread, EXIT_FAILURE);
|
|
}
|
|
while (!quit) {
|
|
struct timeval tv1, tv2;
|
|
for (i = j = 0; i < thread->loopbacks_count; i++) {
|
|
err = pcmjob_pollfds_init(thread->loopbacks[i], &pfds[j]);
|
|
if (err < 0) {
|
|
logit(LOG_CRIT, "Poll FD initialization failed.\n");
|
|
my_exit(thread, EXIT_FAILURE);
|
|
}
|
|
j += err;
|
|
}
|
|
if (verbose > 10)
|
|
gettimeofday(&tv1, NULL);
|
|
err = poll(pfds, j, wake);
|
|
if (err < 0)
|
|
err = -errno;
|
|
if (verbose > 10) {
|
|
gettimeofday(&tv2, NULL);
|
|
snd_output_printf(output, "pool took %lius\n", timediff(tv2, tv1));
|
|
}
|
|
if (err < 0) {
|
|
if (err == -EINTR || err == -ERESTART)
|
|
continue;
|
|
logit(LOG_CRIT, "Poll failed: %s\n", strerror(-err));
|
|
my_exit(thread, EXIT_FAILURE);
|
|
}
|
|
for (i = j = 0; i < thread->loopbacks_count; i++) {
|
|
struct loopback *loop = thread->loopbacks[i];
|
|
if (j < loop->active_pollfd_count) {
|
|
err = pcmjob_pollfds_handle(loop, &pfds[j]);
|
|
if (err < 0) {
|
|
logit(LOG_CRIT, "pcmjob failed.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
j += loop->active_pollfd_count;
|
|
}
|
|
}
|
|
|
|
my_exit(thread, EXIT_SUCCESS);
|
|
}
|
|
|
|
static void thread_job(struct loopback_thread *thread)
|
|
{
|
|
if (!thread->threaded) {
|
|
thread_job1(thread);
|
|
return;
|
|
}
|
|
pthread_create(&thread->thread, NULL, (void *) &thread_job1,
|
|
(void *) thread);
|
|
}
|
|
|
|
static void send_to_all(int sig)
|
|
{
|
|
struct loopback_thread *thread;
|
|
int i;
|
|
|
|
for (i = 0; i < threads_count; i++) {
|
|
thread = &threads[i];
|
|
if (thread->threaded)
|
|
pthread_kill(thread->thread, sig);
|
|
}
|
|
}
|
|
|
|
static void signal_handler(int sig ATTRIBUTE_UNUSED)
|
|
{
|
|
quit = 1;
|
|
send_to_all(SIGUSR2);
|
|
}
|
|
|
|
static void signal_handler_state(int sig)
|
|
{
|
|
pthread_t self = pthread_self();
|
|
struct loopback_thread *thread;
|
|
int i, j;
|
|
|
|
if (pthread_equal(main_job, self))
|
|
send_to_all(SIGUSR1);
|
|
for (i = 0; i < threads_count; i++) {
|
|
thread = &threads[i];
|
|
if (thread->thread == self) {
|
|
for (j = 0; j < thread->loopbacks_count; j++)
|
|
pcmjob_state(thread->loopbacks[j]);
|
|
}
|
|
}
|
|
signal(sig, signal_handler_state);
|
|
}
|
|
|
|
static void signal_handler_ignore(int sig)
|
|
{
|
|
signal(sig, signal_handler_ignore);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
snd_output_t *output;
|
|
int i, j, k, l, err;
|
|
|
|
err = snd_output_stdio_attach(&output, stdout, 0);
|
|
if (err < 0) {
|
|
logit(LOG_CRIT, "Output failed: %s\n", snd_strerror(err));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
err = parse_config(argc, argv, output, 1);
|
|
if (err < 0) {
|
|
logit(LOG_CRIT, "Unable to parse arguments or configuration...\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
while (my_argc > 0)
|
|
free(my_argv[--my_argc]);
|
|
free(my_argv);
|
|
|
|
if (loopbacks_count <= 0) {
|
|
logit(LOG_CRIT, "No loopback defined...\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (daemonize) {
|
|
if (daemon(0, 0) < 0) {
|
|
logit(LOG_CRIT, "daemon() failed: %s\n", strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
i = fork();
|
|
if (i < 0) {
|
|
logit(LOG_CRIT, "fork() failed: %s\n", strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (i > 0) {
|
|
/* wait(&i); */
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
}
|
|
|
|
/* we must sort thread IDs */
|
|
j = -1;
|
|
do {
|
|
k = 0x7fffffff;
|
|
for (i = 0; i < loopbacks_count; i++) {
|
|
if (loopbacks[i]->thread < k &&
|
|
loopbacks[i]->thread > j)
|
|
k = loopbacks[i]->thread;
|
|
}
|
|
j++;
|
|
for (i = 0; i < loopbacks_count; i++) {
|
|
if (loopbacks[i]->thread == k)
|
|
loopbacks[i]->thread = j;
|
|
}
|
|
} while (k != 0x7fffffff);
|
|
/* fix maximum thread id */
|
|
for (i = 0, j = -1; i < loopbacks_count; i++) {
|
|
if (loopbacks[i]->thread > j)
|
|
j = loopbacks[i]->thread;
|
|
}
|
|
j += 1;
|
|
threads = calloc(1, sizeof(struct loopback_thread) * j);
|
|
if (threads == NULL) {
|
|
logit(LOG_CRIT, "No enough memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
/* sort all threads */
|
|
for (k = 0; k < j; k++) {
|
|
for (i = l = 0; i < loopbacks_count; i++)
|
|
if (loopbacks[i]->thread == k)
|
|
l++;
|
|
threads[k].loopbacks = malloc(l * sizeof(struct loopback *));
|
|
threads[k].loopbacks_count = l;
|
|
threads[k].output = output;
|
|
threads[k].threaded = j > 1;
|
|
for (i = l = 0; i < loopbacks_count; i++)
|
|
if (loopbacks[i]->thread == k)
|
|
threads[k].loopbacks[l++] = loopbacks[i];
|
|
}
|
|
threads_count = j;
|
|
main_job = pthread_self();
|
|
|
|
signal(SIGINT, signal_handler);
|
|
signal(SIGTERM, signal_handler);
|
|
signal(SIGABRT, signal_handler);
|
|
signal(SIGUSR1, signal_handler_state);
|
|
signal(SIGUSR2, signal_handler_ignore);
|
|
|
|
for (k = 0; k < threads_count; k++)
|
|
thread_job(&threads[k]);
|
|
|
|
if (j > 1) {
|
|
for (k = 0; k < threads_count; k++)
|
|
pthread_join(threads[k].thread, NULL);
|
|
}
|
|
|
|
if (use_syslog)
|
|
closelog();
|
|
exit(EXIT_SUCCESS);
|
|
}
|