Skip to content

Commit

Permalink
[ALSA] usb-audio: double-buffer all playback data
Browse files Browse the repository at this point in the history
USB generic driver
We always had to use double buffering when capturing, and when playback
data for one URB crosses a buffer boundary.  The latter would make hwptr
updates less precise because the double-buffered data is read from the
buffer much earlier than the other data is read by the host controller.

Double-buffering all data allows to update hwptr immediately after the
data was copied to the USB buffer(s), which has the additional benefit
of avoiding the latency imposed by the host controller's delay of up to
one frame when interrupting.

Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
  • Loading branch information
Clemens Ladisch authored and Jaroslav Kysela committed Aug 30, 2005
1 parent b263a9b commit 7efd8bc
Showing 1 changed file with 65 additions and 86 deletions.
151 changes: 65 additions & 86 deletions sound/usb/usbaudio.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include <sound/driver.h>
#include <linux/bitops.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/string.h>
Expand Down Expand Up @@ -129,8 +130,6 @@ struct snd_urb_ctx {
snd_usb_substream_t *subs;
int index; /* index for urb array */
int packets; /* number of packets per urb */
int transfer; /* transferred size */
char *buf; /* buffer for capture */
};

struct snd_urb_ops {
Expand Down Expand Up @@ -168,9 +167,7 @@ struct snd_usb_substream {

unsigned int running: 1; /* running status */

unsigned int hwptr; /* free frame position in the buffer (only for playback) */
unsigned int hwptr_done; /* processed frame position in the buffer */
unsigned int transfer_sched; /* scheduled frames since last period (for playback) */
unsigned int transfer_done; /* processed frames since last period update */
unsigned long active_mask; /* bitmask of active urbs */
unsigned long unlink_mask; /* bitmask of unlinked urbs */
Expand All @@ -179,12 +176,12 @@ struct snd_usb_substream {
snd_urb_ctx_t dataurb[MAX_URBS]; /* data urb table */
snd_urb_ctx_t syncurb[SYNC_URBS]; /* sync urb table */
char syncbuf[SYNC_URBS * 4]; /* sync buffer; it's so small - let's get static */
char *tmpbuf; /* temporary buffer for playback */

u64 formats; /* format bitmasks (all or'ed) */
unsigned int num_formats; /* number of supported audio formats (list) */
struct list_head fmt_list; /* format list */
spinlock_t lock;
struct tasklet_struct start_period_elapsed; /* for start trigger */

struct snd_urb_ops ops; /* callbacks (must be filled at init) */
};
Expand Down Expand Up @@ -320,7 +317,6 @@ static int prepare_capture_urb(snd_usb_substream_t *subs,
urb->iso_frame_desc[i].length = subs->curpacksize;
offs += subs->curpacksize;
}
urb->transfer_buffer = ctx->buf;
urb->transfer_buffer_length = offs;
urb->number_of_packets = ctx->packets;
#if 0 // for check
Expand Down Expand Up @@ -482,12 +478,10 @@ static int retire_playback_sync_urb_hs(snd_usb_substream_t *subs,
/*
* prepare urb for playback data pipe
*
* we copy the data directly from the pcm buffer.
* the current position to be copied is held in hwptr field.
* since a urb can handle only a single linear buffer, if the total
* transferred area overflows the buffer boundary, we cannot send
* it directly from the buffer. thus the data is once copied to
* a temporary buffer and urb points to that.
* Since a URB can handle only a single linear buffer, we must use double
* buffering when the data to be transferred overflows the buffer boundary.
* To avoid inconsistencies when updating hwptr_done, we use double buffering
* for all URBs.
*/
static int prepare_playback_urb(snd_usb_substream_t *subs,
snd_pcm_runtime_t *runtime,
Expand All @@ -496,6 +490,7 @@ static int prepare_playback_urb(snd_usb_substream_t *subs,
int i, stride, offs;
unsigned int counts;
unsigned long flags;
int period_elapsed = 0;
snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;

stride = runtime->frame_bits >> 3;
Expand All @@ -520,80 +515,81 @@ static int prepare_playback_urb(snd_usb_substream_t *subs,
urb->iso_frame_desc[i].length = counts * stride;
offs += counts;
urb->number_of_packets++;
subs->transfer_sched += counts;
if (subs->transfer_sched >= runtime->period_size) {
subs->transfer_sched -= runtime->period_size;
subs->transfer_done += counts;
if (subs->transfer_done >= runtime->period_size) {
subs->transfer_done -= runtime->period_size;
period_elapsed = 1;
if (subs->fmt_type == USB_FORMAT_TYPE_II) {
if (subs->transfer_sched > 0) {
/* FIXME: fill-max mode is not supported yet */
offs -= subs->transfer_sched;
counts -= subs->transfer_sched;
urb->iso_frame_desc[i].length = counts * stride;
subs->transfer_sched = 0;
if (subs->transfer_done > 0) {
/* FIXME: fill-max mode is not
* supported yet */
offs -= subs->transfer_done;
counts -= subs->transfer_done;
urb->iso_frame_desc[i].length =
counts * stride;
subs->transfer_done = 0;
}
i++;
if (i < ctx->packets) {
/* add a transfer delimiter */
urb->iso_frame_desc[i].offset = offs * stride;
urb->iso_frame_desc[i].offset =
offs * stride;
urb->iso_frame_desc[i].length = 0;
urb->number_of_packets++;
}
}
break;
}
}
if (subs->hwptr + offs > runtime->buffer_size) {
/* err, the transferred area goes over buffer boundary.
* copy the data to the temp buffer.
*/
int len;
len = runtime->buffer_size - subs->hwptr;
urb->transfer_buffer = subs->tmpbuf;
memcpy(subs->tmpbuf, runtime->dma_area + subs->hwptr * stride, len * stride);
memcpy(subs->tmpbuf + len * stride, runtime->dma_area, (offs - len) * stride);
subs->hwptr += offs;
subs->hwptr -= runtime->buffer_size;
if (subs->hwptr_done + offs > runtime->buffer_size) {
/* err, the transferred area goes over buffer boundary. */
unsigned int len = runtime->buffer_size - subs->hwptr_done;
memcpy(urb->transfer_buffer,
runtime->dma_area + subs->hwptr_done * stride,
len * stride);
memcpy(urb->transfer_buffer + len * stride,
runtime->dma_area,
(offs - len) * stride);
} else {
/* set the buffer pointer */
urb->transfer_buffer = runtime->dma_area + subs->hwptr * stride;
subs->hwptr += offs;
if (subs->hwptr == runtime->buffer_size)
subs->hwptr = 0;
memcpy(urb->transfer_buffer,
runtime->dma_area + subs->hwptr_done * stride,
offs * stride);
}
subs->hwptr_done += offs;
if (subs->hwptr_done >= runtime->buffer_size)
subs->hwptr_done -= runtime->buffer_size;
spin_unlock_irqrestore(&subs->lock, flags);
urb->transfer_buffer_length = offs * stride;
ctx->transfer = offs;

if (period_elapsed) {
if (likely(subs->running))
snd_pcm_period_elapsed(subs->pcm_substream);
else
tasklet_hi_schedule(&subs->start_period_elapsed);
}
return 0;
}

/*
* process after playback data complete
*
* update the current position and call callback if a period is processed.
* - nothing to do
*/
static int retire_playback_urb(snd_usb_substream_t *subs,
snd_pcm_runtime_t *runtime,
struct urb *urb)
{
unsigned long flags;
snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;

spin_lock_irqsave(&subs->lock, flags);
subs->transfer_done += ctx->transfer;
subs->hwptr_done += ctx->transfer;
ctx->transfer = 0;
if (subs->hwptr_done >= runtime->buffer_size)
subs->hwptr_done -= runtime->buffer_size;
if (subs->transfer_done >= runtime->period_size) {
subs->transfer_done -= runtime->period_size;
spin_unlock_irqrestore(&subs->lock, flags);
snd_pcm_period_elapsed(subs->pcm_substream);
} else
spin_unlock_irqrestore(&subs->lock, flags);
return 0;
}

/*
* Delay the snd_pcm_period_elapsed() call until after the start trigger
* callback so that we're not longer in the substream's lock.
*/
static void start_period_elapsed(unsigned long data)
{
snd_usb_substream_t *subs = (snd_usb_substream_t *)data;
snd_pcm_period_elapsed(subs->pcm_substream);
}


/*
*/
Expand Down Expand Up @@ -848,11 +844,10 @@ static int snd_usb_pcm_trigger(snd_pcm_substream_t *substream, int cmd)
static void release_urb_ctx(snd_urb_ctx_t *u)
{
if (u->urb) {
kfree(u->urb->transfer_buffer);
usb_free_urb(u->urb);
u->urb = NULL;
}
kfree(u->buf);
u->buf = NULL;
}

/*
Expand All @@ -870,8 +865,6 @@ static void release_substream_urbs(snd_usb_substream_t *subs, int force)
release_urb_ctx(&subs->dataurb[i]);
for (i = 0; i < SYNC_URBS; i++)
release_urb_ctx(&subs->syncurb[i]);
kfree(subs->tmpbuf);
subs->tmpbuf = NULL;
subs->nurbs = 0;
}

Expand Down Expand Up @@ -923,24 +916,15 @@ static int init_substream_urbs(snd_usb_substream_t *subs, unsigned int period_by
urb_packs = 1;
urb_packs *= packs_per_ms;

/* allocate a temporary buffer for playback */
if (is_playback) {
subs->tmpbuf = kmalloc(maxsize * urb_packs, GFP_KERNEL);
if (! subs->tmpbuf) {
snd_printk(KERN_ERR "cannot malloc tmpbuf\n");
return -ENOMEM;
}
}

/* decide how many packets to be used */
if (is_playback) {
unsigned int minsize;
/* determine how small a packet can be */
minsize = (subs->freqn >> (16 - subs->datainterval))
* (frame_bits >> 3);
/* with sync from device, assume it can be 25% lower */
/* with sync from device, assume it can be 12% lower */
if (subs->syncpipe)
minsize -= minsize >> 2;
minsize -= minsize >> 3;
minsize = max(minsize, 1u);
total_packs = (period_bytes + minsize - 1) / minsize;
/* round up to multiple of packs_per_ms */
Expand Down Expand Up @@ -989,27 +973,22 @@ static int init_substream_urbs(snd_usb_substream_t *subs, unsigned int period_by
snd_urb_ctx_t *u = &subs->dataurb[i];
u->index = i;
u->subs = subs;
u->transfer = 0;
u->packets = npacks[i];
if (subs->fmt_type == USB_FORMAT_TYPE_II)
u->packets++; /* for transfer delimiter */
if (! is_playback) {
/* allocate a capture buffer per urb */
u->buf = kmalloc(maxsize * u->packets, GFP_KERNEL);
if (! u->buf) {
release_substream_urbs(subs, 0);
return -ENOMEM;
}
}
u->urb = usb_alloc_urb(u->packets, GFP_KERNEL);
if (! u->urb) {
release_substream_urbs(subs, 0);
return -ENOMEM;
}
u->urb->dev = subs->dev;
u->urb->transfer_buffer = kmalloc(maxsize * u->packets,
GFP_KERNEL);
if (! u->urb->transfer_buffer) {
release_substream_urbs(subs, 0);
return -ENOMEM;
}
u->urb->pipe = subs->datapipe;
u->urb->transfer_flags = URB_ISO_ASAP;
u->urb->number_of_packets = u->packets;
u->urb->interval = 1 << subs->datainterval;
u->urb->context = u;
u->urb->complete = snd_usb_complete_callback(snd_complete_urb);
Expand All @@ -1029,7 +1008,6 @@ static int init_substream_urbs(snd_usb_substream_t *subs, unsigned int period_by
}
u->urb->transfer_buffer = subs->syncbuf + i * 4;
u->urb->transfer_buffer_length = 4;
u->urb->dev = subs->dev;
u->urb->pipe = subs->syncpipe;
u->urb->transfer_flags = URB_ISO_ASAP;
u->urb->number_of_packets = 1;
Expand Down Expand Up @@ -1386,9 +1364,7 @@ static int snd_usb_pcm_prepare(snd_pcm_substream_t *substream)
subs->curframesize = bytes_to_frames(runtime, subs->curpacksize);

/* reset the pointer */
subs->hwptr = 0;
subs->hwptr_done = 0;
subs->transfer_sched = 0;
subs->transfer_done = 0;
subs->phase = 0;

Expand Down Expand Up @@ -2035,6 +2011,9 @@ static void init_substream(snd_usb_stream_t *as, int stream, struct audioformat

INIT_LIST_HEAD(&subs->fmt_list);
spin_lock_init(&subs->lock);
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
tasklet_init(&subs->start_period_elapsed, start_period_elapsed,
(unsigned long)subs);

subs->stream = as;
subs->direction = stream;
Expand Down

0 comments on commit 7efd8bc

Please sign in to comment.