diff options
author | Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> | 2012-10-22 17:42:15 -0400 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2012-10-23 10:13:48 -0400 |
commit | 4eeaaeaea1cec60a25979678182720dc91308550 (patch) | |
tree | ef6895d3ff86454cc6348e2efde2313eceb24471 /sound/core | |
parent | 0e8014d772a7639f48d234b23dc4ce97335cce7f (diff) |
ALSA: core: add hooks for audio timestamps
ALSA did not provide any direct means to infer the audio time for A/V
sync and system/audio time correlations (eg. PulseAudio).
Applications had to track the number of samples read/written and
add/subtract the number of samples queued in the ring buffer. This
accounting led to small errors, typically several samples, due to the
two-step process. Computing the audio time in the kernel is more
direct, as all the information is available in the same routines.
Also add new .audio_wallclock routine to enable fine-grain synchronization
between monotonic system time and audio hardware time.
Using the wallclock, if supported in hardware, allows for a
much better sub-microsecond precision and a common drift tracking for
all devices sharing the same wall clock (master clock).
Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/core')
-rw-r--r-- | sound/core/pcm_compat.c | 19 | ||||
-rw-r--r-- | sound/core/pcm_lib.c | 32 | ||||
-rw-r--r-- | sound/core/pcm_native.c | 2 |
3 files changed, 43 insertions, 10 deletions
diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c index 91cdf9435fec..af2a3fdb8828 100644 --- a/sound/core/pcm_compat.c +++ b/sound/core/pcm_compat.c | |||
@@ -190,7 +190,8 @@ struct snd_pcm_status32 { | |||
190 | u32 avail_max; | 190 | u32 avail_max; |
191 | u32 overrange; | 191 | u32 overrange; |
192 | s32 suspended_state; | 192 | s32 suspended_state; |
193 | unsigned char reserved[60]; | 193 | struct compat_timespec audio_tstamp; |
194 | unsigned char reserved[60-sizeof(struct compat_timespec)]; | ||
194 | } __attribute__((packed)); | 195 | } __attribute__((packed)); |
195 | 196 | ||
196 | 197 | ||
@@ -205,17 +206,16 @@ static int snd_pcm_status_user_compat(struct snd_pcm_substream *substream, | |||
205 | return err; | 206 | return err; |
206 | 207 | ||
207 | if (put_user(status.state, &src->state) || | 208 | if (put_user(status.state, &src->state) || |
208 | put_user(status.trigger_tstamp.tv_sec, &src->trigger_tstamp.tv_sec) || | 209 | compat_put_timespec(&status.trigger_tstamp, &src->trigger_tstamp) || |
209 | put_user(status.trigger_tstamp.tv_nsec, &src->trigger_tstamp.tv_nsec) || | 210 | compat_put_timespec(&status.tstamp, &src->tstamp) || |
210 | put_user(status.tstamp.tv_sec, &src->tstamp.tv_sec) || | ||
211 | put_user(status.tstamp.tv_nsec, &src->tstamp.tv_nsec) || | ||
212 | put_user(status.appl_ptr, &src->appl_ptr) || | 211 | put_user(status.appl_ptr, &src->appl_ptr) || |
213 | put_user(status.hw_ptr, &src->hw_ptr) || | 212 | put_user(status.hw_ptr, &src->hw_ptr) || |
214 | put_user(status.delay, &src->delay) || | 213 | put_user(status.delay, &src->delay) || |
215 | put_user(status.avail, &src->avail) || | 214 | put_user(status.avail, &src->avail) || |
216 | put_user(status.avail_max, &src->avail_max) || | 215 | put_user(status.avail_max, &src->avail_max) || |
217 | put_user(status.overrange, &src->overrange) || | 216 | put_user(status.overrange, &src->overrange) || |
218 | put_user(status.suspended_state, &src->suspended_state)) | 217 | put_user(status.suspended_state, &src->suspended_state) || |
218 | compat_put_timespec(&status.audio_tstamp, &src->audio_tstamp)) | ||
219 | return -EFAULT; | 219 | return -EFAULT; |
220 | 220 | ||
221 | return err; | 221 | return err; |
@@ -364,6 +364,7 @@ struct snd_pcm_mmap_status32 { | |||
364 | u32 hw_ptr; | 364 | u32 hw_ptr; |
365 | struct compat_timespec tstamp; | 365 | struct compat_timespec tstamp; |
366 | s32 suspended_state; | 366 | s32 suspended_state; |
367 | struct compat_timespec audio_tstamp; | ||
367 | } __attribute__((packed)); | 368 | } __attribute__((packed)); |
368 | 369 | ||
369 | struct snd_pcm_mmap_control32 { | 370 | struct snd_pcm_mmap_control32 { |
@@ -426,12 +427,14 @@ static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream, | |||
426 | sstatus.hw_ptr = status->hw_ptr % boundary; | 427 | sstatus.hw_ptr = status->hw_ptr % boundary; |
427 | sstatus.tstamp = status->tstamp; | 428 | sstatus.tstamp = status->tstamp; |
428 | sstatus.suspended_state = status->suspended_state; | 429 | sstatus.suspended_state = status->suspended_state; |
430 | sstatus.audio_tstamp = status->audio_tstamp; | ||
429 | snd_pcm_stream_unlock_irq(substream); | 431 | snd_pcm_stream_unlock_irq(substream); |
430 | if (put_user(sstatus.state, &src->s.status.state) || | 432 | if (put_user(sstatus.state, &src->s.status.state) || |
431 | put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) || | 433 | put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) || |
432 | put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp.tv_sec) || | 434 | compat_put_timespec(&sstatus.tstamp, &src->s.status.tstamp) || |
433 | put_user(sstatus.tstamp.tv_nsec, &src->s.status.tstamp.tv_nsec) || | ||
434 | put_user(sstatus.suspended_state, &src->s.status.suspended_state) || | 435 | put_user(sstatus.suspended_state, &src->s.status.suspended_state) || |
436 | compat_put_timespec(&sstatus.audio_tstamp, | ||
437 | &src->s.status.audio_tstamp) || | ||
435 | put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) || | 438 | put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) || |
436 | put_user(scontrol.avail_min, &src->c.control.avail_min)) | 439 | put_user(scontrol.avail_min, &src->c.control.avail_min)) |
437 | return -EFAULT; | 440 | return -EFAULT; |
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 3dc029e106a2..c4840ff75d00 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c | |||
@@ -316,6 +316,7 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, | |||
316 | unsigned long jdelta; | 316 | unsigned long jdelta; |
317 | unsigned long curr_jiffies; | 317 | unsigned long curr_jiffies; |
318 | struct timespec curr_tstamp; | 318 | struct timespec curr_tstamp; |
319 | struct timespec audio_tstamp; | ||
319 | int crossed_boundary = 0; | 320 | int crossed_boundary = 0; |
320 | 321 | ||
321 | old_hw_ptr = runtime->status->hw_ptr; | 322 | old_hw_ptr = runtime->status->hw_ptr; |
@@ -328,9 +329,14 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, | |||
328 | */ | 329 | */ |
329 | pos = substream->ops->pointer(substream); | 330 | pos = substream->ops->pointer(substream); |
330 | curr_jiffies = jiffies; | 331 | curr_jiffies = jiffies; |
331 | if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) | 332 | if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) { |
332 | snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp); | 333 | snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp); |
333 | 334 | ||
335 | if ((runtime->hw.info & SNDRV_PCM_INFO_HAS_WALL_CLOCK) && | ||
336 | (substream->ops->wall_clock)) | ||
337 | substream->ops->wall_clock(substream, &audio_tstamp); | ||
338 | } | ||
339 | |||
334 | if (pos == SNDRV_PCM_POS_XRUN) { | 340 | if (pos == SNDRV_PCM_POS_XRUN) { |
335 | xrun(substream); | 341 | xrun(substream); |
336 | return -EPIPE; | 342 | return -EPIPE; |
@@ -520,9 +526,31 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, | |||
520 | snd_BUG_ON(crossed_boundary != 1); | 526 | snd_BUG_ON(crossed_boundary != 1); |
521 | runtime->hw_ptr_wrap += runtime->boundary; | 527 | runtime->hw_ptr_wrap += runtime->boundary; |
522 | } | 528 | } |
523 | if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) | 529 | if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) { |
524 | runtime->status->tstamp = curr_tstamp; | 530 | runtime->status->tstamp = curr_tstamp; |
525 | 531 | ||
532 | if (!(runtime->hw.info & SNDRV_PCM_INFO_HAS_WALL_CLOCK)) { | ||
533 | /* | ||
534 | * no wall clock available, provide audio timestamp | ||
535 | * derived from pointer position+delay | ||
536 | */ | ||
537 | u64 audio_frames, audio_nsecs; | ||
538 | |||
539 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
540 | audio_frames = runtime->hw_ptr_wrap | ||
541 | + runtime->status->hw_ptr | ||
542 | - runtime->delay; | ||
543 | else | ||
544 | audio_frames = runtime->hw_ptr_wrap | ||
545 | + runtime->status->hw_ptr | ||
546 | + runtime->delay; | ||
547 | audio_nsecs = div_u64(audio_frames * 1000000000LL, | ||
548 | runtime->rate); | ||
549 | audio_tstamp = ns_to_timespec(audio_nsecs); | ||
550 | } | ||
551 | runtime->status->audio_tstamp = audio_tstamp; | ||
552 | } | ||
553 | |||
526 | return snd_pcm_update_state(substream, runtime); | 554 | return snd_pcm_update_state(substream, runtime); |
527 | } | 555 | } |
528 | 556 | ||
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 5e12e5bacbba..7c800012fff9 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c | |||
@@ -594,6 +594,8 @@ int snd_pcm_status(struct snd_pcm_substream *substream, | |||
594 | snd_pcm_update_hw_ptr(substream); | 594 | snd_pcm_update_hw_ptr(substream); |
595 | if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) { | 595 | if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) { |
596 | status->tstamp = runtime->status->tstamp; | 596 | status->tstamp = runtime->status->tstamp; |
597 | status->audio_tstamp = | ||
598 | runtime->status->audio_tstamp; | ||
597 | goto _tstamp_end; | 599 | goto _tstamp_end; |
598 | } | 600 | } |
599 | } | 601 | } |