[PATCH] usb: gadget: f_uac2: modulate playback data rate

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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.

We need data rate to match, as accurately as possible, the sampling rate
expressed by the UAC2 topology to the Host.

We do this by sending packets of varying length (1 sample more than usual)
in a pattern so that we get the desired data rate over a period of a
second or sooner. The payload pattern is calculated only once (using
"Alan's Algo"), when the Host enables the interface, and saved in an
array so that the 2 ping-pong usb_requests directly index into the
pattern array to figure out the payload length they are supposed to
transfer next. Note that the increased overhead in agdev_iso_complete()
is almost zero.

Signed-off-by: Jassi Brar <jaswinder.singh@xxxxxxxxxx>
---
 drivers/usb/gadget/function/f_uac2.c | 70 ++++++++++++++++++++++++++++++++++--
 1 file changed, 67 insertions(+), 3 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index 246a778..84fd3b0 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -59,8 +59,15 @@ const char *uac2_name = "snd_uac2";
 struct uac2_req {
 	struct uac2_rtd_params *pp; /* parent param */
 	struct usb_request *req;
+	unsigned idx; /* current element of length-pattern loop */
 };
 
+/*
+ * 5512.5Hz is going to need the maximum number of elements (80),
+ * in the length-pattern loop, among standard ALSA supported rates.
+ */
+#define MAX_LOOP_LEN	80
+
 struct uac2_rtd_params {
 	struct snd_uac2_chip *uac2; /* parent chip */
 	bool ep_enabled; /* if the ep is enabled */
@@ -80,6 +87,9 @@ struct uac2_rtd_params {
 	unsigned max_psize;
 	struct uac2_req ureq[USB_XFERS];
 
+	unsigned pattern[MAX_LOOP_LEN];
+	unsigned plen; /* valid entries in pattern[] */
+
 	spinlock_t lock;
 };
 
@@ -191,8 +201,12 @@ agdev_iso_complete(struct usb_ep *ep, struct usb_request *req)
 
 	spin_lock_irqsave(&prm->lock, flags);
 
-	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		/* Update length for next payload */
+		ur->idx = (ur->idx + USB_XFERS) % prm->plen;
+		req->length = prm->pattern[ur->idx];
 		req->actual = req->length;
+	}
 
 	pending = prm->hw_ptr % prm->period_size;
 	pending += req->actual;
@@ -1066,6 +1080,31 @@ err:
 	return -EINVAL;
 }
 
+/*
+ * Find optimal pattern of payloads for a given number
+ * of samples and maximum sync period (in ms) over which
+ * we have to distribute them uniformly.
+ */
+static unsigned
+get_pattern(unsigned samples, unsigned sync, unsigned *pt)
+{
+	unsigned n, x = 0, i = 0, p = samples % sync;
+
+	do {
+		x += p;
+		n = samples / sync;
+		if (x >= sync) {
+			n += 1;
+			x -= sync;
+		}
+		if (pt)
+			pt[i] = n;
+		i++;
+	} while (x);
+
+	return i;
+}
+
 static int
 afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
 {
@@ -1097,11 +1136,35 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
 	if (intf == agdev->as_out_intf) {
 		ep = agdev->out_ep;
 		prm = &uac2->c_prm;
+		prm->plen = 1;
+		prm->pattern[0] = prm->max_psize;
 		config_ep_by_speed(gadget, fn, ep);
 		agdev->as_out_alt = alt;
 	} else if (intf == agdev->as_in_intf) {
+		struct f_uac2_opts *opts = agdev_to_uac2_opts(agdev);
+		unsigned intvl, rate;
+
+		if (gadget->speed == USB_SPEED_FULL)
+			intvl = (1 << (fs_epin_desc.bInterval - 1)) * 1000;
+		else
+			intvl = (1 << (hs_epin_desc.bInterval - 1)) * 125;
+
+		rate = opts->p_srate;
+		if (rate == 5512) { /* which implies 5512.5 practically */
+			rate = 55125;
+			intvl *= 10;
+		}
+
 		ep = agdev->in_ep;
 		prm = &uac2->p_prm;
+		prm->plen = get_pattern(rate, intvl, NULL); /* dry run */
+		/* We try to support arbitrary rates too */
+		if (prm->plen > MAX_LOOP_LEN) {
+			prm->plen = 1;
+			prm->pattern[0] = rate / intvl;
+		} else {
+			prm->plen = get_pattern(rate, intvl, prm->pattern);
+		}
 		config_ep_by_speed(gadget, fn, ep);
 		agdev->as_in_alt = alt;
 	} else {
@@ -1125,12 +1188,13 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
 
 			prm->ureq[i].req = req;
 			prm->ureq[i].pp = prm;
+			prm->ureq[i].idx = i % prm->plen;
 
 			req->zero = 0;
 			req->context = &prm->ureq[i];
-			req->length = prm->max_psize;
+			req->length = prm->pattern[prm->ureq[i].idx];
 			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))
-- 
1.8.1.2

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux