Commit 4eeaaeae authored by Pierre-Louis Bossart's avatar Pierre-Louis Bossart Committed by Takashi Iwai
Browse files

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: default avatarPierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 0e8014d7
...@@ -71,6 +71,8 @@ struct snd_pcm_ops { ...@@ -71,6 +71,8 @@ struct snd_pcm_ops {
int (*prepare)(struct snd_pcm_substream *substream); int (*prepare)(struct snd_pcm_substream *substream);
int (*trigger)(struct snd_pcm_substream *substream, int cmd); int (*trigger)(struct snd_pcm_substream *substream, int cmd);
snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream); snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
int (*wall_clock)(struct snd_pcm_substream *substream,
struct timespec *audio_ts);
int (*copy)(struct snd_pcm_substream *substream, int channel, int (*copy)(struct snd_pcm_substream *substream, int channel,
snd_pcm_uframes_t pos, snd_pcm_uframes_t pos,
void __user *buf, snd_pcm_uframes_t count); void __user *buf, snd_pcm_uframes_t count);
......
...@@ -136,7 +136,7 @@ struct snd_hwdep_dsp_image { ...@@ -136,7 +136,7 @@ struct snd_hwdep_dsp_image {
* * * *
*****************************************************************************/ *****************************************************************************/
#define SNDRV_PCM_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 10) #define SNDRV_PCM_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 11)
typedef unsigned long snd_pcm_uframes_t; typedef unsigned long snd_pcm_uframes_t;
typedef signed long snd_pcm_sframes_t; typedef signed long snd_pcm_sframes_t;
...@@ -258,6 +258,7 @@ typedef int __bitwise snd_pcm_subformat_t; ...@@ -258,6 +258,7 @@ typedef int __bitwise snd_pcm_subformat_t;
#define SNDRV_PCM_INFO_JOINT_DUPLEX 0x00200000 /* playback and capture stream are somewhat correlated */ #define SNDRV_PCM_INFO_JOINT_DUPLEX 0x00200000 /* playback and capture stream are somewhat correlated */
#define SNDRV_PCM_INFO_SYNC_START 0x00400000 /* pcm support some kind of sync go */ #define SNDRV_PCM_INFO_SYNC_START 0x00400000 /* pcm support some kind of sync go */
#define SNDRV_PCM_INFO_NO_PERIOD_WAKEUP 0x00800000 /* period wakeup can be disabled */ #define SNDRV_PCM_INFO_NO_PERIOD_WAKEUP 0x00800000 /* period wakeup can be disabled */
#define SNDRV_PCM_INFO_HAS_WALL_CLOCK 0x01000000 /* has audio wall clock for audio/system time sync */
#define SNDRV_PCM_INFO_FIFO_IN_FRAMES 0x80000000 /* internal kernel flag - FIFO size is in frames */ #define SNDRV_PCM_INFO_FIFO_IN_FRAMES 0x80000000 /* internal kernel flag - FIFO size is in frames */
typedef int __bitwise snd_pcm_state_t; typedef int __bitwise snd_pcm_state_t;
...@@ -406,7 +407,8 @@ struct snd_pcm_status { ...@@ -406,7 +407,8 @@ struct snd_pcm_status {
snd_pcm_uframes_t avail_max; /* max frames available on hw since last status */ snd_pcm_uframes_t avail_max; /* max frames available on hw since last status */
snd_pcm_uframes_t overrange; /* count of ADC (capture) overrange detections from last status */ snd_pcm_uframes_t overrange; /* count of ADC (capture) overrange detections from last status */
snd_pcm_state_t suspended_state; /* suspended stream state */ snd_pcm_state_t suspended_state; /* suspended stream state */
unsigned char reserved[60]; /* must be filled with zero */ struct timespec audio_tstamp; /* from sample counter or wall clock */
unsigned char reserved[60-sizeof(struct timespec)]; /* must be filled with zero */
}; };
struct snd_pcm_mmap_status { struct snd_pcm_mmap_status {
...@@ -415,6 +417,7 @@ struct snd_pcm_mmap_status { ...@@ -415,6 +417,7 @@ struct snd_pcm_mmap_status {
snd_pcm_uframes_t hw_ptr; /* RO: hw ptr (0...boundary-1) */ snd_pcm_uframes_t hw_ptr; /* RO: hw ptr (0...boundary-1) */
struct timespec tstamp; /* Timestamp */ struct timespec tstamp; /* Timestamp */
snd_pcm_state_t suspended_state; /* RO: suspended stream state */ snd_pcm_state_t suspended_state; /* RO: suspended stream state */
struct timespec audio_tstamp; /* from sample counter or wall clock */
}; };
struct snd_pcm_mmap_control { struct snd_pcm_mmap_control {
......
...@@ -190,7 +190,8 @@ struct snd_pcm_status32 { ...@@ -190,7 +190,8 @@ struct snd_pcm_status32 {
u32 avail_max; u32 avail_max;
u32 overrange; u32 overrange;
s32 suspended_state; s32 suspended_state;
unsigned char reserved[60]; struct compat_timespec audio_tstamp;
unsigned char reserved[60-sizeof(struct compat_timespec)];
} __attribute__((packed)); } __attribute__((packed));
...@@ -205,17 +206,16 @@ static int snd_pcm_status_user_compat(struct snd_pcm_substream *substream, ...@@ -205,17 +206,16 @@ static int snd_pcm_status_user_compat(struct snd_pcm_substream *substream,
return err; return err;
if (put_user(status.state, &src->state) || if (put_user(status.state, &src->state) ||
put_user(status.trigger_tstamp.tv_sec, &src->trigger_tstamp.tv_sec) || compat_put_timespec(&status.trigger_tstamp, &src->trigger_tstamp) ||
put_user(status.trigger_tstamp.tv_nsec, &src->trigger_tstamp.tv_nsec) || compat_put_timespec(&status.tstamp, &src->tstamp) ||
put_user(status.tstamp.tv_sec, &src->tstamp.tv_sec) ||
put_user(status.tstamp.tv_nsec, &src->tstamp.tv_nsec) ||
put_user(status.appl_ptr, &src->appl_ptr) || put_user(status.appl_ptr, &src->appl_ptr) ||
put_user(status.hw_ptr, &src->hw_ptr) || put_user(status.hw_ptr, &src->hw_ptr) ||
put_user(status.delay, &src->delay) || put_user(status.delay, &src->delay) ||
put_user(status.avail, &src->avail) || put_user(status.avail, &src->avail) ||
put_user(status.avail_max, &src->avail_max) || put_user(status.avail_max, &src->avail_max) ||
put_user(status.overrange, &src->overrange) || put_user(status.overrange, &src->overrange) ||
put_user(status.suspended_state, &src->suspended_state)) put_user(status.suspended_state, &src->suspended_state) ||
compat_put_timespec(&status.audio_tstamp, &src->audio_tstamp))
return -EFAULT; return -EFAULT;
return err; return err;
...@@ -364,6 +364,7 @@ struct snd_pcm_mmap_status32 { ...@@ -364,6 +364,7 @@ struct snd_pcm_mmap_status32 {
u32 hw_ptr; u32 hw_ptr;
struct compat_timespec tstamp; struct compat_timespec tstamp;
s32 suspended_state; s32 suspended_state;
struct compat_timespec audio_tstamp;
} __attribute__((packed)); } __attribute__((packed));
struct snd_pcm_mmap_control32 { struct snd_pcm_mmap_control32 {
...@@ -426,12 +427,14 @@ static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream, ...@@ -426,12 +427,14 @@ static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream,
sstatus.hw_ptr = status->hw_ptr % boundary; sstatus.hw_ptr = status->hw_ptr % boundary;
sstatus.tstamp = status->tstamp; sstatus.tstamp = status->tstamp;
sstatus.suspended_state = status->suspended_state; sstatus.suspended_state = status->suspended_state;
sstatus.audio_tstamp = status->audio_tstamp;
snd_pcm_stream_unlock_irq(substream); snd_pcm_stream_unlock_irq(substream);
if (put_user(sstatus.state, &src->s.status.state) || if (put_user(sstatus.state, &src->s.status.state) ||
put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) || put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) ||
put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp.tv_sec) || compat_put_timespec(&sstatus.tstamp, &src->s.status.tstamp) ||
put_user(sstatus.tstamp.tv_nsec, &src->s.status.tstamp.tv_nsec) ||
put_user(sstatus.suspended_state, &src->s.status.suspended_state) || put_user(sstatus.suspended_state, &src->s.status.suspended_state) ||
compat_put_timespec(&sstatus.audio_tstamp,
&src->s.status.audio_tstamp) ||
put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) || put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
put_user(scontrol.avail_min, &src->c.control.avail_min)) put_user(scontrol.avail_min, &src->c.control.avail_min))
return -EFAULT; return -EFAULT;
......
...@@ -316,6 +316,7 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, ...@@ -316,6 +316,7 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
unsigned long jdelta; unsigned long jdelta;
unsigned long curr_jiffies; unsigned long curr_jiffies;
struct timespec curr_tstamp; struct timespec curr_tstamp;
struct timespec audio_tstamp;
int crossed_boundary = 0; int crossed_boundary = 0;
old_hw_ptr = runtime->status->hw_ptr; old_hw_ptr = runtime->status->hw_ptr;
...@@ -328,9 +329,14 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, ...@@ -328,9 +329,14 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
*/ */
pos = substream->ops->pointer(substream); pos = substream->ops->pointer(substream);
curr_jiffies = jiffies; curr_jiffies = jiffies;
if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {
snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp); snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp);
if ((runtime->hw.info & SNDRV_PCM_INFO_HAS_WALL_CLOCK) &&
(substream->ops->wall_clock))
substream->ops->wall_clock(substream, &audio_tstamp);
}
if (pos == SNDRV_PCM_POS_XRUN) { if (pos == SNDRV_PCM_POS_XRUN) {
xrun(substream); xrun(substream);
return -EPIPE; return -EPIPE;
...@@ -520,9 +526,31 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, ...@@ -520,9 +526,31 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
snd_BUG_ON(crossed_boundary != 1); snd_BUG_ON(crossed_boundary != 1);
runtime->hw_ptr_wrap += runtime->boundary; runtime->hw_ptr_wrap += runtime->boundary;
} }
if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {
runtime->status->tstamp = curr_tstamp; runtime->status->tstamp = curr_tstamp;
if (!(runtime->hw.info & SNDRV_PCM_INFO_HAS_WALL_CLOCK)) {
/*
* no wall clock available, provide audio timestamp
* derived from pointer position+delay
*/
u64 audio_frames, audio_nsecs;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
audio_frames = runtime->hw_ptr_wrap
+ runtime->status->hw_ptr
- runtime->delay;
else
audio_frames = runtime->hw_ptr_wrap
+ runtime->status->hw_ptr
+ runtime->delay;
audio_nsecs = div_u64(audio_frames * 1000000000LL,
runtime->rate);
audio_tstamp = ns_to_timespec(audio_nsecs);
}
runtime->status->audio_tstamp = audio_tstamp;
}
return snd_pcm_update_state(substream, runtime); return snd_pcm_update_state(substream, runtime);
} }
......
...@@ -594,6 +594,8 @@ int snd_pcm_status(struct snd_pcm_substream *substream, ...@@ -594,6 +594,8 @@ int snd_pcm_status(struct snd_pcm_substream *substream,
snd_pcm_update_hw_ptr(substream); snd_pcm_update_hw_ptr(substream);
if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) { if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {
status->tstamp = runtime->status->tstamp; status->tstamp = runtime->status->tstamp;
status->audio_tstamp =
runtime->status->audio_tstamp;
goto _tstamp_end; goto _tstamp_end;
} }
} }
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment