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 | |
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>
-rw-r--r-- | include/sound/pcm.h | 2 | ||||
-rw-r--r-- | include/uapi/sound/asound.h | 7 | ||||
-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 |
5 files changed, 50 insertions, 12 deletions
diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 28fd9f95f9ba..45c1981c9ca2 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h | |||
@@ -71,6 +71,8 @@ struct snd_pcm_ops { | |||
71 | int (*prepare)(struct snd_pcm_substream *substream); | 71 | int (*prepare)(struct snd_pcm_substream *substream); |
72 | int (*trigger)(struct snd_pcm_substream *substream, int cmd); | 72 | int (*trigger)(struct snd_pcm_substream *substream, int cmd); |
73 | snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream); | 73 | snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream); |
74 | int (*wall_clock)(struct snd_pcm_substream *substream, | ||
75 | struct timespec *audio_ts); | ||
74 | int (*copy)(struct snd_pcm_substream *substream, int channel, | 76 | int (*copy)(struct snd_pcm_substream *substream, int channel, |
75 | snd_pcm_uframes_t pos, | 77 | snd_pcm_uframes_t pos, |
76 | void __user *buf, snd_pcm_uframes_t count); | 78 | void __user *buf, snd_pcm_uframes_t count); |
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 92b104e496b5..85b2e4dde883 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h | |||
@@ -136,7 +136,7 @@ struct snd_hwdep_dsp_image { | |||
136 | * * | 136 | * * |
137 | *****************************************************************************/ | 137 | *****************************************************************************/ |
138 | 138 | ||
139 | #define SNDRV_PCM_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 10) | 139 | #define SNDRV_PCM_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 11) |
140 | 140 | ||
141 | typedef unsigned long snd_pcm_uframes_t; | 141 | typedef unsigned long snd_pcm_uframes_t; |
142 | typedef signed long snd_pcm_sframes_t; | 142 | typedef signed long snd_pcm_sframes_t; |
@@ -258,6 +258,7 @@ typedef int __bitwise snd_pcm_subformat_t; | |||
258 | #define SNDRV_PCM_INFO_JOINT_DUPLEX 0x00200000 /* playback and capture stream are somewhat correlated */ | 258 | #define SNDRV_PCM_INFO_JOINT_DUPLEX 0x00200000 /* playback and capture stream are somewhat correlated */ |
259 | #define SNDRV_PCM_INFO_SYNC_START 0x00400000 /* pcm support some kind of sync go */ | 259 | #define SNDRV_PCM_INFO_SYNC_START 0x00400000 /* pcm support some kind of sync go */ |
260 | #define SNDRV_PCM_INFO_NO_PERIOD_WAKEUP 0x00800000 /* period wakeup can be disabled */ | 260 | #define SNDRV_PCM_INFO_NO_PERIOD_WAKEUP 0x00800000 /* period wakeup can be disabled */ |
261 | #define SNDRV_PCM_INFO_HAS_WALL_CLOCK 0x01000000 /* has audio wall clock for audio/system time sync */ | ||
261 | #define SNDRV_PCM_INFO_FIFO_IN_FRAMES 0x80000000 /* internal kernel flag - FIFO size is in frames */ | 262 | #define SNDRV_PCM_INFO_FIFO_IN_FRAMES 0x80000000 /* internal kernel flag - FIFO size is in frames */ |
262 | 263 | ||
263 | typedef int __bitwise snd_pcm_state_t; | 264 | typedef int __bitwise snd_pcm_state_t; |
@@ -406,7 +407,8 @@ struct snd_pcm_status { | |||
406 | snd_pcm_uframes_t avail_max; /* max frames available on hw since last status */ | 407 | snd_pcm_uframes_t avail_max; /* max frames available on hw since last status */ |
407 | snd_pcm_uframes_t overrange; /* count of ADC (capture) overrange detections from last status */ | 408 | snd_pcm_uframes_t overrange; /* count of ADC (capture) overrange detections from last status */ |
408 | snd_pcm_state_t suspended_state; /* suspended stream state */ | 409 | snd_pcm_state_t suspended_state; /* suspended stream state */ |
409 | unsigned char reserved[60]; /* must be filled with zero */ | 410 | struct timespec audio_tstamp; /* from sample counter or wall clock */ |
411 | unsigned char reserved[60-sizeof(struct timespec)]; /* must be filled with zero */ | ||
410 | }; | 412 | }; |
411 | 413 | ||
412 | struct snd_pcm_mmap_status { | 414 | struct snd_pcm_mmap_status { |
@@ -415,6 +417,7 @@ struct snd_pcm_mmap_status { | |||
415 | snd_pcm_uframes_t hw_ptr; /* RO: hw ptr (0...boundary-1) */ | 417 | snd_pcm_uframes_t hw_ptr; /* RO: hw ptr (0...boundary-1) */ |
416 | struct timespec tstamp; /* Timestamp */ | 418 | struct timespec tstamp; /* Timestamp */ |
417 | snd_pcm_state_t suspended_state; /* RO: suspended stream state */ | 419 | snd_pcm_state_t suspended_state; /* RO: suspended stream state */ |
420 | struct timespec audio_tstamp; /* from sample counter or wall clock */ | ||
418 | }; | 421 | }; |
419 | 422 | ||
420 | struct snd_pcm_mmap_control { | 423 | struct snd_pcm_mmap_control { |
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 | } |