usb: gadget: f_uac2: send reasonably sized packets

The UAC2 function driver currently responds to all packets at all times
with wMaxPacketSize packets. That results in way too fast audio
playback as the function driver (which is in fact supposed to define
the audio stream pace) delivers as fast as it can.

Fix this by sizing each packet correctly with the following steps:

 a) Set the packet's size by dividing the nominal data rate by the
    playback endpoint's interval.

 b) If there is a residual value from the calculation in a), add
    it to a accumulator to keep track of it across packets.

 c) If the accumulator has gathered at least the number of bytes
    that are needed for one sample frame, increase the packet size.

This way, the packet size calculation will get rid of any kind of
imprecision that would otherwise occur with a simple division over
time.

Some of the variables that are needed while processing each packet
are pre-computed for performance reasons.

Signed-off-by: Daniel Mack <zonque@gmail.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
This commit is contained in:
Daniel Mack 2014-08-27 19:09:07 +02:00 committed by Felipe Balbi
parent ec9e43138f
commit 9bb87f1689

View File

@ -92,6 +92,15 @@ struct snd_uac2_chip {
struct snd_card *card; struct snd_card *card;
struct snd_pcm *pcm; struct snd_pcm *pcm;
/* timekeeping for the playback endpoint */
unsigned int p_interval;
unsigned int p_residue;
/* pre-calculated values for playback iso completion */
unsigned int p_pktsize;
unsigned int p_pktsize_residue;
unsigned int p_framesize;
}; };
#define BUFF_SIZE_MAX (PAGE_SIZE * 16) #define BUFF_SIZE_MAX (PAGE_SIZE * 16)
@ -191,8 +200,29 @@ agdev_iso_complete(struct usb_ep *ep, struct usb_request *req)
spin_lock_irqsave(&prm->lock, flags); spin_lock_irqsave(&prm->lock, flags);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
/*
* For each IN packet, take the quotient of the current data
* rate and the endpoint's interval as the base packet size.
* If there is a residue from this division, add it to the
* residue accumulator.
*/
req->length = uac2->p_pktsize;
uac2->p_residue += uac2->p_pktsize_residue;
/*
* Whenever there are more bytes in the accumulator than we
* need to add one more sample frame, increase this packet's
* size and decrease the accumulator.
*/
if (uac2->p_residue / uac2->p_interval >= uac2->p_framesize) {
req->length += uac2->p_framesize;
uac2->p_residue -= uac2->p_framesize *
uac2->p_interval;
}
req->actual = req->length; req->actual = req->length;
}
pending = prm->hw_ptr % prm->period_size; pending = prm->hw_ptr % prm->period_size;
pending += req->actual; pending += req->actual;
@ -346,6 +376,7 @@ static int uac2_pcm_open(struct snd_pcm_substream *substream)
c_srate = opts->c_srate; c_srate = opts->c_srate;
p_chmask = opts->p_chmask; p_chmask = opts->p_chmask;
c_chmask = opts->c_chmask; c_chmask = opts->c_chmask;
uac2->p_residue = 0;
runtime->hw = uac2_pcm_hardware; runtime->hw = uac2_pcm_hardware;
@ -1077,7 +1108,7 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
struct usb_request *req; struct usb_request *req;
struct usb_ep *ep; struct usb_ep *ep;
struct uac2_rtd_params *prm; struct uac2_rtd_params *prm;
int i; int req_len, i;
/* No i/f has more than 2 alt settings */ /* No i/f has more than 2 alt settings */
if (alt > 1) { if (alt > 1) {
@ -1099,11 +1130,41 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
prm = &uac2->c_prm; prm = &uac2->c_prm;
config_ep_by_speed(gadget, fn, ep); config_ep_by_speed(gadget, fn, ep);
agdev->as_out_alt = alt; agdev->as_out_alt = alt;
req_len = prm->max_psize;
} else if (intf == agdev->as_in_intf) { } else if (intf == agdev->as_in_intf) {
struct f_uac2_opts *opts = agdev_to_uac2_opts(agdev);
unsigned int factor, rate;
struct usb_endpoint_descriptor *ep_desc;
ep = agdev->in_ep; ep = agdev->in_ep;
prm = &uac2->p_prm; prm = &uac2->p_prm;
config_ep_by_speed(gadget, fn, ep); config_ep_by_speed(gadget, fn, ep);
agdev->as_in_alt = alt; agdev->as_in_alt = alt;
/* pre-calculate the playback endpoint's interval */
if (gadget->speed == USB_SPEED_FULL) {
ep_desc = &fs_epin_desc;
factor = 1000;
} else {
ep_desc = &hs_epin_desc;
factor = 125;
}
/* pre-compute some values for iso_complete() */
uac2->p_framesize = opts->p_ssize *
num_channels(opts->p_chmask);
rate = opts->p_srate * uac2->p_framesize;
uac2->p_interval = (1 << (ep_desc->bInterval - 1)) * factor;
uac2->p_pktsize = min_t(unsigned int, rate / uac2->p_interval,
prm->max_psize);
if (uac2->p_pktsize < prm->max_psize)
uac2->p_pktsize_residue = rate % uac2->p_interval;
else
uac2->p_pktsize_residue = 0;
req_len = uac2->p_pktsize;
uac2->p_residue = 0;
} else { } else {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
return -EINVAL; return -EINVAL;
@ -1128,9 +1189,9 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
req->zero = 0; req->zero = 0;
req->context = &prm->ureq[i]; req->context = &prm->ureq[i];
req->length = prm->max_psize; req->length = req_len;
req->complete = agdev_iso_complete; req->complete = agdev_iso_complete;
req->buf = prm->rbuf + i * req->length; req->buf = prm->rbuf + i * prm->max_psize;
} }
if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC)) if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))