diff options
Diffstat (limited to 'sound/usb/pcm.c')
| -rw-r--r-- | sound/usb/pcm.c | 67 | 
1 files changed, 58 insertions, 9 deletions
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index a1298f37942..f782ce19bf5 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -212,7 +212,7 @@ int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface,  	}  } -static int start_endpoints(struct snd_usb_substream *subs) +static int start_endpoints(struct snd_usb_substream *subs, int can_sleep)  {  	int err; @@ -225,7 +225,7 @@ static int start_endpoints(struct snd_usb_substream *subs)  		snd_printdd(KERN_DEBUG "Starting data EP @%p\n", ep);  		ep->data_subs = subs; -		err = snd_usb_endpoint_start(ep); +		err = snd_usb_endpoint_start(ep, can_sleep);  		if (err < 0) {  			clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags);  			return err; @@ -236,10 +236,25 @@ static int start_endpoints(struct snd_usb_substream *subs)  	    !test_and_set_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags)) {  		struct snd_usb_endpoint *ep = subs->sync_endpoint; +		if (subs->data_endpoint->iface != subs->sync_endpoint->iface || +		    subs->data_endpoint->alt_idx != subs->sync_endpoint->alt_idx) { +			err = usb_set_interface(subs->dev, +						subs->sync_endpoint->iface, +						subs->sync_endpoint->alt_idx); +			if (err < 0) { +				snd_printk(KERN_ERR +					   "%d:%d:%d: cannot set interface (%d)\n", +					   subs->dev->devnum, +					   subs->sync_endpoint->iface, +					   subs->sync_endpoint->alt_idx, err); +				return -EIO; +			} +		} +  		snd_printdd(KERN_DEBUG "Starting sync EP @%p\n", ep);  		ep->sync_slave = subs->data_endpoint; -		err = snd_usb_endpoint_start(ep); +		err = snd_usb_endpoint_start(ep, can_sleep);  		if (err < 0) {  			clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags);  			return err; @@ -547,7 +562,7 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)  	/* for playback, submit the URBs now; otherwise, the first hwptr_done  	 * updates for all URBs would happen at the same time when starting */  	if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) -		return start_endpoints(subs); +		return start_endpoints(subs, 1);  	return 0;  } @@ -1029,6 +1044,7 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,  				 struct urb *urb)  {  	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; +	struct snd_usb_endpoint *ep = subs->data_endpoint;  	struct snd_urb_ctx *ctx = urb->context;  	unsigned int counts, frames, bytes;  	int i, stride, period_elapsed = 0; @@ -1040,7 +1056,11 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,  	urb->number_of_packets = 0;  	spin_lock_irqsave(&subs->lock, flags);  	for (i = 0; i < ctx->packets; i++) { -		counts = ctx->packet_size[i]; +		if (ctx->packet_size[i]) +			counts = ctx->packet_size[i]; +		else +			counts = snd_usb_endpoint_next_packet_size(ep); +  		/* set up descriptor */  		urb->iso_frame_desc[i].offset = frames * stride;  		urb->iso_frame_desc[i].length = counts * stride; @@ -1091,7 +1111,16 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,  	subs->hwptr_done += bytes;  	if (subs->hwptr_done >= runtime->buffer_size * stride)  		subs->hwptr_done -= runtime->buffer_size * stride; + +	/* update delay with exact number of samples queued */ +	runtime->delay = subs->last_delay;  	runtime->delay += frames; +	subs->last_delay = runtime->delay; + +	/* realign last_frame_number */ +	subs->last_frame_number = usb_get_current_frame_number(subs->dev); +	subs->last_frame_number &= 0xFF; /* keep 8 LSBs */ +  	spin_unlock_irqrestore(&subs->lock, flags);  	urb->transfer_buffer_length = bytes;  	if (period_elapsed) @@ -1109,12 +1138,32 @@ static void retire_playback_urb(struct snd_usb_substream *subs,  	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;  	int stride = runtime->frame_bits >> 3;  	int processed = urb->transfer_buffer_length / stride; +	int est_delay; + +	/* ignore the delay accounting when procssed=0 is given, i.e. +	 * silent payloads are procssed before handling the actual data +	 */ +	if (!processed) +		return;  	spin_lock_irqsave(&subs->lock, flags); -	if (processed > runtime->delay) -		runtime->delay = 0; +	est_delay = snd_usb_pcm_delay(subs, runtime->rate); +	/* update delay with exact number of samples played */ +	if (processed > subs->last_delay) +		subs->last_delay = 0;  	else -		runtime->delay -= processed; +		subs->last_delay -= processed; +	runtime->delay = subs->last_delay; + +	/* +	 * Report when delay estimate is off by more than 2ms. +	 * The error should be lower than 2ms since the estimate relies +	 * on two reads of a counter updated every ms. +	 */ +	if (abs(est_delay - subs->last_delay) * 1000 > runtime->rate * 2) +		snd_printk(KERN_DEBUG "delay: estimated %d, actual %d\n", +			est_delay, subs->last_delay); +  	spin_unlock_irqrestore(&subs->lock, flags);  } @@ -1172,7 +1221,7 @@ static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream  	switch (cmd) {  	case SNDRV_PCM_TRIGGER_START: -		err = start_endpoints(subs); +		err = start_endpoints(subs, 0);  		if (err < 0)  			return err;  |