[PATCH 4/4] usb: gadget: f_uac2: tie capture clocking to system clock

[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.

Consider the following setup: the host uses arecord to record the
audio, and the gadget uses aplay to feed the stream. If both sides
use files as sink and source, everything is fine, because the data
is correctly transferred. However, in my tests, a one-minute wave
file took around 30 seconds to transfer that way. If one of the
sides expects data to be delivered at the nominal data rate though,
everything breaks apart.

Fix this by introducing some state variables to keep track of the
audio stream timing. Send 0-byte packets when the hosts ask for more
data than the local timing allows us to send.

Successfully tested on a BBB for the gadget side.

Signed-off-by: Daniel Mack <zonque@xxxxxxxxx>
---
 drivers/usb/gadget/function/f_uac2.c | 37 +++++++++++++++++++++++++++++++++++-
 1 file changed, 36 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index 1d6d085..176898d 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -15,6 +15,7 @@
 #include <linux/usb/audio-v2.h>
 #include <linux/platform_device.h>
 #include <linux/module.h>
+#include <linux/time.h>
 
 #include <sound/core.h>
 #include <sound/pcm.h>
@@ -90,6 +91,11 @@ struct snd_uac2_chip {
 	struct uac2_rtd_params p_prm;
 	struct uac2_rtd_params c_prm;
 
+	/* clocking state for capture */
+	unsigned int c_nsecs_per_byte;
+	struct timespec c_timestamp;
+	unsigned int c_residue;
+
 	struct snd_card *card;
 	struct snd_pcm *pcm;
 };
@@ -186,9 +192,26 @@ agdev_iso_complete(struct usb_ep *ep, struct usb_request *req)
 	spin_lock_irqsave(&prm->lock, flags);
 
 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		unsigned int nsecs;
+		struct timespec now;
+
+		now = current_kernel_time();
+		nsecs = (now.tv_sec - uac2->c_timestamp.tv_sec) * NSEC_PER_SEC
+		      + (now.tv_nsec - uac2->c_timestamp.tv_nsec);
+		uac2->c_residue += nsecs / uac2->c_nsecs_per_byte;
+
 		src = prm->dma_area + prm->hw_ptr;
-		req->actual = req->length;
 		dst = req->buf;
+
+		if (uac2->c_residue >= prm->max_psize) {
+			req->length = prm->max_psize;
+			uac2->c_residue -= prm->max_psize;
+		} else {
+			req->length = 0;
+		}
+
+		req->actual = req->length;
+		memcpy(&uac2->c_timestamp, &now, sizeof(now));
 	} else {
 		dst = prm->dma_area + prm->hw_ptr;
 		src = req->buf;
@@ -237,6 +260,8 @@ uac2_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 	case SNDRV_PCM_TRIGGER_START:
 	case SNDRV_PCM_TRIGGER_RESUME:
 		prm->ss = substream;
+		uac2->c_timestamp = current_kernel_time();
+		uac2->c_residue = 0;
 		break;
 	case SNDRV_PCM_TRIGGER_STOP:
 	case SNDRV_PCM_TRIGGER_SUSPEND:
@@ -1054,6 +1079,7 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
 	struct snd_uac2_chip *uac2 = &agdev->uac2;
 	struct usb_gadget *gadget = cdev->gadget;
 	struct device *dev = &uac2->pdev.dev;
+	struct f_uac2_opts *opts;
 	struct usb_request *req;
 	struct usb_ep *ep;
 	struct uac2_rtd_params *prm;
@@ -1074,11 +1100,17 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
 		return 0;
 	}
 
+	opts = container_of(agdev->func.fi, struct f_uac2_opts, func_inst);
+
 	if (intf == agdev->as_out_intf) {
 		ep = agdev->out_ep;
 		prm = &uac2->c_prm;
 		config_ep_by_speed(gadget, fn, ep);
 		agdev->as_out_alt = alt;
+
+		uac2->c_nsecs_per_byte =
+			NSEC_PER_SEC / (opts->c_srate * opts->c_ssize *
+					num_channels(opts->c_chmask));
 	} else if (intf == agdev->as_in_intf) {
 		ep = agdev->in_ep;
 		prm = &uac2->p_prm;
@@ -1117,6 +1149,9 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
 			dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
 	}
 
+	uac2->c_timestamp = current_kernel_time();
+	uac2->c_residue = 0;
+
 	return 0;
 }
 
-- 
2.1.0

--
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