From: Timo Wischer twischer@de.adit-jv.com
If there is a hardware sound card linked to the loopback device the sound timer of the hardware sound card will be used for this loopback device. Such a link will be created when snd_pcm_link() was called. Linked dummy and loopback devices will be ignored.
This feature can be enabled when loading the module with the following command: $ modprobe snd_aloop enable=1 timer_source=-1
Signed-off-by: Timo Wischer twischer@de.adit-jv.com --- sound/drivers/aloop.c | 187 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 172 insertions(+), 15 deletions(-)
diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c index a411aeb..e2f1d64 100644 --- a/sound/drivers/aloop.c +++ b/sound/drivers/aloop.c @@ -51,7 +51,8 @@ MODULE_LICENSE("GPL"); MODULE_SUPPORTED_DEVICE("{{ALSA,Loopback soundcard}}");
#define MAX_PCM_SUBSTREAMS 8 -#define TIMER_SRC_JIFFIES -1 +#define TIMER_SRC_JIFFIES -2 +#define TIMER_SRC_AUTODETECT -1
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ @@ -76,7 +77,7 @@ MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels change * sound timer is not supported. */ module_param_array(timer_source, charp, NULL, 0444); -MODULE_PARM_DESC(timer_source, "Sound card number of timer to be used (>=0). -2 jiffies timer [default]."); +MODULE_PARM_DESC(timer_source, "Sound card number of timer to be used (>=0). -2 jiffies timer [default]. -1 auto detect sound timer.");
#define NO_PITCH 100000
@@ -252,6 +253,14 @@ static int loopback_snd_timer_start(struct loopback_pcm *dpcm) { int err;
+ /* only in auto detect mode the sound timer will not be opened when the + * device was opened. It will be opened on the first snd_pcm_link() call + */ + if (!dpcm->cable->snd_timer.instance) { + pcm_err(dpcm->substream->pcm, "Sound timer not auto detected. At least one application of the same loopback cable has to call snd_pcm_link() to detect the sound timer."); + return -EINVAL; + } + /* Loopback device has to use same period as timer card. Therefore * wake up for each snd_pcm_period_elapsed() call of timer card. */ @@ -286,6 +295,10 @@ static int loopback_snd_timer_stop(struct loopback_pcm *dpcm) { int err;
+ /* no need to stop the timer if it was not yet opened */ + if (!dpcm->cable->snd_timer.instance) + return 0; + /* only stop if both devices (playback and capture) are not running */ if (dpcm->cable->running) return 0; @@ -307,10 +320,15 @@ static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm) }
/* call in loopback->cable_lock */ -static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm) +static int loopback_snd_timer_close(struct loopback_pcm *dpcm, + struct snd_timer_instance * const timer) { int err;
+ /* no need to close the timer if it was not yet opened */ + if (!timer) + return 0; + /* wait till drain tasklet has finished if requested */ tasklet_kill(&dpcm->cable->snd_timer.event_tasklet);
@@ -319,7 +337,7 @@ static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm) * loopback->cable_lock is locked. Therefore no need to lock * cable->lock; */ - err = snd_timer_close(dpcm->cable->snd_timer.instance); + err = snd_timer_close(timer); if (err < 0) { pcm_err(dpcm->substream->pcm, "snd_timer_close(card %d) = %d", dpcm->cable->snd_timer.id.card, err); @@ -329,6 +347,12 @@ static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm) return err; }
+/* call in loopback->cable_lock */ +static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm) +{ + return loopback_snd_timer_close(dpcm, dpcm->cable->snd_timer.instance); +} + static int loopback_check_format(struct loopback_cable *cable, int stream) { struct snd_pcm_runtime *runtime, *cruntime; @@ -1065,11 +1089,11 @@ static int loopback_snd_card_by_name(const char * const name) return -EINVAL; }
-/* call in loopback->cable_lock */ -static int loopback_snd_timer_open(struct loopback_pcm *dpcm) +/* call in cable->lock */ +static int loopback_snd_timer_source_update(struct loopback_pcm *dpcm) { - int err = 0; - unsigned long flags; + int changed = 0; + struct snd_pcm_substream *s; struct snd_timer_id tid = { .dev_class = SNDRV_TIMER_CLASS_PCM, .dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION, @@ -1078,18 +1102,109 @@ static int loopback_snd_timer_open(struct loopback_pcm *dpcm) .device = 0, .subdevice = 0, }; - struct snd_timer_instance *timer = NULL;
- spin_lock_irqsave(&dpcm->cable->lock, flags); + /* Enforce kernel module parameter if not auto detect */ + if (tid.card != TIMER_SRC_AUTODETECT) { + dpcm->cable->snd_timer.owner = dpcm->substream->stream; + dpcm->cable->snd_timer.id = tid; + return 0; + } + + /* find the first HW device which is linked to this loop device */ + snd_pcm_group_for_each_entry(s, dpcm->substream) { + /* ignore all linked devices also using jiffies timer */ + if (strcmp(s->pcm->card->driver, "Loopback") == 0) + continue; + if (strcmp(s->pcm->card->driver, "Dummy") == 0) + continue; + + tid.card = s->pcm->card->number; + tid.device = s->pcm->device; + tid.subdevice = s->number; + break; + } + + /* check if a sound timer could already be detected */ + if (tid.card < 0) + return -ENODEV; + + /* check if timer source has changed */ + changed = (dpcm->cable->snd_timer.id.card != tid.card || + dpcm->cable->snd_timer.id.device != tid.device || + dpcm->cable->snd_timer.id.subdevice != tid.subdevice); + /* Do not change anything if we are the second device + * which calls snd_pcm_link() + */ + if (changed) { + if (dpcm->cable->snd_timer.owner > 0 && + dpcm->cable->snd_timer.owner != dpcm->substream->stream) { + pcm_warn(dpcm->substream->pcm, "Both devices of the same loopback cable are requesting different sound timers. May be the wrong one is used. (used hw:%d,%d,%d reqested hw:%d,%d,%d)", + dpcm->cable->snd_timer.id.card, + dpcm->cable->snd_timer.id.device, + dpcm->cable->snd_timer.id.subdevice, tid.card, + tid.device, tid.subdevice); + return 0; + } else if (dpcm->cable->running) { + pcm_warn(dpcm->substream->pcm, "Another sound timer was requested but at least one device is already running. May be the wrong one is used. (used hw:%d,%d,%d reqested hw:%d,%d,%d)", + dpcm->cable->snd_timer.id.card, + dpcm->cable->snd_timer.id.device, + dpcm->cable->snd_timer.id.subdevice, tid.card, + tid.device, tid.subdevice); + return 0; + } + } + + /* always return a valid timer id with defined classes. Also in case + * when it looks like card has not changed because hw:0,0,0 should be + * used + */ dpcm->cable->snd_timer.owner = dpcm->substream->stream; dpcm->cable->snd_timer.id = tid;
- /* check if timer was already opened. It is only opened once - * per playback and capture subdevice (aka cable). + return changed; +} + +/* call in loopback->cable_lock */ +static int loopback_snd_timer_open(struct loopback_pcm *dpcm) +{ + int err = 0; + unsigned long flags; + struct snd_timer_instance *timer = NULL; + + spin_lock_irqsave(&dpcm->cable->lock, flags); + err = loopback_snd_timer_source_update(dpcm); + if (err < 0) { + /* Could not yet detect the right sound timer because no valid + * snd_pcm_link() exists. This should not be handled as an error + * because the right timer will be opened with the call to + * snd_pcm_link(). + */ + if (err == -ENODEV) + err = 0; + goto unlock; + } + /* do not reopen the timer if it is already opened and nothing has + * changed */ - if (dpcm->cable->snd_timer.instance) + if (dpcm->cable->snd_timer.instance && err < 1) goto unlock;
+ /* Avoids that any function accesses an invalid timer instance when + * reopening the sound timer. Reopening the sound timer is only + * supported in TIMER_SRC_AUTODETECT mode. snd_pcm_start() will fail on + * the second device when the first device currently reopens the_timer: + * [proc1] Calls snd_pcm_link() -> loopback_timer_open() -> + * Unlock cable->lock for snd_timer_close/open() call + * [proc2] Calls snd_pcm_start() when timer reopening is in progress + * But this is fine because snd_pcm_start() would also fail if + * snd_pcm_link() was not called from any device of the same cable in + * TIMER_SRC_AUTODETECT mode. Therefore the user has to guarantee in + * TIMER_SRC_AUTODETECT mode that snd_pcm_link() is called before anyone + * calls snd_pcm_start() of the same cable. + */ + timer = dpcm->cable->snd_timer.instance; + dpcm->cable->snd_timer.instance = NULL; + /* snd_timer_close() and snd_timer_open() should not be called with * locked spinlock because both functions can block on a mutex. The * mutex loopback->cable_lock is kept locked. Therefore snd_timer_open() @@ -1103,6 +1218,10 @@ static int loopback_snd_timer_open(struct loopback_pcm *dpcm) * instance */ spin_unlock_irqrestore(&dpcm->cable->lock, flags); + /* close timer if there is already something open */ + err = loopback_snd_timer_close(dpcm, timer); + if (err < 0) + return err; err = snd_timer_open(&timer, dpcm->loopback->card->id, &dpcm->cable->snd_timer.id, current->pid); @@ -1137,6 +1256,19 @@ static int loopback_snd_timer_open(struct loopback_pcm *dpcm) return err; }
+static int loopback_snd_timer_link_changed(struct snd_pcm_substream *substream) +{ + int err; + struct loopback_pcm *dpcm = substream->runtime->private_data; + + mutex_lock(&dpcm->loopback->cable_lock); + /* Try to reopen the timer here if the link_list has changed. */ + err = loopback_snd_timer_open(dpcm); + mutex_unlock(&dpcm->loopback->cable_lock); + + return err; +} + /* stop_sync() is not required for sound timer because it does not need to be * restarted in loopback_prepare() on Xrun recovery */ @@ -1148,6 +1280,13 @@ static struct loopback_ops loopback_snd_timer_ops = { .dpcm_info = loopback_snd_timer_dpcm_info, };
+static struct loopback_ops loopback_snd_timer_auto_ops = { + .start = loopback_snd_timer_start, + .stop = loopback_snd_timer_stop, + .close_cable = loopback_snd_timer_close_cable, + .dpcm_info = loopback_snd_timer_dpcm_info, +}; + static int loopback_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; @@ -1178,6 +1317,8 @@ static int loopback_open(struct snd_pcm_substream *substream) cable->hw = loopback_pcm_hardware; if (loopback->timer_source <= TIMER_SRC_JIFFIES) cable->ops = &loopback_jiffies_timer_ops; + else if (loopback->timer_source == TIMER_SRC_AUTODETECT) + cable->ops = &loopback_snd_timer_auto_ops; else cable->ops = &loopback_snd_timer_ops; loopback->cables[substream->number][dev] = cable; @@ -1276,18 +1417,34 @@ static const struct snd_pcm_ops loopback_pcm_ops = { .page = snd_pcm_lib_get_vmalloc_page, };
+static const struct snd_pcm_ops loopback_pcm_auto_ops = { + .open = loopback_open, + .close = loopback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = loopback_hw_params, + .hw_free = loopback_hw_free, + .prepare = loopback_prepare, + .trigger = loopback_trigger, + .pointer = loopback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .link_changed = loopback_snd_timer_link_changed, +}; + static int loopback_pcm_new(struct loopback *loopback, int device, int substreams) { struct snd_pcm *pcm; int err; + const struct snd_pcm_ops * const ops = + (loopback->timer_source == TIMER_SRC_AUTODETECT) ? + &loopback_pcm_auto_ops : &loopback_pcm_ops;
err = snd_pcm_new(loopback->card, "Loopback PCM", device, substreams, substreams, &pcm); if (err < 0) return err; - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &loopback_pcm_ops); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &loopback_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, ops);
pcm->private_data = loopback; pcm->info_flags = 0;