Skip to content

Commit

Permalink
ALSA: usb-audio: automatically detect feedback format
Browse files Browse the repository at this point in the history
There are two USB Audio Class specifications (v1 and v2), but neither of
them clearly defines the feedback format for high-speed UAC v1 devices.
Add to this whatever the Creative and M-Audio firmware writers have been
smoking, and it becomes impossible to predict the exact feedback format
used by a particular device.

Therefore, automatically detect the feedback format by looking at the
magnitude of the first received feedback value.

Also, this allows us to get rid of some special cases for E-Mu devices.

Signed-off-by: Clemens Ladisch <[email protected]>
Signed-off-by: Takashi Iwai <[email protected]>
  • Loading branch information
cladisch authored and tiwai committed Oct 27, 2010
1 parent 0d040df commit 89e1e66
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 112 deletions.
2 changes: 2 additions & 0 deletions sound/usb/card.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,14 @@ struct snd_usb_substream {
unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */
unsigned int freqn; /* nominal sampling rate in fs/fps in Q16.16 format */
unsigned int freqm; /* momentary sampling rate in fs/fps in Q16.16 format */
int freqshift; /* how much to shift the feedback value to get Q16.16 */
unsigned int freqmax; /* maximum sampling rate, used for buffer management */
unsigned int phase; /* phase accumulator */
unsigned int maxpacksize; /* max packet size in bytes */
unsigned int maxframesize; /* max packet size in frames */
unsigned int curpacksize; /* current packet size in bytes (for capture) */
unsigned int curframesize; /* current packet size in frames (for capture) */
unsigned int syncmaxsize; /* sync endpoint packet size */
unsigned int fill_max: 1; /* fill max packet size always */
unsigned int txfr_quirk:1; /* allow sub-frame alignment */
unsigned int fmt_type; /* USB audio format type (1-3) */
Expand Down
2 changes: 2 additions & 0 deletions sound/usb/pcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt)
subs->datainterval = fmt->datainterval;
subs->syncpipe = subs->syncinterval = 0;
subs->maxpacksize = fmt->maxpacksize;
subs->syncmaxsize = 0;
subs->fill_max = 0;

/* we need a sync pipe in async OUT or adaptive IN mode */
Expand Down Expand Up @@ -283,6 +284,7 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt)
subs->syncinterval = get_endpoint(alts, 1)->bInterval - 1;
else
subs->syncinterval = 3;
subs->syncmaxsize = le16_to_cpu(get_endpoint(alts, 1)->wMaxPacketSize);
}

/* always fill max packet size */
Expand Down
5 changes: 5 additions & 0 deletions sound/usb/proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ static void proc_dump_substream_status(struct snd_usb_substream *subs, struct sn
? get_full_speed_hz(subs->freqm)
: get_high_speed_hz(subs->freqm),
subs->freqm >> 16, subs->freqm & 0xffff);
if (subs->freqshift != INT_MIN)
snd_iprintf(buffer, " Feedback Format = %d.%d\n",
(subs->syncmaxsize > 3 ? 32 : 24)
- (16 - subs->freqshift),
16 - subs->freqshift);
} else {
snd_iprintf(buffer, " Status: Stop\n");
}
Expand Down
170 changes: 58 additions & 112 deletions sound/usb/urb.c
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ int snd_usb_init_substream_urbs(struct snd_usb_substream *subs,
else
subs->freqn = get_usb_high_speed_rate(rate);
subs->freqm = subs->freqn;
subs->freqshift = INT_MIN;
/* calculate max. frequency */
if (subs->maxpacksize) {
/* whatever fits into a max. size packet */
Expand Down Expand Up @@ -513,115 +514,89 @@ static int retire_paused_capture_urb(struct snd_usb_substream *subs,


/*
* prepare urb for full speed playback sync pipe
* prepare urb for playback sync pipe
*
* set up the offset and length to receive the current frequency.
*/

static int prepare_playback_sync_urb(struct snd_usb_substream *subs,
struct snd_pcm_runtime *runtime,
struct urb *urb)
{
struct snd_urb_ctx *ctx = urb->context;

urb->dev = ctx->subs->dev; /* we need to set this at each time */
urb->iso_frame_desc[0].length = 3;
urb->iso_frame_desc[0].length = min(4u, ctx->subs->syncmaxsize);
urb->iso_frame_desc[0].offset = 0;
return 0;
}

/*
* prepare urb for high speed playback sync pipe
* process after playback sync complete
*
* set up the offset and length to receive the current frequency.
*/

static int prepare_playback_sync_urb_hs(struct snd_usb_substream *subs,
struct snd_pcm_runtime *runtime,
struct urb *urb)
{
struct snd_urb_ctx *ctx = urb->context;

urb->dev = ctx->subs->dev; /* we need to set this at each time */
urb->iso_frame_desc[0].length = 4;
urb->iso_frame_desc[0].offset = 0;
return 0;
}

/*
* process after full speed playback sync complete
*
* retrieve the current 10.14 frequency from pipe, and set it.
* the value is referred in prepare_playback_urb().
* Full speed devices report feedback values in 10.14 format as samples per
* frame, high speed devices in 16.16 format as samples per microframe.
* Because the Audio Class 1 spec was written before USB 2.0, many high speed
* devices use a wrong interpretation, some others use an entirely different
* format. Therefore, we cannot predict what format any particular device uses
* and must detect it automatically.
*/
static int retire_playback_sync_urb(struct snd_usb_substream *subs,
struct snd_pcm_runtime *runtime,
struct urb *urb)
{
unsigned int f;
int shift;
unsigned long flags;

if (urb->iso_frame_desc[0].status == 0 &&
urb->iso_frame_desc[0].actual_length == 3) {
f = combine_triple((u8*)urb->transfer_buffer) << 2;
if (f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax) {
spin_lock_irqsave(&subs->lock, flags);
subs->freqm = f;
spin_unlock_irqrestore(&subs->lock, flags);
}
}

return 0;
}
if (urb->iso_frame_desc[0].status != 0 ||
urb->iso_frame_desc[0].actual_length < 3)
return 0;

/*
* process after high speed playback sync complete
*
* retrieve the current 12.13 frequency from pipe, and set it.
* the value is referred in prepare_playback_urb().
*/
static int retire_playback_sync_urb_hs(struct snd_usb_substream *subs,
struct snd_pcm_runtime *runtime,
struct urb *urb)
{
unsigned int f;
unsigned long flags;
f = le32_to_cpup(urb->transfer_buffer);
if (urb->iso_frame_desc[0].actual_length == 3)
f &= 0x00ffffff;
else
f &= 0x0fffffff;
if (f == 0)
return 0;

if (urb->iso_frame_desc[0].status == 0 &&
urb->iso_frame_desc[0].actual_length == 4) {
f = combine_quad((u8*)urb->transfer_buffer) & 0x0fffffff;
if (f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax) {
spin_lock_irqsave(&subs->lock, flags);
subs->freqm = f;
spin_unlock_irqrestore(&subs->lock, flags);
if (unlikely(subs->freqshift == INT_MIN)) {
/*
* The first time we see a feedback value, determine its format
* by shifting it left or right until it matches the nominal
* frequency value. This assumes that the feedback does not
* differ from the nominal value more than +50% or -25%.
*/
shift = 0;
while (f < subs->freqn - subs->freqn / 4) {
f <<= 1;
shift++;
}
while (f > subs->freqn + subs->freqn / 2) {
f >>= 1;
shift--;
}
subs->freqshift = shift;
}
else if (subs->freqshift >= 0)
f <<= subs->freqshift;
else
f >>= -subs->freqshift;

return 0;
}

/*
* process after E-Mu 0202/0404/Tracker Pre high speed playback sync complete
*
* These devices return the number of samples per packet instead of the number
* of samples per microframe.
*/
static int retire_playback_sync_urb_hs_emu(struct snd_usb_substream *subs,
struct snd_pcm_runtime *runtime,
struct urb *urb)
{
unsigned int f;
unsigned long flags;

if (urb->iso_frame_desc[0].status == 0 &&
urb->iso_frame_desc[0].actual_length == 4) {
f = combine_quad((u8*)urb->transfer_buffer) & 0x0fffffff;
f >>= subs->datainterval;
if (f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax) {
spin_lock_irqsave(&subs->lock, flags);
subs->freqm = f;
spin_unlock_irqrestore(&subs->lock, flags);
}
if (likely(f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax)) {
/*
* If the frequency looks valid, set it.
* This value is referred to in prepare_playback_urb().
*/
spin_lock_irqsave(&subs->lock, flags);
subs->freqm = f;
spin_unlock_irqrestore(&subs->lock, flags);
} else {
/*
* Out of range; maybe the shift value is wrong.
* Reset it so that we autodetect again the next time.
*/
subs->freqshift = INT_MIN;
}

return 0;
Expand Down Expand Up @@ -878,21 +853,6 @@ static struct snd_urb_ops audio_urb_ops[2] = {
},
};

static struct snd_urb_ops audio_urb_ops_high_speed[2] = {
{
.prepare = prepare_nodata_playback_urb,
.retire = retire_playback_urb,
.prepare_sync = prepare_playback_sync_urb_hs,
.retire_sync = retire_playback_sync_urb_hs,
},
{
.prepare = prepare_capture_urb,
.retire = retire_capture_urb,
.prepare_sync = prepare_capture_sync_urb_hs,
.retire_sync = retire_capture_sync_urb,
},
};

/*
* initialize the substream instance.
*/
Expand All @@ -909,23 +869,9 @@ void snd_usb_init_substream(struct snd_usb_stream *as,
subs->direction = stream;
subs->dev = as->chip->dev;
subs->txfr_quirk = as->chip->txfr_quirk;
if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL) {
subs->ops = audio_urb_ops[stream];
} else {
subs->ops = audio_urb_ops_high_speed[stream];
switch (as->chip->usb_id) {
case USB_ID(0x041e, 0x3f02): /* E-Mu 0202 USB */
case USB_ID(0x041e, 0x3f04): /* E-Mu 0404 USB */
case USB_ID(0x041e, 0x3f0a): /* E-Mu Tracker Pre */
subs->ops.retire_sync = retire_playback_sync_urb_hs_emu;
break;
case USB_ID(0x0763, 0x2080): /* M-Audio Fast Track Ultra 8 */
case USB_ID(0x0763, 0x2081): /* M-Audio Fast Track Ultra 8R */
subs->ops.prepare_sync = prepare_playback_sync_urb;
subs->ops.retire_sync = retire_playback_sync_urb;
break;
}
}
subs->ops = audio_urb_ops[stream];
if (snd_usb_get_speed(subs->dev) >= USB_SPEED_HIGH)
subs->ops.prepare_sync = prepare_capture_sync_urb_hs;

snd_usb_set_pcm_ops(as->pcm, stream);

Expand Down

0 comments on commit 89e1e66

Please sign in to comment.