aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJaroslav Kysela <perex@perex.cz>2010-07-16 15:18:08 +0200
committerJaroslav Kysela <perex@perex.cz>2010-07-16 15:20:17 +0200
commit78564f0c9bfc2a8e4f9fcdc0f05df2efc3fd7496 (patch)
treeb62dfd64e464afae2f80109585858e2dc11cb763
parent94d64cd8c69a5d757b714371ae43d93be18aef2a (diff)
downloadalsa-driver-build-unstable-78564f0c9bfc2a8e4f9fcdc0f05df2efc3fd7496.tar.gz
aloop module: separate playback and capture timers
- the timing of playback and capture streams must be separate to ensure proper function - use snd_pcm_format_set_silence() rather than memset(0) - align copy transfers to frame size Signed-off-by: Jaroslav Kysela <perex@perex.cz>
-rw-r--r--drivers/aloop-kernel.c223
1 files changed, 123 insertions, 100 deletions
diff --git a/drivers/aloop-kernel.c b/drivers/aloop-kernel.c
index 2524c8d22..a5c386251 100644
--- a/drivers/aloop-kernel.c
+++ b/drivers/aloop-kernel.c
@@ -10,6 +10,9 @@
* Major (almost complete) rewrite:
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
+ * A next major update in 2010 (separate timers for playback and capture):
+ * Copyright (c) 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
@@ -62,20 +65,12 @@ MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver.");
struct loopback_pcm;
struct loopback_cable {
+ spinlock_t lock;
struct loopback_pcm *streams[2];
struct snd_pcm_hardware hw;
- /* PCM parameters */
- unsigned int pcm_period_size;
- unsigned int pcm_bps; /* bytes per second */
/* flags */
unsigned int valid;
unsigned int running;
- unsigned int period_update_pending :1;
- /* timer stuff */
- unsigned int irq_pos; /* fractional IRQ position */
- unsigned int period_size_frac;
- unsigned long last_jiffies;
- struct timer_list timer;
};
struct loopback {
@@ -92,26 +87,42 @@ struct loopback_pcm {
unsigned int pcm_buffer_size;
unsigned int buf_pos; /* position in buffer */
unsigned int silent_size;
+ /* PCM parameters */
+ unsigned int pcm_period_size;
+ unsigned int pcm_bps; /* bytes per second */
+ unsigned int pcm_salign; /* bytes per sample * channels */
+ /* flags */
+ unsigned int period_update_pending :1;
+ /* timer stuff */
+ unsigned int irq_pos; /* fractional IRQ position */
+ unsigned int period_size_frac;
+ unsigned long last_jiffies;
+ struct timer_list timer;
};
static struct platform_device *devices[SNDRV_CARDS];
-#define byte_pos(x) ((x) / HZ)
+static inline unsigned int byte_pos(struct loopback_pcm *dpcm, unsigned int x)
+{
+ x /= HZ;
+ return x - (x % dpcm->pcm_salign);
+}
+
#define frac_pos(x) ((x) * HZ)
-static void loopback_timer_start(struct loopback_cable *cable)
+static void loopback_timer_start(struct loopback_pcm *dpcm)
{
unsigned long tick;
- tick = cable->period_size_frac - cable->irq_pos;
- tick = (tick + cable->pcm_bps - 1) / cable->pcm_bps;
- cable->timer.expires = jiffies + tick;
- add_timer(&cable->timer);
+ tick = dpcm->period_size_frac - dpcm->irq_pos;
+ tick = (tick + dpcm->pcm_bps - 1) / dpcm->pcm_bps;
+ dpcm->timer.expires = jiffies + tick;
+ add_timer(&dpcm->timer);
}
-static void loopback_timer_stop(struct loopback_cable *cable)
+static inline void loopback_timer_stop(struct loopback_pcm *dpcm)
{
- del_timer(&cable->timer);
+ del_timer(&dpcm->timer);
}
static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
@@ -122,16 +133,13 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
- if (!cable->running) {
- cable->last_jiffies = jiffies;
- loopback_timer_start(cable);
- }
+ dpcm->last_jiffies = jiffies;
+ loopback_timer_start(dpcm);
cable->running |= (1 << substream->stream);
break;
case SNDRV_PCM_TRIGGER_STOP:
cable->running &= ~(1 << substream->stream);
- if (!cable->running)
- loopback_timer_stop(cable);
+ loopback_timer_stop(dpcm);
break;
default:
return -EINVAL;
@@ -144,12 +152,12 @@ static int loopback_prepare(struct snd_pcm_substream *substream)
struct snd_pcm_runtime *runtime = substream->runtime;
struct loopback_pcm *dpcm = runtime->private_data;
struct loopback_cable *cable = dpcm->cable;
- unsigned int bps;
+ unsigned int bps, salign;
- bps = runtime->rate * runtime->channels;
- bps *= snd_pcm_format_width(runtime->format);
- bps /= 8;
- if (bps <= 0)
+ salign = (snd_pcm_format_width(runtime->format) *
+ runtime->channels) / 8;
+ bps = salign * runtime->rate;
+ if (bps <= 0 || salign <= 0)
return -EINVAL;
dpcm->buf_pos = 0;
@@ -157,28 +165,24 @@ static int loopback_prepare(struct snd_pcm_substream *substream)
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
/* clear capture buffer */
dpcm->silent_size = dpcm->pcm_buffer_size;
- memset(runtime->dma_area, 0, dpcm->pcm_buffer_size);
+ snd_pcm_format_set_silence(runtime->format, runtime->dma_area,
+ runtime->buffer_size * runtime->channels);
}
- if (!cable->running) {
- cable->irq_pos = 0;
- cable->period_update_pending = 0;
- }
+ dpcm->irq_pos = 0;
+ dpcm->period_update_pending = 0;
+ dpcm->pcm_bps = bps;
+ dpcm->pcm_salign = salign;
+ dpcm->pcm_period_size = frames_to_bytes(runtime, runtime->period_size);
+ dpcm->period_size_frac = frac_pos(dpcm->pcm_period_size);
mutex_lock(&dpcm->loopback->cable_lock);
if (!(cable->valid & ~(1 << substream->stream))) {
- cable->pcm_bps = bps;
- cable->pcm_period_size =
- frames_to_bytes(runtime, runtime->period_size);
- cable->period_size_frac = frac_pos(cable->pcm_period_size);
-
cable->hw.formats = (1ULL << runtime->format);
cable->hw.rate_min = runtime->rate;
cable->hw.rate_max = runtime->rate;
cable->hw.channels_min = runtime->channels;
cable->hw.channels_max = runtime->channels;
- cable->hw.period_bytes_min = cable->pcm_period_size;
- cable->hw.period_bytes_max = cable->pcm_period_size;
}
cable->valid |= 1 << substream->stream;
mutex_unlock(&dpcm->loopback->cable_lock);
@@ -202,10 +206,7 @@ static void copy_play_buf(struct loopback_pcm *play,
if (dst_off + size > capt->pcm_buffer_size)
size = capt->pcm_buffer_size - dst_off;
memcpy(dst + dst_off, src + src_off, size);
- if (size < capt->silent_size)
- capt->silent_size -= size;
- else
- capt->silent_size = 0;
+ capt->silent_size = 0;
bytes -= size;
if (!bytes)
break;
@@ -216,7 +217,8 @@ static void copy_play_buf(struct loopback_pcm *play,
static void clear_capture_buf(struct loopback_pcm *dpcm, unsigned int bytes)
{
- char *dst = dpcm->substream->runtime->dma_area;
+ struct snd_pcm_runtime *runtime = dpcm->substream->runtime;
+ char *dst = runtime->dma_area;
unsigned int dst_off = dpcm->buf_pos;
if (dpcm->silent_size >= dpcm->pcm_buffer_size)
@@ -228,7 +230,9 @@ static void clear_capture_buf(struct loopback_pcm *dpcm, unsigned int bytes)
unsigned int size = bytes;
if (dst_off + size > dpcm->pcm_buffer_size)
size = dpcm->pcm_buffer_size - dst_off;
- memset(dst + dst_off, 0, size);
+ snd_pcm_format_set_silence(runtime->format, dst + dst_off,
+ bytes_to_frames(runtime, size) *
+ runtime->channels);
dpcm->silent_size += size;
bytes -= size;
if (!bytes)
@@ -237,76 +241,93 @@ static void clear_capture_buf(struct loopback_pcm *dpcm, unsigned int bytes)
}
}
-#define CABLE_PLAYBACK (1 << SNDRV_PCM_STREAM_PLAYBACK)
-#define CABLE_CAPTURE (1 << SNDRV_PCM_STREAM_CAPTURE)
-#define CABLE_BOTH (CABLE_PLAYBACK | CABLE_CAPTURE)
+#define BYTEPOS_UPDATE_POSONLY 0
+#define BYTEPOS_UPDATE_CLEAR 1
+#define BYTEPOS_UPDATE_COPY 2
-static void loopback_xfer_buf(struct loopback_cable *cable, unsigned int count)
+static void loopback_bytepos_update(struct loopback_pcm *dpcm,
+ unsigned int delta,
+ unsigned int cmd)
{
- int i;
+ unsigned int count;
+ unsigned long last_pos;
- switch (cable->running) {
- case CABLE_CAPTURE:
- clear_capture_buf(cable->streams[SNDRV_PCM_STREAM_CAPTURE],
- count);
- break;
- case CABLE_BOTH:
- copy_play_buf(cable->streams[SNDRV_PCM_STREAM_PLAYBACK],
- cable->streams[SNDRV_PCM_STREAM_CAPTURE],
+ last_pos = byte_pos(dpcm, dpcm->irq_pos);
+ dpcm->irq_pos += delta * dpcm->pcm_bps;
+ count = byte_pos(dpcm, dpcm->irq_pos) - last_pos;
+ if (!count)
+ return;
+ if (cmd == BYTEPOS_UPDATE_CLEAR)
+ clear_capture_buf(dpcm, count);
+ else if (cmd == BYTEPOS_UPDATE_COPY)
+ copy_play_buf(dpcm->cable->streams[SNDRV_PCM_STREAM_PLAYBACK],
+ dpcm->cable->streams[SNDRV_PCM_STREAM_CAPTURE],
count);
- break;
- }
-
- for (i = 0; i < 2; i++) {
- if (cable->running & (1 << i)) {
- struct loopback_pcm *dpcm = cable->streams[i];
- dpcm->buf_pos += count;
- dpcm->buf_pos %= dpcm->pcm_buffer_size;
- }
+ dpcm->buf_pos += count;
+ dpcm->buf_pos %= dpcm->pcm_buffer_size;
+ if (dpcm->irq_pos >= dpcm->period_size_frac) {
+ dpcm->irq_pos %= dpcm->period_size_frac;
+ dpcm->period_update_pending = 1;
}
}
static void loopback_pos_update(struct loopback_cable *cable)
{
- unsigned int last_pos, count;
- unsigned long delta;
+ struct loopback_pcm *dpcm_play =
+ cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ struct loopback_pcm *dpcm_capt =
+ cable->streams[SNDRV_PCM_STREAM_CAPTURE];
+ unsigned long delta_play = 0, delta_capt = 0;
+
+ spin_lock(&cable->lock);
+ if (cable->running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) {
+ delta_play = jiffies - dpcm_play->last_jiffies;
+ dpcm_play->last_jiffies += delta_play;
+ }
- if (!cable->running)
- return;
- delta = jiffies - cable->last_jiffies;
- if (!delta)
+ if (cable->running & (1 << SNDRV_PCM_STREAM_CAPTURE)) {
+ delta_capt = jiffies - dpcm_capt->last_jiffies;
+ dpcm_capt->last_jiffies += delta_capt;
+ }
+
+ if (delta_play == 0 && delta_capt == 0) {
+ spin_unlock(&cable->lock);
return;
- cable->last_jiffies += delta;
+ }
+
+ if (delta_play > delta_capt) {
+ loopback_bytepos_update(dpcm_play, delta_play - delta_capt,
+ BYTEPOS_UPDATE_POSONLY);
+ delta_play = delta_capt;
+ } else if (delta_play < delta_capt) {
+ loopback_bytepos_update(dpcm_capt, delta_capt - delta_play,
+ BYTEPOS_UPDATE_CLEAR);
+ delta_capt = delta_play;
+ }
- last_pos = byte_pos(cable->irq_pos);
- cable->irq_pos += delta * cable->pcm_bps;
- count = byte_pos(cable->irq_pos) - last_pos;
- if (!count)
+ if (delta_play == 0 && delta_capt == 0) {
+ spin_unlock(&cable->lock);
return;
- loopback_xfer_buf(cable, count);
- if (cable->irq_pos >= cable->period_size_frac) {
- cable->irq_pos %= cable->period_size_frac;
- cable->period_update_pending = 1;
}
+ /* note delta_capt == delta_play at this moment */
+ loopback_bytepos_update(dpcm_capt, delta_capt, BYTEPOS_UPDATE_COPY);
+ loopback_bytepos_update(dpcm_play, delta_play, BYTEPOS_UPDATE_POSONLY);
+ spin_unlock(&cable->lock);
}
static void loopback_timer_function(unsigned long data)
{
- struct loopback_cable *cable = (struct loopback_cable *)data;
- int i;
+ struct loopback_pcm *dpcm = (struct loopback_pcm *)data;
+ int stream;
- if (!cable->running)
- return;
- loopback_pos_update(cable);
- loopback_timer_start(cable);
- if (cable->period_update_pending) {
- cable->period_update_pending = 0;
- for (i = 0; i < 2; i++) {
- if (cable->running & (1 << i)) {
- struct loopback_pcm *dpcm = cable->streams[i];
- snd_pcm_period_elapsed(dpcm->substream);
- }
- }
+ loopback_pos_update(dpcm->cable);
+ stream = dpcm->substream->stream;
+ if (dpcm->cable->running & (1 << stream))
+ loopback_timer_start(dpcm);
+ if (dpcm->period_update_pending) {
+ dpcm->period_update_pending = 0;
+ if (dpcm->cable->running & (1 << stream))
+ snd_pcm_period_elapsed(dpcm->substream);
}
}
@@ -388,6 +409,8 @@ static int loopback_open(struct snd_pcm_substream *substream)
}
dpcm->loopback = loopback;
dpcm->substream = substream;
+ setup_timer(&dpcm->timer, loopback_timer_function,
+ (unsigned long)dpcm);
cable = loopback->cables[substream->number][dev];
if (!cable) {
@@ -397,9 +420,8 @@ static int loopback_open(struct snd_pcm_substream *substream)
err = -ENOMEM;
goto unlock;
}
+ spin_lock_init(&cable->lock);
cable->hw = loopback_pcm_hardware;
- setup_timer(&cable->timer, loopback_timer_function,
- (unsigned long)cable);
loopback->cables[substream->number][dev] = cable;
}
dpcm->cable = cable;
@@ -418,9 +440,11 @@ static int loopback_open(struct snd_pcm_substream *substream)
static int loopback_close(struct snd_pcm_substream *substream)
{
struct loopback *loopback = substream->private_data;
+ struct loopback_pcm *dpcm = substream->runtime->private_data;
struct loopback_cable *cable;
int dev = get_cable_index(substream);
+ loopback_timer_stop(dpcm);
mutex_lock(&loopback->cable_lock);
cable = loopback->cables[substream->number][dev];
if (cable->streams[!substream->stream]) {
@@ -428,7 +452,6 @@ static int loopback_close(struct snd_pcm_substream *substream)
cable->streams[substream->stream] = NULL;
} else {
/* free the cable */
- del_timer(&cable->timer);
loopback->cables[substream->number][dev] = NULL;
kfree(cable);
}