alsaloop: Fixes and added --workaround option

- added workaround for alsa-lib (pthread configuration parsing issue) -
  the workaround must be activated manually using ('--workaround serialopen')
- fixed avail_min initialization (caused high CPU usage or xruns)
- fixed shared buffer initialization (both capture and playback buffers
  must have equal number of samples in this config)

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
This commit is contained in:
Jaroslav Kysela 2010-10-11 10:24:14 +02:00
parent e77983d3c5
commit bee994f509
5 changed files with 112 additions and 38 deletions

View file

@ -1,5 +1,6 @@
INCLUDES = -I$(top_srcdir)/include INCLUDES = -I$(top_srcdir)/include
LDADD = -lm LDADD = -lm
CFLAGS += -D_GNU_SOURCE
if HAVE_SAMPLERATE if HAVE_SAMPLERATE
LDADD += -lsamplerate LDADD += -lsamplerate
endif endif

View file

@ -43,6 +43,7 @@ struct loopback_thread {
}; };
int verbose = 0; int verbose = 0;
int workarounds = 0;
int daemonize = 0; int daemonize = 0;
int use_syslog = 0; int use_syslog = 0;
struct loopback **loopbacks = NULL; struct loopback **loopbacks = NULL;
@ -171,6 +172,7 @@ void help(void)
" ALSA_ID@OSS_ID (for example: \"Master@VOLUME\")\n" " ALSA_ID@OSS_ID (for example: \"Master@VOLUME\")\n"
"-e,--effect apply an effect (bandpass filter sweep)\n" "-e,--effect apply an effect (bandpass filter sweep)\n"
"-v,--verbose verbose mode (more -v means more verbose)\n" "-v,--verbose verbose mode (more -v means more verbose)\n"
"-w,--workaround use workaround (serialopen)\n"
); );
printf("\nRecognized sample formats are:"); printf("\nRecognized sample formats are:");
for (k = 0; k < SND_PCM_FORMAT_LAST; ++k) { for (k = 0; k < SND_PCM_FORMAT_LAST; ++k) {
@ -339,6 +341,7 @@ static int parse_config(int argc, char *argv[], snd_output_t *output)
{"thread", 1, NULL, 'T'}, {"thread", 1, NULL, 'T'},
{"mixer", 1, NULL, 'm'}, {"mixer", 1, NULL, 'm'},
{"ossmixer", 1, NULL, 'O'}, {"ossmixer", 1, NULL, 'O'},
{"workaround", 1, NULL, 'w'},
{NULL, 0, NULL, 0}, {NULL, 0, NULL, 0},
}; };
int err, morehelp; int err, morehelp;
@ -370,7 +373,7 @@ static int parse_config(int argc, char *argv[], snd_output_t *output)
while (1) { while (1) {
int c; int c;
if ((c = getopt_long(argc, argv, if ((c = getopt_long(argc, argv,
"hdg:P:C:l:t:F:f:c:r:s:benvA:S:a:m:T:O:", "hdg:P:C:l:t:F:f:c:r:s:benvA:S:a:m:T:O:w:",
long_option, NULL)) < 0) long_option, NULL)) < 0)
break; break;
switch (c) { switch (c) {
@ -504,6 +507,10 @@ static int parse_config(int argc, char *argv[], snd_output_t *output)
case 'v': case 'v':
verbose++; verbose++;
break; break;
case 'w':
if (strcasecmp(optarg, "serialopen") == 0)
workarounds |= WORKAROUND_SERIALOPEN;
break;
} }
} }

View file

@ -40,6 +40,8 @@ enum {
#define FILE_CWRITE "/tmp/alsaloop.craw" #define FILE_CWRITE "/tmp/alsaloop.craw"
#endif #endif
#define WORKAROUND_SERIALOPEN (1<<0)
typedef enum _sync_type { typedef enum _sync_type {
SYNC_TYPE_NONE = 0, SYNC_TYPE_NONE = 0,
SYNC_TYPE_SIMPLE, /* add or remove samples */ SYNC_TYPE_SIMPLE, /* add or remove samples */
@ -171,6 +173,7 @@ struct loopback {
}; };
extern int verbose; extern int verbose;
extern int workarounds;
extern int use_syslog; extern int use_syslog;
#define logit(priority, fmt, args...) do { \ #define logit(priority, fmt, args...) do { \

View file

@ -31,9 +31,11 @@
#include <sys/time.h> #include <sys/time.h>
#include <math.h> #include <math.h>
#include <syslog.h> #include <syslog.h>
#include <pthread.h>
#include "alsaloop.h" #include "alsaloop.h"
static int set_rate_shift(struct loopback_handle *lhandle, double pitch); static int set_rate_shift(struct loopback_handle *lhandle, double pitch);
static int get_rate(struct loopback_handle *lhandle);
#define SYNCTYPE(v) [SYNC_TYPE_##v] = #v #define SYNCTYPE(v) [SYNC_TYPE_##v] = #v
@ -56,6 +58,21 @@ static const char *src_types[] = {
SRCTYPE(LINEAR) SRCTYPE(LINEAR)
}; };
static pthread_mutex_t pcm_open_mutex =
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
static inline void pcm_open_lock(void)
{
if (workarounds & WORKAROUND_SERIALOPEN)
pthread_mutex_lock(&pcm_open_mutex);
}
static inline void pcm_open_unlock(void)
{
if (workarounds & WORKAROUND_SERIALOPEN)
pthread_mutex_unlock(&pcm_open_mutex);
}
static inline snd_pcm_uframes_t get_whole_latency(struct loopback *loop) static inline snd_pcm_uframes_t get_whole_latency(struct loopback *loop)
{ {
return loop->latency; return loop->latency;
@ -76,7 +93,7 @@ static int setparams_stream(struct loopback_handle *lhandle,
err = snd_pcm_hw_params_any(handle, params); err = snd_pcm_hw_params_any(handle, params);
if (err < 0) { if (err < 0) {
logit(LOG_CRIT, "Broken configuration for %s PCM: no configurations available: %s\n", snd_strerror(err), lhandle->id); logit(LOG_CRIT, "Broken configuration for %s PCM: no configurations available: %s\n", lhandle->id, snd_strerror(err));
return err; return err;
} }
err = snd_pcm_hw_params_set_rate_resample(handle, params, lhandle->resample); err = snd_pcm_hw_params_set_rate_resample(handle, params, lhandle->resample);
@ -153,6 +170,8 @@ static int setparams_bufsize(struct loopback_handle *lhandle,
goto __again; goto __again;
} }
snd_pcm_hw_params_get_buffer_size(params, &periodsize); snd_pcm_hw_params_get_buffer_size(params, &periodsize);
if (verbose > 6)
snd_output_printf(lhandle->loopback->output, "%s: buffer_size=%li\n", lhandle->id, periodsize);
if (lhandle->period_size_req > 0) if (lhandle->period_size_req > 0)
periodsize = lhandle->period_size_req; periodsize = lhandle->period_size_req;
else else
@ -163,6 +182,8 @@ static int setparams_bufsize(struct loopback_handle *lhandle,
goto __again; goto __again;
} }
snd_pcm_hw_params_get_period_size(params, &periodsize, NULL); snd_pcm_hw_params_get_period_size(params, &periodsize, NULL);
if (verbose > 6)
snd_output_printf(lhandle->loopback->output, "%s: period_size=%li\n", lhandle->id, periodsize);
if (periodsize != bufsize) if (periodsize != bufsize)
bufsize = periodsize; bufsize = periodsize;
snd_pcm_hw_params_get_buffer_size(params, &buffersize); snd_pcm_hw_params_get_buffer_size(params, &buffersize);
@ -175,11 +196,12 @@ static int setparams_bufsize(struct loopback_handle *lhandle,
static int setparams_set(struct loopback_handle *lhandle, static int setparams_set(struct loopback_handle *lhandle,
snd_pcm_hw_params_t *params, snd_pcm_hw_params_t *params,
snd_pcm_sw_params_t *swparams) snd_pcm_sw_params_t *swparams,
snd_pcm_uframes_t bufsize)
{ {
snd_pcm_t *handle = lhandle->handle; snd_pcm_t *handle = lhandle->handle;
int err; int err;
snd_pcm_uframes_t val, val1; snd_pcm_uframes_t val, period_size, buffer_size;
err = snd_pcm_hw_params(handle, params); err = snd_pcm_hw_params(handle, params);
if (err < 0) { if (err < 0) {
@ -196,21 +218,29 @@ static int setparams_set(struct loopback_handle *lhandle,
logit(LOG_CRIT, "Unable to set start threshold mode for %s: %s\n", lhandle->id, snd_strerror(err)); logit(LOG_CRIT, "Unable to set start threshold mode for %s: %s\n", lhandle->id, snd_strerror(err));
return err; return err;
} }
snd_pcm_hw_params_get_period_size(params, &val, NULL); snd_pcm_hw_params_get_period_size(params, &period_size, NULL);
snd_pcm_hw_params_get_buffer_size(params, &val1); snd_pcm_hw_params_get_buffer_size(params, &buffer_size);
if (lhandle->nblock) { if (lhandle->nblock) {
if (lhandle == lhandle->loopback->play) { if (lhandle == lhandle->loopback->play) {
val = val1 - (2 * val - 4); val = buffer_size - (2 * period_size - 4);
} else { } else {
val = 4; val = 4;
} }
if (verbose > 6)
snd_output_printf(lhandle->loopback->output, "%s: avail_min1=%li\n", lhandle->id, val);
} else { } else {
if (lhandle == lhandle->loopback->play) { if (lhandle == lhandle->loopback->play) {
snd_pcm_hw_params_get_buffer_size(params, &val1); val = bufsize / 2 + bufsize / 4 + bufsize / 8;
val = val1 - val - val / 2; if (val > buffer_size / 4)
val = buffer_size / 4;
val = buffer_size - val;
} else { } else {
val /= 2; val = bufsize / 2;
if (val > buffer_size / 4)
val = buffer_size / 4;
} }
if (verbose > 6)
snd_output_printf(lhandle->loopback->output, "%s: avail_min2=%li\n", lhandle->id, val);
} }
err = snd_pcm_sw_params_set_avail_min(handle, swparams, val); err = snd_pcm_sw_params_set_avail_min(handle, swparams, val);
if (err < 0) { if (err < 0) {
@ -256,11 +286,11 @@ static int setparams(struct loopback *loop, snd_pcm_uframes_t bufsize)
return err; return err;
} }
if ((err = setparams_set(loop->play, p_params, p_swparams)) < 0) { if ((err = setparams_set(loop->play, p_params, p_swparams, bufsize / loop->play->pitch)) < 0) {
logit(LOG_CRIT, "Unable to set sw parameters for %s stream: %s\n", loop->play->id, snd_strerror(err)); logit(LOG_CRIT, "Unable to set sw parameters for %s stream: %s\n", loop->play->id, snd_strerror(err));
return err; return err;
} }
if ((err = setparams_set(loop->capt, c_params, c_swparams)) < 0) { if ((err = setparams_set(loop->capt, c_params, c_swparams, bufsize / loop->capt->pitch)) < 0) {
logit(LOG_CRIT, "Unable to set sw parameters for %s stream: %s\n", loop->capt->id, snd_strerror(err)); logit(LOG_CRIT, "Unable to set sw parameters for %s stream: %s\n", loop->capt->id, snd_strerror(err));
return err; return err;
} }
@ -495,6 +525,12 @@ static int readit(struct loopback_handle *lhandle)
int err; int err;
avail = snd_pcm_avail_update(lhandle->handle); avail = snd_pcm_avail_update(lhandle->handle);
if (avail == -EPIPE) {
return xrun(lhandle);
} else if (avail == -ESTRPIPE) {
if ((err = suspend(lhandle)) < 0)
return err;
}
if (avail > buf_avail(lhandle)) { if (avail > buf_avail(lhandle)) {
lhandle->buf_over += avail - buf_avail(lhandle); lhandle->buf_over += avail - buf_avail(lhandle);
avail = buf_avail(lhandle); avail = buf_avail(lhandle);
@ -1008,7 +1044,10 @@ static int openit(struct loopback_handle *lhandle)
SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_PLAYBACK :
SND_PCM_STREAM_CAPTURE; SND_PCM_STREAM_CAPTURE;
int err, card, device, subdevice; int err, card, device, subdevice;
if ((err = snd_pcm_open(&lhandle->handle, lhandle->device, stream, SND_PCM_NONBLOCK)) < 0) { pcm_open_lock();
err = snd_pcm_open(&lhandle->handle, lhandle->device, stream, SND_PCM_NONBLOCK);
pcm_open_unlock();
if (err < 0) {
logit(LOG_CRIT, "%s open error: %s\n", lhandle->id, snd_strerror(err)); logit(LOG_CRIT, "%s open error: %s\n", lhandle->id, snd_strerror(err));
return err; return err;
} }
@ -1027,7 +1066,9 @@ static int openit(struct loopback_handle *lhandle)
if (card >= 0) { if (card >= 0) {
char name[16]; char name[16];
sprintf(name, "hw:%i", card); sprintf(name, "hw:%i", card);
pcm_open_lock();
err = snd_ctl_open(&lhandle->ctl, name, SND_CTL_NONBLOCK); err = snd_ctl_open(&lhandle->ctl, name, SND_CTL_NONBLOCK);
pcm_open_unlock();
if (err < 0) { if (err < 0) {
logit(LOG_CRIT, "%s [%s] ctl open error: %s\n", lhandle->id, name, snd_strerror(err)); logit(LOG_CRIT, "%s [%s] ctl open error: %s\n", lhandle->id, name, snd_strerror(err));
lhandle->ctl = NULL; lhandle->ctl = NULL;
@ -1254,6 +1295,17 @@ int pcmjob_start(struct loopback *loop)
goto __error; goto __error;
} }
loop->play->buf = nbuf; loop->play->buf = nbuf;
loop->play->buf_size = loop->capt->buf_size;
} else if (loop->capt->buf_size < loop->play->buf_size) {
char *nbuf = realloc(loop->capt->buf,
loop->play->buf_size *
loop->play->frame_size);
if (nbuf == NULL) {
err = -ENOMEM;
goto __error;
}
loop->capt->buf = nbuf;
loop->capt->buf_size = loop->play->buf_size;
} }
loop->capt->buf = loop->play->buf; loop->capt->buf = loop->play->buf;
} else { } else {
@ -1278,7 +1330,7 @@ int pcmjob_start(struct loopback *loop)
if (loop->use_samplerate) { if (loop->use_samplerate) {
if (loop->capt->format != SND_PCM_FORMAT_S16 || if (loop->capt->format != SND_PCM_FORMAT_S16 ||
loop->play->format != SND_PCM_FORMAT_S16) { loop->play->format != SND_PCM_FORMAT_S16) {
logit(LOG_CRIT, "samplerate conversion supports only S16_LE format (%i, %i)\n", loop->play->format, loop->capt->format); logit(LOG_CRIT, "samplerate conversion supports only %s format (play=%s, capt=%s)\n", snd_pcm_format_name(SND_PCM_FORMAT_S16), snd_pcm_format_name(loop->play->format), snd_pcm_format_name(loop->capt->format));
loop->use_samplerate = 0; loop->use_samplerate = 0;
err = -EIO; err = -EIO;
goto __error; goto __error;
@ -1325,6 +1377,8 @@ int pcmjob_start(struct loopback *loop)
logit(LOG_CRIT, "%s: silence error\n", loop->id); logit(LOG_CRIT, "%s: silence error\n", loop->id);
goto __error; goto __error;
} }
if (verbose > 4)
snd_output_printf(loop->output, "%s: capt->buffer_size = %li, play->buffer_size = %li\n", loop->id, loop->capt->buf_size, loop->play->buf_size);
loop->pitch = 1.0; loop->pitch = 1.0;
update_pitch(loop); update_pitch(loop);
loop->pitch_delta = 1.0 / ((double)loop->capt->rate * 4); loop->pitch_delta = 1.0 / ((double)loop->capt->rate * 4);
@ -1334,8 +1388,13 @@ int pcmjob_start(struct loopback *loop)
loop->play->buf_count = count; loop->play->buf_count = count;
if (loop->play->buf == loop->capt->buf) if (loop->play->buf == loop->capt->buf)
loop->capt->buf_pos = count; loop->capt->buf_pos = count;
if (writeit(loop->play) != count) { err = writeit(loop->play);
logit(LOG_CRIT, "%s: initial playback fill error\n", loop->id); if (verbose > 4)
snd_output_printf(loop->output, "%s: silence queued %i samples\n", loop->id, err);
if (count > loop->play->buffer_size)
count = loop->play->buffer_size;
if (err != count) {
logit(LOG_CRIT, "%s: initial playback fill error (%i/%i/%i)\n", loop->id, err, (int)count, loop->play->buffer_size);
err = -EIO; err = -EIO;
goto __error; goto __error;
} }
@ -1511,13 +1570,13 @@ int pcmjob_pollfds_handle(struct loopback *loop, struct pollfd *fds)
if (verbose > 12) { if (verbose > 12) {
snd_pcm_sframes_t pdelay, cdelay; snd_pcm_sframes_t pdelay, cdelay;
if ((err = snd_pcm_delay(play->handle, &pdelay)) < 0) if ((err = snd_pcm_delay(play->handle, &pdelay)) < 0)
snd_output_printf(loop->output, "%s: delay error: %s\n", play->id, snd_strerror(err)); snd_output_printf(loop->output, "%s: delay error: %s / %li / %li\n", play->id, snd_strerror(err), play->buf_size, play->buf_count);
else else
snd_output_printf(loop->output, "%s: delay %li\n", play->id, pdelay); snd_output_printf(loop->output, "%s: delay %li / %li / %li\n", play->id, pdelay, play->buf_size, play->buf_count);
if ((err = snd_pcm_delay(capt->handle, &cdelay)) < 0) if ((err = snd_pcm_delay(capt->handle, &cdelay)) < 0)
snd_output_printf(loop->output, "%s: delay error: %s\n", capt->id, snd_strerror(err)); snd_output_printf(loop->output, "%s: delay error: %s / %li / %li\n", capt->id, snd_strerror(err), capt->buf_size, capt->buf_count);
else else
snd_output_printf(loop->output, "%s: delay %li\n", capt->id, cdelay); snd_output_printf(loop->output, "%s: delay %li / %li / %li\n", capt->id, cdelay, capt->buf_size, capt->buf_count);
} }
idx = 0; idx = 0;
if (loop->running) { if (loop->running) {
@ -1570,7 +1629,7 @@ int pcmjob_pollfds_handle(struct loopback *loop, struct pollfd *fds)
} }
if (verbose > 9) if (verbose > 9)
snd_output_printf(loop->output, "%s: prevents = 0x%x, crevents = 0x%x\n", loop->id, prevents, crevents); snd_output_printf(loop->output, "%s: prevents = 0x%x, crevents = 0x%x\n", loop->id, prevents, crevents);
if (prevents == 0 && crevents == 0) if (!loop->running)
goto __pcm_end; goto __pcm_end;
do { do {
ccount = readit(capt); ccount = readit(capt);
@ -1637,22 +1696,22 @@ int pcmjob_pollfds_handle(struct loopback *loop, struct pollfd *fds)
if (verbose > 4) if (verbose > 4)
snd_output_printf(loop->output, "%s: queued %li/%li samples\n", loop->id, pqueued, cqueued); snd_output_printf(loop->output, "%s: queued %li/%li samples\n", loop->id, pqueued, cqueued);
if (pqueued > 0) if (pqueued > 0)
loop->play->total_queued += pqueued; play->total_queued += pqueued;
if (cqueued > 0) if (cqueued > 0)
loop->capt->total_queued += cqueued; capt->total_queued += cqueued;
if (pqueued > 0 || cqueued > 0) if (pqueued > 0 || cqueued > 0)
loop->total_queued_count += 1; loop->total_queued_count += 1;
} }
if (verbose > 12) { if (verbose > 12) {
snd_pcm_sframes_t pdelay, cdelay; snd_pcm_sframes_t pdelay, cdelay;
if ((err = snd_pcm_delay(play->handle, &pdelay)) < 0) 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)); snd_output_printf(loop->output, "%s: end delay error: %s / %li / %li\n", play->id, snd_strerror(err), play->buf_size, play->buf_count);
else else
snd_output_printf(loop->output, "%s: end delay %li\n", play->id, pdelay); snd_output_printf(loop->output, "%s: end delay %li / %li / %li\n", play->id, pdelay, play->buf_size, play->buf_count);
if ((err = snd_pcm_delay(capt->handle, &cdelay)) < 0) 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)); snd_output_printf(loop->output, "%s: end delay error: %s / %li / %li\n", capt->id, snd_strerror(err), capt->buf_size, capt->buf_count);
else else
snd_output_printf(loop->output, "%s: end delay %li\n", capt->id, cdelay); snd_output_printf(loop->output, "%s: end delay %li / %li / %li\n", capt->id, cdelay, capt->buf_size, capt->buf_count);
} }
__pcm_end: __pcm_end:
if (verbose > 13) { if (verbose > 13) {

View file

@ -3,6 +3,7 @@
#DBG="gdb --args " #DBG="gdb --args "
#DBG="strace" #DBG="strace"
#DBG="valgrind --leak-check=full" #DBG="valgrind --leak-check=full"
ARGS=
CFGFILE="/tmp/alsaloop.test.cfg" CFGFILE="/tmp/alsaloop.test.cfg"
test1() { test1() {
@ -13,7 +14,8 @@ test1() {
--mixer "name='Master Playback Switch'@name='Master Playback Switch'" \ --mixer "name='Master Playback Switch'@name='Master Playback Switch'" \
--mixer "name='PCM Playback Volume'" \ --mixer "name='PCM Playback Volume'" \
--ossmixer "Master@VOLUME" \ --ossmixer "Master@VOLUME" \
--ossmixer "PCM@PCM" --ossmixer "PCM@PCM" \
$ARGS
} }
test2() { test2() {
@ -27,7 +29,7 @@ cat > $CFGFILE <<EOF
# next line - second job # next line - second job
-C hw:1,0,1 -P hw:0,1,0 --tlatency 50000 --thread 2 -C hw:1,0,1 -P hw:0,1,0 --tlatency 50000 --thread 2
EOF EOF
$DBG ./alsaloop -d --config $CFGFILE $DBG ./alsaloop -d --config $CFGFILE $ARGS
} }
test3() { test3() {
@ -46,7 +48,8 @@ cat > $CFGFILE <<EOF
-C hw:1,0,6 -P plug:dmix:0 --tlatency 50000 --thread 6 -C hw:1,0,6 -P plug:dmix:0 --tlatency 50000 --thread 6
-C hw:1,0,7 -P plug:dmix:0 --tlatency 50000 --thread 7 -C hw:1,0,7 -P plug:dmix:0 --tlatency 50000 --thread 7
EOF EOF
$DBG ./alsaloop --config $CFGFILE LD_PRELOAD="/home/perex/alsa/alsa-lib/src/.libs/libasound.so" \
$DBG ./alsaloop --config $CFGFILE $ARGS
} }
test4() { test4() {
@ -55,7 +58,8 @@ test4() {
--tlatency 50000 \ --tlatency 50000 \
--mixer "name='Master Playback Volume'@name='Master Playback Volume'" \ --mixer "name='Master Playback Volume'@name='Master Playback Volume'" \
--mixer "name='Master Playback Switch'@name='Master Playback Switch'" \ --mixer "name='Master Playback Switch'@name='Master Playback Switch'" \
--mixer "name='PCM Playback Volume'" --mixer "name='PCM Playback Volume'" \
$ARGS
} }
test5() { test5() {
@ -68,14 +72,14 @@ cat > $CFGFILE <<EOF
--ossmixer "name=Master@VOLUME" --ossmixer "name=Master@VOLUME"
-C hw:1,0,1 -P plughw:0,1 --tlatency 50000 --thread 2 -C hw:1,0,1 -P plughw:0,1 --tlatency 50000 --thread 2
EOF EOF
$DBG ./alsaloop --config $CFGFILE $DBG ./alsaloop --config $CFGFILE $ARGS
} }
case "$1" in case "$1" in
test1) test1 ;; test1) shift; ARGS="$@"; test1 ;;
test2) test2 ;; test2) shift; ARGS="$@"; test2 ;;
test3) test3 ;; test3) shift; ARGS="$@"; test3 ;;
test4) test4 ;; test4) shift; ARGS="$@"; test4 ;;
test5) test5 ;; test5) shift; ARGS="$@"; test5 ;;
*) test1 ;; *) ARGS="$@"; test1 ;;
esac esac