* [PATCH 1/2] ALSA: core: add hooks for audio timestamps
@ 2012-09-28 8:15 Pierre-Louis Bossart
2012-09-28 8:15 ` [PATCH 2/2] ALSA: hda: support for wallclock timestamps Pierre-Louis Bossart
2012-09-28 9:33 ` [PATCH 1/2] ALSA: core: add hooks for audio timestamps Clemens Ladisch
0 siblings, 2 replies; 18+ messages in thread
From: Pierre-Louis Bossart @ 2012-09-28 8:15 UTC (permalink / raw)
To: alsa-devel; +Cc: Pierre-Louis Bossart
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>
---
include/sound/asound.h | 7 +++++--
include/sound/pcm.h | 2 ++
sound/core/pcm_compat.c | 13 +++++++++++--
sound/core/pcm_lib.c | 29 +++++++++++++++++++++++++++--
sound/core/pcm_native.c | 2 ++
5 files changed, 47 insertions(+), 6 deletions(-)
diff --git a/include/sound/asound.h b/include/sound/asound.h
index 0876a1e..2d1ba63 100644
--- a/include/sound/asound.h
+++ b/include/sound/asound.h
@@ -152,7 +152,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 signed long snd_pcm_sframes_t;
@@ -274,6 +274,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_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_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 */
typedef int __bitwise snd_pcm_state_t;
@@ -422,7 +423,8 @@ struct snd_pcm_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_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 {
@@ -431,6 +433,7 @@ struct snd_pcm_mmap_status {
snd_pcm_uframes_t hw_ptr; /* RO: hw ptr (0...boundary-1) */
struct timespec tstamp; /* Timestamp */
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 {
diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index cdca2ab..35c2bbf 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -71,6 +71,8 @@ struct snd_pcm_ops {
int (*prepare)(struct snd_pcm_substream *substream);
int (*trigger)(struct snd_pcm_substream *substream, int cmd);
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,
snd_pcm_uframes_t pos,
void __user *buf, snd_pcm_uframes_t count);
diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c
index 91cdf94..ab4c953 100644
--- a/sound/core/pcm_compat.c
+++ b/sound/core/pcm_compat.c
@@ -190,7 +190,8 @@ struct snd_pcm_status32 {
u32 avail_max;
u32 overrange;
s32 suspended_state;
- unsigned char reserved[60];
+ struct timespec audio_tstamp;
+ unsigned char reserved[60-sizeof(struct timespec)];
} __attribute__((packed));
@@ -215,7 +216,9 @@ static int snd_pcm_status_user_compat(struct snd_pcm_substream *substream,
put_user(status.avail, &src->avail) ||
put_user(status.avail_max, &src->avail_max) ||
put_user(status.overrange, &src->overrange) ||
- put_user(status.suspended_state, &src->suspended_state))
+ put_user(status.suspended_state, &src->suspended_state) ||
+ put_user(status.audio_tstamp.tv_sec, &src->audio_tstamp.tv_sec) ||
+ put_user(status.audio_tstamp.tv_nsec, &src->audio_tstamp.tv_nsec))
return -EFAULT;
return err;
@@ -364,6 +367,7 @@ struct snd_pcm_mmap_status32 {
u32 hw_ptr;
struct compat_timespec tstamp;
s32 suspended_state;
+ struct compat_timespec audio_tstamp;
} __attribute__((packed));
struct snd_pcm_mmap_control32 {
@@ -426,12 +430,17 @@ static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream,
sstatus.hw_ptr = status->hw_ptr % boundary;
sstatus.tstamp = status->tstamp;
sstatus.suspended_state = status->suspended_state;
+ sstatus.audio_tstamp = status->audio_tstamp;
snd_pcm_stream_unlock_irq(substream);
if (put_user(sstatus.state, &src->s.status.state) ||
put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) ||
put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp.tv_sec) ||
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.audio_tstamp.tv_sec,
+ &src->s.status.audio_tstamp.tv_sec) ||
+ put_user(sstatus.audio_tstamp.tv_nsec,
+ &src->s.status.audio_tstamp.tv_nsec) ||
put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
put_user(scontrol.avail_min, &src->c.control.avail_min))
return -EFAULT;
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index 7ae6719..ee0c931 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -315,6 +315,7 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
unsigned long jdelta;
unsigned long curr_jiffies;
struct timespec curr_tstamp;
+ struct timespec audio_tstamp;
old_hw_ptr = runtime->status->hw_ptr;
@@ -326,9 +327,14 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
*/
pos = substream->ops->pointer(substream);
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);
+ 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) {
xrun(substream);
return -EPIPE;
@@ -506,8 +512,27 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
runtime->hw_ptr_base = hw_base;
runtime->status->hw_ptr = new_hw_ptr;
runtime->hw_ptr_jiffies = curr_jiffies;
- if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE)
+ if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {
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->status->hw_ptr
+ - runtime->delay;
+ else
+ audio_frames = runtime->status->hw_ptr
+ + runtime->delay;
+ audio_nsecs = 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);
}
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 53b5ada..c5206f4 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,
snd_pcm_update_hw_ptr(substream);
if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {
status->tstamp = runtime->status->tstamp;
+ status->audio_tstamp =
+ runtime->status->audio_tstamp;
goto _tstamp_end;
}
}
--
1.7.9.5
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 2/2] ALSA: hda: support for wallclock timestamps
2012-09-28 8:15 [PATCH 1/2] ALSA: core: add hooks for audio timestamps Pierre-Louis Bossart
@ 2012-09-28 8:15 ` Pierre-Louis Bossart
2012-09-28 9:33 ` Clemens Ladisch
2012-09-29 1:47 ` Raymond Yau
2012-09-28 9:33 ` [PATCH 1/2] ALSA: core: add hooks for audio timestamps Clemens Ladisch
1 sibling, 2 replies; 18+ messages in thread
From: Pierre-Louis Bossart @ 2012-09-28 8:15 UTC (permalink / raw)
To: alsa-devel; +Cc: Pierre-Louis Bossart
Reuse code from clocksource to handle wall clock counter.
Since wrapparound occurs, the audio timestamp is reinitialized
to zero on a trigger. Synchronized linked devices will
start counting from same reference to avoid any drift.
Max buffer time is limited to 178 seconds to make sure
wall clock counter does not overflow
Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
---
sound/pci/hda/hda_intel.c | 89 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 89 insertions(+)
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
index b682442..208f73d 100644
--- a/sound/pci/hda/hda_intel.c
+++ b/sound/pci/hda/hda_intel.c
@@ -46,6 +46,10 @@
#include <linux/mutex.h>
#include <linux/reboot.h>
#include <linux/io.h>
+
+#include <linux/clocksource.h>
+#include <linux/time.h>
+
#ifdef CONFIG_X86
/* for snoop control */
#include <asm/pgtable.h>
@@ -406,6 +410,9 @@ struct azx_dev {
*/
unsigned int insufficient :1;
unsigned int wc_marked:1;
+
+ struct timecounter azx_tc;
+ struct cyclecounter azx_cc;
};
/* CORB/RIRB */
@@ -1703,6 +1710,64 @@ static inline void azx_release_device(struct azx_dev *azx_dev)
azx_dev->opened = 0;
}
+static cycle_t azx_cc_read(const struct cyclecounter *cc)
+{
+ struct azx_dev *azx_dev = container_of(cc, struct azx_dev, azx_cc);
+ struct snd_pcm_substream *substream = azx_dev->substream;
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+
+ return azx_readl(chip, WALLCLK);
+}
+
+static void azx_timecounter_init(struct snd_pcm_substream *substream,
+ bool force, cycle_t last)
+{
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ struct timecounter *tc = &azx_dev->azx_tc;
+ struct cyclecounter *cc = &azx_dev->azx_cc;
+ u64 nsec;
+
+ cc->read = azx_cc_read;
+ cc->mask = CLOCKSOURCE_MASK(32);
+
+ /*
+ * Converting from 24 MHz to ns means applying a 125/3 factor.
+ * To avoid any saturation issues in intermediate operations,
+ * the 125 factor is applied first. The division is applied
+ * last after reading the timecounter value.
+ * Applying the 1/3 factor as part of the multiplication
+ * requires at least 20 bits for a decent precision, however
+ * overflows occur after about 4 hours or less, not a option.
+ */
+
+ cc->mult = 125; /* saturation after 195 years */
+ cc->shift = 0;
+
+ nsec = 0; /* audio time is elapsed time since trigger */
+ timecounter_init(tc, cc, nsec);
+ if (force)
+ /*
+ * force timecounter to use predefined value,
+ * used for synchronized starts
+ */
+ tc->cycle_last = last;
+}
+
+static int azx_get_wallclock_tstamp(struct snd_pcm_substream *substream,
+ struct timespec *ts)
+{
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ u64 nsec;
+
+ nsec = timecounter_read(&azx_dev->azx_tc);
+ nsec /= 3; /* can be optimized */
+
+ *ts = ns_to_timespec(nsec);
+
+ return 0;
+}
+
static struct snd_pcm_hardware azx_pcm_hw = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
@@ -1712,6 +1777,7 @@ static struct snd_pcm_hardware azx_pcm_hw = {
/* SNDRV_PCM_INFO_RESUME |*/
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_SYNC_START |
+ SNDRV_PCM_INFO_HAS_WALL_CLOCK |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_48000,
@@ -1751,6 +1817,12 @@ static int azx_pcm_open(struct snd_pcm_substream *substream)
runtime->hw.rates = hinfo->rates;
snd_pcm_limit_hw_rates(runtime);
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+ /* avoid wrap-around with wall-clock */
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_TIME,
+ 20,
+ 178000000);
+
if (chip->align_buffer_size)
/* constrain buffer sizes to be multiple of 128
bytes. This is more efficient in terms of memory
@@ -2024,6 +2096,22 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
azx_readl(chip, OLD_SSYNC) & ~sbits);
else
azx_writel(chip, SSYNC, azx_readl(chip, SSYNC) & ~sbits);
+ if (start) {
+ azx_timecounter_init(substream, 0, 0);
+ if (nsync > 1) {
+ cycle_t cycle_last;
+
+ /* same start cycle for master and group */
+ azx_dev = get_azx_dev(substream);
+ cycle_last = azx_dev->azx_tc.cycle_last;
+
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s->pcm->card != substream->pcm->card)
+ continue;
+ azx_timecounter_init(s, 1, cycle_last);
+ }
+ }
+ }
spin_unlock(&chip->reg_lock);
return 0;
}
@@ -2260,6 +2348,7 @@ static struct snd_pcm_ops azx_pcm_ops = {
.prepare = azx_pcm_prepare,
.trigger = azx_pcm_trigger,
.pointer = azx_pcm_pointer,
+ .wall_clock = azx_get_wallclock_tstamp,
.mmap = azx_pcm_mmap,
.page = snd_pcm_sgbuf_ops_page,
};
--
1.7.9.5
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH 2/2] ALSA: hda: support for wallclock timestamps
2012-09-28 8:15 ` [PATCH 2/2] ALSA: hda: support for wallclock timestamps Pierre-Louis Bossart
@ 2012-09-28 9:33 ` Clemens Ladisch
2012-09-29 20:12 ` Pierre-Louis Bossart
2012-09-29 1:47 ` Raymond Yau
1 sibling, 1 reply; 18+ messages in thread
From: Clemens Ladisch @ 2012-09-28 9:33 UTC (permalink / raw)
To: Pierre-Louis Bossart; +Cc: alsa-devel
Pierre-Louis Bossart wrote:
> @@ -1712,6 +1777,7 @@ static struct snd_pcm_hardware azx_pcm_hw = {
> /* SNDRV_PCM_INFO_RESUME |*/
> SNDRV_PCM_INFO_PAUSE |
> SNDRV_PCM_INFO_SYNC_START |
> + SNDRV_PCM_INFO_HAS_WALL_CLOCK |
... but not if this device is connected to an S/PDIF input.
Regards,
Clemens
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/2] ALSA: hda: support for wallclock timestamps
2012-09-28 9:33 ` Clemens Ladisch
@ 2012-09-29 20:12 ` Pierre-Louis Bossart
2012-09-30 10:12 ` Clemens Ladisch
0 siblings, 1 reply; 18+ messages in thread
From: Pierre-Louis Bossart @ 2012-09-29 20:12 UTC (permalink / raw)
To: alsa-devel
On 09/28/2012 04:33 AM, Clemens Ladisch wrote:
> Pierre-Louis Bossart wrote:
>> @@ -1712,6 +1777,7 @@ static struct snd_pcm_hardware azx_pcm_hw = {
>> /* SNDRV_PCM_INFO_RESUME |*/
>> SNDRV_PCM_INFO_PAUSE |
>> SNDRV_PCM_INFO_SYNC_START |
>> + SNDRV_PCM_INFO_HAS_WALL_CLOCK |
>
> ... but not if this device is connected to an S/PDIF input.
Yeah, but I can't figure out what happens here. HDAudio is the clock
master in all cases, unless the audio codec does an ASRC I don't think
this works. I've looked at the spec several times, asked around, this
case looks wild.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/2] ALSA: hda: support for wallclock timestamps
2012-09-29 20:12 ` Pierre-Louis Bossart
@ 2012-09-30 10:12 ` Clemens Ladisch
0 siblings, 0 replies; 18+ messages in thread
From: Clemens Ladisch @ 2012-09-30 10:12 UTC (permalink / raw)
To: alsa-devel
Pierre-Louis Bossart wrote:
> On 09/28/2012 04:33 AM, Clemens Ladisch wrote:
>> Pierre-Louis Bossart wrote:
>>> @@ -1712,6 +1777,7 @@ static struct snd_pcm_hardware azx_pcm_hw = {
>>> /* SNDRV_PCM_INFO_RESUME |*/
>>> SNDRV_PCM_INFO_PAUSE |
>>> SNDRV_PCM_INFO_SYNC_START |
>>> + SNDRV_PCM_INFO_HAS_WALL_CLOCK |
>>
>> ... but not if this device is connected to an S/PDIF input.
>
> Yeah, but I can't figure out what happens here. HDAudio is the clock
> master in all cases
Except for those cases described in section 5.4.3 of the HDA spec.
Finding out whether a device is connected to an S/PDIF input is not easy
and might change dynamically.
It might be easiest to omit HAS_WALL_CLOCK for inputs ...
Regards,
Clemens
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/2] ALSA: hda: support for wallclock timestamps
2012-09-28 8:15 ` [PATCH 2/2] ALSA: hda: support for wallclock timestamps Pierre-Louis Bossart
2012-09-28 9:33 ` Clemens Ladisch
@ 2012-09-29 1:47 ` Raymond Yau
1 sibling, 0 replies; 18+ messages in thread
From: Raymond Yau @ 2012-09-29 1:47 UTC (permalink / raw)
To: Pierre-Louis Bossart; +Cc: alsa-devel
>
> Reuse code from clocksource to handle wall clock counter.
> Since wrapparound occurs, the audio timestamp is reinitialized
> to zero on a trigger. Synchronized linked devices will
> start counting from same reference to avoid any drift.
> @@ -2024,6 +2096,22 @@ static int azx_pcm_trigger(struct
snd_pcm_substream *substream, int cmd)
> azx_readl(chip, OLD_SSYNC) & ~sbits);
> else
> azx_writel(chip, SSYNC, azx_readl(chip, SSYNC) & ~sbits);
> + if (start) {
> + azx_timecounter_init(substream, 0, 0);
> + if (nsync > 1) {
> + cycle_t cycle_last;
> +
> + /* same start cycle for master and group */
> + azx_dev = get_azx_dev(substream);
> + cycle_last = azx_dev->azx_tc.cycle_last;
> +
> + snd_pcm_group_for_each_entry(s, substream) {
> + if (s->pcm->card != substream->pcm->card)
> + continue;
> + azx_timecounter_init(s, 1, cycle_last);
> + }
> + }
> + }
> spin_unlock(&chip->reg_lock);
> return 0;
> }
Will the timer reinitialise when multistreaming using front panel headphone
, rear panel speakers , digital output and hdmi ?
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/2] ALSA: core: add hooks for audio timestamps
2012-09-28 8:15 [PATCH 1/2] ALSA: core: add hooks for audio timestamps Pierre-Louis Bossart
2012-09-28 8:15 ` [PATCH 2/2] ALSA: hda: support for wallclock timestamps Pierre-Louis Bossart
@ 2012-09-28 9:33 ` Clemens Ladisch
2012-09-29 20:17 ` Pierre-Louis Bossart
1 sibling, 1 reply; 18+ messages in thread
From: Clemens Ladisch @ 2012-09-28 9:33 UTC (permalink / raw)
To: Pierre-Louis Bossart; +Cc: alsa-devel
Pierre-Louis Bossart wrote:
> @@ -506,8 +512,27 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
> + 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->status->hw_ptr
> + - runtime->delay;
> + else
> + audio_frames = runtime->status->hw_ptr
> + + runtime->delay;
> + audio_nsecs = audio_frames * 1000000000LL /
> + runtime->rate;
This looks like a 64-bit division.
And what happens if audio_frames becomes negative?
Regards,
Clemens
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/2] ALSA: core: add hooks for audio timestamps
2012-09-28 9:33 ` [PATCH 1/2] ALSA: core: add hooks for audio timestamps Clemens Ladisch
@ 2012-09-29 20:17 ` Pierre-Louis Bossart
2012-09-30 10:11 ` Clemens Ladisch
0 siblings, 1 reply; 18+ messages in thread
From: Pierre-Louis Bossart @ 2012-09-29 20:17 UTC (permalink / raw)
To: Clemens Ladisch; +Cc: alsa-devel
>> + audio_frames = runtime->status->hw_ptr
>> + + runtime->delay;
>> + audio_nsecs = audio_frames * 1000000000LL /
>> + runtime->rate;
>
> This looks like a 64-bit division.
>
> And what happens if audio_frames becomes negative?
It's my understanding that hw_ptr represents the cumulative frames
played since the beginning, not sure why it'd become negative, ever.
I know this deserves more love, I don't understand the notion of
'boundary' and the use of the hw_ptr_base, I figured smarter people than
me would help.
Thanks,
-Pierre
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/2] ALSA: core: add hooks for audio timestamps
2012-09-29 20:17 ` Pierre-Louis Bossart
@ 2012-09-30 10:11 ` Clemens Ladisch
0 siblings, 0 replies; 18+ messages in thread
From: Clemens Ladisch @ 2012-09-30 10:11 UTC (permalink / raw)
To: Pierre-Louis Bossart; +Cc: alsa-devel
Pierre-Louis Bossart wrote:
>>> + audio_frames = runtime->status->hw_ptr
>>> + + runtime->delay;
>>> + audio_nsecs = audio_frames * 1000000000LL /
>>> + runtime->rate;
>>
>> This looks like a 64-bit division.
... which needs to be handled with a function from <linux/math64.h>.
>> And what happens if audio_frames becomes negative?
>
> It's my understanding that hw_ptr represents the cumulative frames
> played since the beginning, not sure why it'd become negative, ever.
2^32 / 192 kHz = 6.2 h
> I don't understand the notion of 'boundary'
This is where hw_ptr wraps around. It's a multiple of the buffer size
to make some calculations easier.
> and the use of the hw_ptr_base
It's the hw_ptr corresponding to the start of the buffer.
I guess audio_tstamp isn't supposed to wrap around?
Regards,
Clemens
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 0/2] RFC: support for audio wall clock
@ 2012-06-28 21:12 Pierre-Louis Bossart
2012-06-28 21:12 ` [PATCH 2/2] ALSA: hda: support for wallclock timestamps Pierre-Louis Bossart
0 siblings, 1 reply; 18+ messages in thread
From: Pierre-Louis Bossart @ 2012-06-28 21:12 UTC (permalink / raw)
To: alsa-devel; +Cc: Pierre-Louis Bossart
V2 of patches to show how the audio wall clock (eg. the
HDAudio one) can be used to provide precise audio timestamps and track
the drift between audio time and system time. On my laptop I am able
to track a 7ppm delta.
I will explain in more details how this can be
used during LPC in San Diego, but with the summer coming I'd like to
get feedback as there are changes to the core/alsa-lib that haven't
been modified in years.
TODO:
- 64-bit alignment, verification of pcm_compat.c changes
- handling of digital inputs, need to disable wall-clock timestamps in that case (feedback from Clemens)
- check differences in protocol
- change timecounter to avoid accumulation of rounding errors in cycle->ns conversions
- check overflows
- rework the timecounter init on trigger
Pierre-Louis Bossart (2):
ALSA: core: add hooks for audio timestamps read from WALLCLOCK
ALSA: hda: support for wallclock timestamps
include/sound/asound.h | 7 ++++-
include/sound/pcm.h | 2 +
sound/core/pcm_compat.c | 13 +++++++++-
sound/core/pcm_lib.c | 15 ++++++++++-
sound/core/pcm_native.c | 2 +
sound/pci/hda/hda_intel.c | 54 ++++++++++++++++++++++++++++++++++++++++++++-
6 files changed, 86 insertions(+), 7 deletions(-)
--
1.7.6.5
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 2/2] ALSA: hda: support for wallclock timestamps
2012-06-28 21:12 [PATCH 0/2] RFC: support for audio wall clock Pierre-Louis Bossart
@ 2012-06-28 21:12 ` Pierre-Louis Bossart
0 siblings, 0 replies; 18+ messages in thread
From: Pierre-Louis Bossart @ 2012-06-28 21:12 UTC (permalink / raw)
To: alsa-devel; +Cc: Pierre-Louis Bossart
reuse code from clocksource to handle wall clock counter.
Since wrapparound occurs, the timestamps are reinitialized with
monotonic time on a trigger.
TODO:
- only re-init timecounter if there was no device active.
- Keep the same timestamp for all devices on same chip.
- make sure no overflow occurs in the 125/3 scaling implementation
Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
---
sound/pci/hda/hda_intel.c | 54 ++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 53 insertions(+), 1 deletions(-)
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
index 86758dd..9bb795b 100644
--- a/sound/pci/hda/hda_intel.c
+++ b/sound/pci/hda/hda_intel.c
@@ -46,6 +46,10 @@
#include <linux/mutex.h>
#include <linux/reboot.h>
#include <linux/io.h>
+
+#include <linux/clocksource.h>
+#include <linux/time.h>
+
#ifdef CONFIG_X86
/* for snoop control */
#include <asm/pgtable.h>
@@ -496,6 +500,10 @@ struct azx {
/* reboot notifier (for mysterious hangup problem at power-down) */
struct notifier_block reboot_notifier;
+
+ struct timecounter azx_tc;
+ struct cyclecounter azx_cc;
+
};
/* driver types */
@@ -1700,6 +1708,45 @@ static inline void azx_release_device(struct azx_dev *azx_dev)
azx_dev->opened = 0;
}
+static cycle_t azx_cc_read(const struct cyclecounter *cc)
+{
+ struct azx *chip = container_of(cc, struct azx, azx_cc);
+
+ return azx_readl(chip, WALLCLK);
+}
+
+static void azx_timecounter_init(struct azx *chip)
+{
+ struct timecounter *tc = &chip->azx_tc;
+ struct cyclecounter *cc = &chip->azx_cc;
+ struct timespec ts;
+ u64 nsec;
+
+ cc->read = azx_cc_read;
+ cc->mask = CLOCKSOURCE_MASK(32);
+
+#define NBITS_NS 12 /* accuracy issues in cyc2ns with lower number of bits */
+ cc->mult = clocksource_khz2mult(24000, NBITS_NS);
+ cc->shift = NBITS_NS;
+
+ ktime_get_ts(&ts);
+ nsec = timespec_to_ns(&ts);
+ timecounter_init(tc, cc, nsec);
+}
+
+static int azx_get_wallclock_tstamp(struct snd_pcm_substream *substream,
+ struct timespec *ts)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ u64 nsec;
+
+ nsec = timecounter_read(&chip->azx_tc);
+ *ts = ns_to_timespec(nsec);
+
+ return 0;
+}
+
static struct snd_pcm_hardware azx_pcm_hw = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
@@ -1709,6 +1756,7 @@ static struct snd_pcm_hardware azx_pcm_hw = {
/* SNDRV_PCM_INFO_RESUME |*/
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_SYNC_START |
+ SNDRV_PCM_INFO_HAS_WALL_CLOCK |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_48000,
@@ -1982,8 +2030,10 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
}
spin_unlock(&chip->reg_lock);
if (start) {
- if (nsync == 1)
+ if (nsync == 1) {
+ azx_timecounter_init(chip);
return 0;
+ }
/* wait until all FIFOs get ready */
for (timeout = 5000; timeout; timeout--) {
nwait = 0;
@@ -1999,6 +2049,7 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
break;
cpu_relax();
}
+ azx_timecounter_init(chip);
} else {
/* wait until all RUN bits are cleared */
for (timeout = 5000; timeout; timeout--) {
@@ -2240,6 +2291,7 @@ static struct snd_pcm_ops azx_pcm_ops = {
.prepare = azx_pcm_prepare,
.trigger = azx_pcm_trigger,
.pointer = azx_pcm_pointer,
+ .wall_clock = azx_get_wallclock_tstamp,
.mmap = azx_pcm_mmap,
.page = snd_pcm_sgbuf_ops_page,
};
--
1.7.6.5
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 0/2] RFC: support for audio wall clock
@ 2012-06-13 20:26 Pierre-Louis Bossart
2012-06-13 20:26 ` [PATCH 2/2] ALSA: hda: support for wallclock timestamps Pierre-Louis Bossart
0 siblings, 1 reply; 18+ messages in thread
From: Pierre-Louis Bossart @ 2012-06-13 20:26 UTC (permalink / raw)
To: alsa-devel; +Cc: Pierre-Louis Bossart
Attached a set of patches to show how the audio wall clock (eg. the
HDAudio one) can be used to provide precise audio timestamps and track
the drift between audio time and system time. On my laptop I am able
to track a 7ppm delta. I will explain in more details how this can be
used during LPC in San Diego, but with the summer coming I'd like to
get feedback as there are changes to the core/alsa-lib that haven't
been modified in years.
Pierre-Louis Bossart (2):
ALSA: core: add hooks for audio timestamps read from WALLCLOCK
ALSA: hda: support for wallclock timestamps
include/sound/asound.h | 7 +++-
include/sound/pcm.h | 2 +
sound/core/pcm_lib.c | 15 ++++++-
sound/core/pcm_native.c | 2 +
sound/pci/hda/hda_intel.c | 106 ++++++++++++++++++++++++++++++++++++++++++++-
5 files changed, 128 insertions(+), 4 deletions(-)
--
1.7.6.5
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 2/2] ALSA: hda: support for wallclock timestamps
2012-06-13 20:26 [PATCH 0/2] RFC: support for audio wall clock Pierre-Louis Bossart
@ 2012-06-13 20:26 ` Pierre-Louis Bossart
2012-06-14 3:32 ` Wang Xingchao
2012-06-14 7:38 ` Takashi Iwai
0 siblings, 2 replies; 18+ messages in thread
From: Pierre-Louis Bossart @ 2012-06-13 20:26 UTC (permalink / raw)
To: alsa-devel; +Cc: Pierre-Louis Bossart
reuse code from clocksource to handle wall clock counter,
translate cycles to timespec. The code wasn't reused as
is as the HDA wall clock is based on 24MHz ticks. To avoid
compounding rounding errors, the counters track cycles
and will convert elapsed cycles to ns, instead of delta cycles
as done in the cyclecounter code.
Since wrapparound occurs, the timestamps are reinitialized with
monotonic time on a trigger.
TODO:
- only re-init timecounter if there was no device active.
- Keep the same timestamp for all devices on same chip.
- make sure no overflow occurs in the 125/3 scaling implementation
Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
---
sound/pci/hda/hda_intel.c | 106 ++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 105 insertions(+), 1 deletions(-)
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
index 2b6392b..f9b9bc1 100644
--- a/sound/pci/hda/hda_intel.c
+++ b/sound/pci/hda/hda_intel.c
@@ -46,6 +46,10 @@
#include <linux/mutex.h>
#include <linux/reboot.h>
#include <linux/io.h>
+
+#include <linux/clocksource.h>
+#include <linux/time.h>
+
#ifdef CONFIG_X86
/* for snoop control */
#include <asm/pgtable.h>
@@ -426,6 +430,15 @@ struct azx_pcm {
struct list_head list;
};
+struct azx_timecounter {
+ cycle_t cycle_last;
+ cycle_t mask;
+ cycle_t elapsed_cycles;
+ u64 initial_time_nsec;
+ u32 mult;
+ u32 shift;
+};
+
struct azx {
struct snd_card *card;
struct pci_dev *pci;
@@ -496,6 +509,9 @@ struct azx {
/* reboot notifier (for mysterious hangup problem at power-down) */
struct notifier_block reboot_notifier;
+
+ /* Wall clock counter */
+ struct azx_timecounter tc;
};
/* driver types */
@@ -1699,6 +1715,89 @@ static inline void azx_release_device(struct azx_dev *azx_dev)
azx_dev->opened = 0;
}
+static void azx_timecounter_init(struct azx *chip)
+{
+ struct azx_timecounter *tc = &chip->tc;
+ struct timespec ts;
+
+ tc->cycle_last = azx_readl(chip, WALLCLK);
+ tc->elapsed_cycles = 0;
+ tc->mask = CLOCKSOURCE_MASK(32);
+ /*
+ * conversion from 24MHz to nsec requires fractional operation,
+ * approximate 125/3 ratio
+ */
+#define NBITS_NS 16
+ tc->mult = (u32)(1<<NBITS_NS)*125L/3L;
+ tc->shift = NBITS_NS;
+
+ /* save initial time */
+ ktime_get_ts(&ts);
+ tc->initial_time_nsec = timespec_to_ns(&ts);
+}
+
+/**
+ * azx_timecounter_read_delta - get nanoseconds since last call of this function
+ * @tc: Pointer to time counter
+ *
+ * When the underlying cycle counter runs over, this will be handled
+ * correctly as long as it does not run over more than once between
+ * calls.
+ *
+ * The first call to this function for a new time counter initializes
+ * the time tracking and returns an undefined result.
+ */
+static u64 azx_timecounter_read_delta(struct azx_timecounter *tc,
+ cycle_t cycle_now)
+{
+ cycle_t cycle_delta;
+ u64 nsec;
+
+ /* calculate the delta since the last timecounter_read_delta(): */
+ cycle_delta = (cycle_now - tc->cycle_last) & tc->mask;
+
+ tc->elapsed_cycles += cycle_delta;
+
+ /* convert to nanoseconds: */
+ nsec = (u64)tc->elapsed_cycles;
+ nsec = (nsec * tc->mult) >> tc->shift;
+
+ /* update time stamp of azx_timecounter_read_delta() call: */
+ tc->cycle_last = cycle_now;
+
+ return nsec;
+}
+
+u64 azx_timecounter_read(struct azx *chip)
+{
+ u64 nsec;
+ struct azx_timecounter *tc = &chip->tc;
+ cycle_t cycle_now;
+
+ /* read cycle counter: */
+ cycle_now = azx_readl(chip, WALLCLK);
+
+ /* increment time by nanoseconds since last call */
+ nsec = azx_timecounter_read_delta(tc, cycle_now);
+ nsec += tc->initial_time_nsec;
+
+ return nsec;
+}
+
+
+static int azx_get_wallclock_tstamp(struct snd_pcm_substream *substream,
+ struct timespec *ts)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ u64 nsec;
+
+ nsec = azx_timecounter_read(chip);
+ *ts = ns_to_timespec(nsec);
+
+ return 0;
+}
+
static struct snd_pcm_hardware azx_pcm_hw = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
@@ -1708,6 +1807,7 @@ static struct snd_pcm_hardware azx_pcm_hw = {
/* SNDRV_PCM_INFO_RESUME |*/
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_SYNC_START |
+ SNDRV_PCM_INFO_HAS_WALL_CLOCK |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_48000,
@@ -1981,8 +2081,10 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
}
spin_unlock(&chip->reg_lock);
if (start) {
- if (nsync == 1)
+ if (nsync == 1) {
+ azx_timecounter_init(chip);
return 0;
+ }
/* wait until all FIFOs get ready */
for (timeout = 5000; timeout; timeout--) {
nwait = 0;
@@ -1998,6 +2100,7 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
break;
cpu_relax();
}
+ azx_timecounter_init(chip);
} else {
/* wait until all RUN bits are cleared */
for (timeout = 5000; timeout; timeout--) {
@@ -2239,6 +2342,7 @@ static struct snd_pcm_ops azx_pcm_ops = {
.prepare = azx_pcm_prepare,
.trigger = azx_pcm_trigger,
.pointer = azx_pcm_pointer,
+ .wall_clock = azx_get_wallclock_tstamp,
.mmap = azx_pcm_mmap,
.page = snd_pcm_sgbuf_ops_page,
};
--
1.7.6.5
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH 2/2] ALSA: hda: support for wallclock timestamps
2012-06-13 20:26 ` [PATCH 2/2] ALSA: hda: support for wallclock timestamps Pierre-Louis Bossart
@ 2012-06-14 3:32 ` Wang Xingchao
2012-06-14 4:57 ` Pierre-Louis Bossart
2012-06-14 7:38 ` Takashi Iwai
1 sibling, 1 reply; 18+ messages in thread
From: Wang Xingchao @ 2012-06-14 3:32 UTC (permalink / raw)
To: Pierre-Louis Bossart; +Cc: alsa-devel
Hi Pierre,
2012/6/14 Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>:
> reuse code from clocksource to handle wall clock counter,
> translate cycles to timespec. The code wasn't reused as
> is as the HDA wall clock is based on 24MHz ticks. To avoid
> compounding rounding errors, the counters track cycles
> and will convert elapsed cycles to ns, instead of delta cycles
> as done in the cyclecounter code.
> Since wrapparound occurs, the timestamps are reinitialized with
> monotonic time on a trigger.
>
> TODO:
> - only re-init timecounter if there was no device active.
> - Keep the same timestamp for all devices on same chip.
> - make sure no overflow occurs in the 125/3 scaling implementation
>
I'm afraid you donot think much about the Wall Clock Counter register,
the counter will roll over to 0 within 179s under 24Mhz. Another case
maybe BCLK stop and controller reset.
thanks
--xingchao
> Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
> ---
> sound/pci/hda/hda_intel.c | 106 ++++++++++++++++++++++++++++++++++++++++++++-
> 1 files changed, 105 insertions(+), 1 deletions(-)
>
> diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
> index 2b6392b..f9b9bc1 100644
> --- a/sound/pci/hda/hda_intel.c
> +++ b/sound/pci/hda/hda_intel.c
> @@ -46,6 +46,10 @@
> #include <linux/mutex.h>
> #include <linux/reboot.h>
> #include <linux/io.h>
> +
> +#include <linux/clocksource.h>
> +#include <linux/time.h>
> +
> #ifdef CONFIG_X86
> /* for snoop control */
> #include <asm/pgtable.h>
> @@ -426,6 +430,15 @@ struct azx_pcm {
> struct list_head list;
> };
>
> +struct azx_timecounter {
> + cycle_t cycle_last;
> + cycle_t mask;
> + cycle_t elapsed_cycles;
> + u64 initial_time_nsec;
> + u32 mult;
> + u32 shift;
> +};
> +
> struct azx {
> struct snd_card *card;
> struct pci_dev *pci;
> @@ -496,6 +509,9 @@ struct azx {
>
> /* reboot notifier (for mysterious hangup problem at power-down) */
> struct notifier_block reboot_notifier;
> +
> + /* Wall clock counter */
> + struct azx_timecounter tc;
> };
>
> /* driver types */
> @@ -1699,6 +1715,89 @@ static inline void azx_release_device(struct azx_dev *azx_dev)
> azx_dev->opened = 0;
> }
>
> +static void azx_timecounter_init(struct azx *chip)
> +{
> + struct azx_timecounter *tc = &chip->tc;
> + struct timespec ts;
> +
> + tc->cycle_last = azx_readl(chip, WALLCLK);
> + tc->elapsed_cycles = 0;
> + tc->mask = CLOCKSOURCE_MASK(32);
> + /*
> + * conversion from 24MHz to nsec requires fractional operation,
> + * approximate 125/3 ratio
> + */
> +#define NBITS_NS 16
> + tc->mult = (u32)(1<<NBITS_NS)*125L/3L;
> + tc->shift = NBITS_NS;
> +
> + /* save initial time */
> + ktime_get_ts(&ts);
> + tc->initial_time_nsec = timespec_to_ns(&ts);
> +}
> +
> +/**
> + * azx_timecounter_read_delta - get nanoseconds since last call of this function
> + * @tc: Pointer to time counter
> + *
> + * When the underlying cycle counter runs over, this will be handled
> + * correctly as long as it does not run over more than once between
> + * calls.
> + *
> + * The first call to this function for a new time counter initializes
> + * the time tracking and returns an undefined result.
> + */
> +static u64 azx_timecounter_read_delta(struct azx_timecounter *tc,
> + cycle_t cycle_now)
> +{
> + cycle_t cycle_delta;
> + u64 nsec;
> +
> + /* calculate the delta since the last timecounter_read_delta(): */
> + cycle_delta = (cycle_now - tc->cycle_last) & tc->mask;
> +
> + tc->elapsed_cycles += cycle_delta;
> +
> + /* convert to nanoseconds: */
> + nsec = (u64)tc->elapsed_cycles;
> + nsec = (nsec * tc->mult) >> tc->shift;
> +
> + /* update time stamp of azx_timecounter_read_delta() call: */
> + tc->cycle_last = cycle_now;
> +
> + return nsec;
> +}
> +
> +u64 azx_timecounter_read(struct azx *chip)
> +{
> + u64 nsec;
> + struct azx_timecounter *tc = &chip->tc;
> + cycle_t cycle_now;
> +
> + /* read cycle counter: */
> + cycle_now = azx_readl(chip, WALLCLK);
> +
> + /* increment time by nanoseconds since last call */
> + nsec = azx_timecounter_read_delta(tc, cycle_now);
> + nsec += tc->initial_time_nsec;
> +
> + return nsec;
> +}
> +
> +
> +static int azx_get_wallclock_tstamp(struct snd_pcm_substream *substream,
> + struct timespec *ts)
> +{
> + struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
> + struct azx *chip = apcm->chip;
> + u64 nsec;
> +
> + nsec = azx_timecounter_read(chip);
> + *ts = ns_to_timespec(nsec);
> +
> + return 0;
> +}
> +
> static struct snd_pcm_hardware azx_pcm_hw = {
> .info = (SNDRV_PCM_INFO_MMAP |
> SNDRV_PCM_INFO_INTERLEAVED |
> @@ -1708,6 +1807,7 @@ static struct snd_pcm_hardware azx_pcm_hw = {
> /* SNDRV_PCM_INFO_RESUME |*/
> SNDRV_PCM_INFO_PAUSE |
> SNDRV_PCM_INFO_SYNC_START |
> + SNDRV_PCM_INFO_HAS_WALL_CLOCK |
> SNDRV_PCM_INFO_NO_PERIOD_WAKEUP),
> .formats = SNDRV_PCM_FMTBIT_S16_LE,
> .rates = SNDRV_PCM_RATE_48000,
> @@ -1981,8 +2081,10 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
> }
> spin_unlock(&chip->reg_lock);
> if (start) {
> - if (nsync == 1)
> + if (nsync == 1) {
> + azx_timecounter_init(chip);
> return 0;
> + }
> /* wait until all FIFOs get ready */
> for (timeout = 5000; timeout; timeout--) {
> nwait = 0;
> @@ -1998,6 +2100,7 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
> break;
> cpu_relax();
> }
> + azx_timecounter_init(chip);
> } else {
> /* wait until all RUN bits are cleared */
> for (timeout = 5000; timeout; timeout--) {
> @@ -2239,6 +2342,7 @@ static struct snd_pcm_ops azx_pcm_ops = {
> .prepare = azx_pcm_prepare,
> .trigger = azx_pcm_trigger,
> .pointer = azx_pcm_pointer,
> + .wall_clock = azx_get_wallclock_tstamp,
> .mmap = azx_pcm_mmap,
> .page = snd_pcm_sgbuf_ops_page,
> };
> --
> 1.7.6.5
>
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel@alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/2] ALSA: hda: support for wallclock timestamps
2012-06-14 3:32 ` Wang Xingchao
@ 2012-06-14 4:57 ` Pierre-Louis Bossart
2012-06-14 8:15 ` Clemens Ladisch
0 siblings, 1 reply; 18+ messages in thread
From: Pierre-Louis Bossart @ 2012-06-14 4:57 UTC (permalink / raw)
To: alsa-devel
On 6/13/2012 10:32 PM, Wang Xingchao wrote:
> I'm afraid you donot think much about the Wall Clock Counter register,
> the counter will roll over to 0 within 179s under 24Mhz. Another case
> maybe BCLK stop and controller reset. thanks --xingchao
Hi Xingchao,
all cycle counters will wrap around at some point, the HDAudio wall
clock isn't an exception. As long as you have 2 periods or events per
179s, the wrap-around can be detected without any issues. It's
explicitely handled in the code.
If the BCLK stops, then there's no real audio transfer happening, is
there? Likewise the controller reset does not matter since we re-init
during a trigger.
I don't see any of these points as show-stoppers. The HDAudio spec
clearly mentions that such a wall clock should be used for
synchronization with other devices, and I am enabling just that.
Regards,
-Pierre
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/2] ALSA: hda: support for wallclock timestamps
2012-06-14 4:57 ` Pierre-Louis Bossart
@ 2012-06-14 8:15 ` Clemens Ladisch
2012-06-14 17:01 ` Pierre-Louis Bossart
0 siblings, 1 reply; 18+ messages in thread
From: Clemens Ladisch @ 2012-06-14 8:15 UTC (permalink / raw)
To: Pierre-Louis Bossart; +Cc: alsa-devel
Pierre-Louis Bossart wrote:
> As long as you have 2 periods or events per 179s, the wrap-around can
> be detected without any issues. It's explicitely handled in the code.
AFAICS there is no code that enforces the 179s restriction.
And why are you using a separate wallclock timer instead of the sample
count? Does the higher resolution result in a noticeable improvement?
How should userspace detect streams whose sample clock is not
synchronous with this wall clock, such as digital inputs?
Regards,
Clemens
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/2] ALSA: hda: support for wallclock timestamps
2012-06-14 8:15 ` Clemens Ladisch
@ 2012-06-14 17:01 ` Pierre-Louis Bossart
0 siblings, 0 replies; 18+ messages in thread
From: Pierre-Louis Bossart @ 2012-06-14 17:01 UTC (permalink / raw)
To: alsa-devel
On 6/14/2012 3:15 AM, Clemens Ladisch wrote:
>> As long as you have 2 periods or events per 179s, the wrap-around can
>> be detected without any issues. It's explicitely handled in the code.
> AFAICS there is no code that enforces the 179s restriction.
179s corresponds to a 33MB buffer for stereo 48kHz 16bit. There is
indeed nothing preventing the wrap-around at the moment but this could
be achieved by limiting the buffer size.
> And why are you using a separate wallclock timer instead of the sample
> count? Does the higher resolution result in a noticeable improvement?
The wallclock is common for each HDAudio controller, this helps you
build _one_ estimator for the drift between audio time and system
(monotonic) time. It'd help avoid what PulseAudio does today, ie a
different drift estimate per sink/source. If you work with sample
counts, you'll have separate results for each devices and possibly
different ASRC in user-space. Also the precision of sample counters is
limited to 10us for 48kHz, a lot higher than what we can get with
PTP-based schemes. The accuracy of the wall clock is 41.6 ns, order of
magnitude more precise.
>
> How should userspace detect streams whose sample clock is not
> synchronous with this wall clock, such as digital inputs?
Good point. I didn't think about this case, i need to look into it.
Thanks for your feedback, much appreciated
-Pierre
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/2] ALSA: hda: support for wallclock timestamps
2012-06-13 20:26 ` [PATCH 2/2] ALSA: hda: support for wallclock timestamps Pierre-Louis Bossart
2012-06-14 3:32 ` Wang Xingchao
@ 2012-06-14 7:38 ` Takashi Iwai
2012-06-15 10:02 ` Pierre-Louis Bossart
1 sibling, 1 reply; 18+ messages in thread
From: Takashi Iwai @ 2012-06-14 7:38 UTC (permalink / raw)
To: Pierre-Louis Bossart; +Cc: alsa-devel
At Wed, 13 Jun 2012 15:26:32 -0500,
Pierre-Louis Bossart wrote:
>
> reuse code from clocksource to handle wall clock counter,
> translate cycles to timespec. The code wasn't reused as
> is as the HDA wall clock is based on 24MHz ticks. To avoid
> compounding rounding errors, the counters track cycles
> and will convert elapsed cycles to ns, instead of delta cycles
> as done in the cyclecounter code.
> Since wrapparound occurs, the timestamps are reinitialized with
> monotonic time on a trigger.
>
> TODO:
> - only re-init timecounter if there was no device active.
> - Keep the same timestamp for all devices on same chip.
> - make sure no overflow occurs in the 125/3 scaling implementation
>
> Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
> ---
> sound/pci/hda/hda_intel.c | 106 ++++++++++++++++++++++++++++++++++++++++++++-
> 1 files changed, 105 insertions(+), 1 deletions(-)
>
> diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
> index 2b6392b..f9b9bc1 100644
> --- a/sound/pci/hda/hda_intel.c
> +++ b/sound/pci/hda/hda_intel.c
> @@ -46,6 +46,10 @@
> #include <linux/mutex.h>
> #include <linux/reboot.h>
> #include <linux/io.h>
> +
> +#include <linux/clocksource.h>
> +#include <linux/time.h>
> +
> #ifdef CONFIG_X86
> /* for snoop control */
> #include <asm/pgtable.h>
> @@ -426,6 +430,15 @@ struct azx_pcm {
> struct list_head list;
> };
>
> +struct azx_timecounter {
> + cycle_t cycle_last;
> + cycle_t mask;
> + cycle_t elapsed_cycles;
> + u64 initial_time_nsec;
> + u32 mult;
> + u32 shift;
> +};
Any reason not using the normal struct timecounter stuff?
Most of the open codes can be replaced gracefully with functions /
macros there, I guess.
Takashi
> +
> struct azx {
> struct snd_card *card;
> struct pci_dev *pci;
> @@ -496,6 +509,9 @@ struct azx {
>
> /* reboot notifier (for mysterious hangup problem at power-down) */
> struct notifier_block reboot_notifier;
> +
> + /* Wall clock counter */
> + struct azx_timecounter tc;
> };
>
> /* driver types */
> @@ -1699,6 +1715,89 @@ static inline void azx_release_device(struct azx_dev *azx_dev)
> azx_dev->opened = 0;
> }
>
> +static void azx_timecounter_init(struct azx *chip)
> +{
> + struct azx_timecounter *tc = &chip->tc;
> + struct timespec ts;
> +
> + tc->cycle_last = azx_readl(chip, WALLCLK);
> + tc->elapsed_cycles = 0;
> + tc->mask = CLOCKSOURCE_MASK(32);
> + /*
> + * conversion from 24MHz to nsec requires fractional operation,
> + * approximate 125/3 ratio
> + */
> +#define NBITS_NS 16
> + tc->mult = (u32)(1<<NBITS_NS)*125L/3L;
> + tc->shift = NBITS_NS;
> +
> + /* save initial time */
> + ktime_get_ts(&ts);
> + tc->initial_time_nsec = timespec_to_ns(&ts);
> +}
> +
> +/**
> + * azx_timecounter_read_delta - get nanoseconds since last call of this function
> + * @tc: Pointer to time counter
> + *
> + * When the underlying cycle counter runs over, this will be handled
> + * correctly as long as it does not run over more than once between
> + * calls.
> + *
> + * The first call to this function for a new time counter initializes
> + * the time tracking and returns an undefined result.
> + */
> +static u64 azx_timecounter_read_delta(struct azx_timecounter *tc,
> + cycle_t cycle_now)
> +{
> + cycle_t cycle_delta;
> + u64 nsec;
> +
> + /* calculate the delta since the last timecounter_read_delta(): */
> + cycle_delta = (cycle_now - tc->cycle_last) & tc->mask;
> +
> + tc->elapsed_cycles += cycle_delta;
> +
> + /* convert to nanoseconds: */
> + nsec = (u64)tc->elapsed_cycles;
> + nsec = (nsec * tc->mult) >> tc->shift;
> +
> + /* update time stamp of azx_timecounter_read_delta() call: */
> + tc->cycle_last = cycle_now;
> +
> + return nsec;
> +}
> +
> +u64 azx_timecounter_read(struct azx *chip)
> +{
> + u64 nsec;
> + struct azx_timecounter *tc = &chip->tc;
> + cycle_t cycle_now;
> +
> + /* read cycle counter: */
> + cycle_now = azx_readl(chip, WALLCLK);
> +
> + /* increment time by nanoseconds since last call */
> + nsec = azx_timecounter_read_delta(tc, cycle_now);
> + nsec += tc->initial_time_nsec;
> +
> + return nsec;
> +}
> +
> +
> +static int azx_get_wallclock_tstamp(struct snd_pcm_substream *substream,
> + struct timespec *ts)
> +{
> + struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
> + struct azx *chip = apcm->chip;
> + u64 nsec;
> +
> + nsec = azx_timecounter_read(chip);
> + *ts = ns_to_timespec(nsec);
> +
> + return 0;
> +}
> +
> static struct snd_pcm_hardware azx_pcm_hw = {
> .info = (SNDRV_PCM_INFO_MMAP |
> SNDRV_PCM_INFO_INTERLEAVED |
> @@ -1708,6 +1807,7 @@ static struct snd_pcm_hardware azx_pcm_hw = {
> /* SNDRV_PCM_INFO_RESUME |*/
> SNDRV_PCM_INFO_PAUSE |
> SNDRV_PCM_INFO_SYNC_START |
> + SNDRV_PCM_INFO_HAS_WALL_CLOCK |
> SNDRV_PCM_INFO_NO_PERIOD_WAKEUP),
> .formats = SNDRV_PCM_FMTBIT_S16_LE,
> .rates = SNDRV_PCM_RATE_48000,
> @@ -1981,8 +2081,10 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
> }
> spin_unlock(&chip->reg_lock);
> if (start) {
> - if (nsync == 1)
> + if (nsync == 1) {
> + azx_timecounter_init(chip);
> return 0;
> + }
> /* wait until all FIFOs get ready */
> for (timeout = 5000; timeout; timeout--) {
> nwait = 0;
> @@ -1998,6 +2100,7 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
> break;
> cpu_relax();
> }
> + azx_timecounter_init(chip);
> } else {
> /* wait until all RUN bits are cleared */
> for (timeout = 5000; timeout; timeout--) {
> @@ -2239,6 +2342,7 @@ static struct snd_pcm_ops azx_pcm_ops = {
> .prepare = azx_pcm_prepare,
> .trigger = azx_pcm_trigger,
> .pointer = azx_pcm_pointer,
> + .wall_clock = azx_get_wallclock_tstamp,
> .mmap = azx_pcm_mmap,
> .page = snd_pcm_sgbuf_ops_page,
> };
> --
> 1.7.6.5
>
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel@alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/2] ALSA: hda: support for wallclock timestamps
2012-06-14 7:38 ` Takashi Iwai
@ 2012-06-15 10:02 ` Pierre-Louis Bossart
2012-06-15 10:34 ` Takashi Iwai
0 siblings, 1 reply; 18+ messages in thread
From: Pierre-Louis Bossart @ 2012-06-15 10:02 UTC (permalink / raw)
To: alsa-devel
>> +struct azx_timecounter {
>> + cycle_t cycle_last;
>> + cycle_t mask;
>> + cycle_t elapsed_cycles;
>> + u64 initial_time_nsec;
>> + u32 mult;
>> + u32 shift;
>> +};
>
> Any reason not using the normal struct timecounter stuff?
> Most of the open codes can be replaced gracefully with functions /
> macros there, I guess.
Yes there is a reason. The conversion from wall clock cycles to ns is a
fractional operation (125/3 ratio from 24 MHz to 1 GHz). If you do this
conversion to ns every time, you will accumulate rounding errors. That
doesn't seem like a very good design if the precision depends on the
duration of the track...
On top of this, I couldn't find a way to pass the 'chip' argument in the
cyclecounter .read() operation to map it to azx_read.
Makes sense?
-Pierre
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/2] ALSA: hda: support for wallclock timestamps
2012-06-15 10:02 ` Pierre-Louis Bossart
@ 2012-06-15 10:34 ` Takashi Iwai
0 siblings, 0 replies; 18+ messages in thread
From: Takashi Iwai @ 2012-06-15 10:34 UTC (permalink / raw)
To: Pierre-Louis Bossart; +Cc: alsa-devel
At Fri, 15 Jun 2012 05:02:38 -0500,
Pierre-Louis Bossart wrote:
>
>
> >> +struct azx_timecounter {
> >> + cycle_t cycle_last;
> >> + cycle_t mask;
> >> + cycle_t elapsed_cycles;
> >> + u64 initial_time_nsec;
> >> + u32 mult;
> >> + u32 shift;
> >> +};
> >
> > Any reason not using the normal struct timecounter stuff?
> > Most of the open codes can be replaced gracefully with functions /
> > macros there, I guess.
>
> Yes there is a reason. The conversion from wall clock cycles to ns is a
> fractional operation (125/3 ratio from 24 MHz to 1 GHz). If you do this
> conversion to ns every time, you will accumulate rounding errors. That
> doesn't seem like a very good design if the precision depends on the
> duration of the track...
Hm, OK. It's a shortcoming in the generic timecounter code, IMO.
It might make more sense to fix there.
BTW, the calculation mult can be simplified with
clocksource_khz2multi().
> On top of this, I couldn't find a way to pass the 'chip' argument in the
> cyclecounter .read() operation to map it to azx_read.
You can use container_of().
Takashi
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2012-09-30 10:13 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-09-28 8:15 [PATCH 1/2] ALSA: core: add hooks for audio timestamps Pierre-Louis Bossart
2012-09-28 8:15 ` [PATCH 2/2] ALSA: hda: support for wallclock timestamps Pierre-Louis Bossart
2012-09-28 9:33 ` Clemens Ladisch
2012-09-29 20:12 ` Pierre-Louis Bossart
2012-09-30 10:12 ` Clemens Ladisch
2012-09-29 1:47 ` Raymond Yau
2012-09-28 9:33 ` [PATCH 1/2] ALSA: core: add hooks for audio timestamps Clemens Ladisch
2012-09-29 20:17 ` Pierre-Louis Bossart
2012-09-30 10:11 ` Clemens Ladisch
-- strict thread matches above, loose matches on Subject: below --
2012-06-28 21:12 [PATCH 0/2] RFC: support for audio wall clock Pierre-Louis Bossart
2012-06-28 21:12 ` [PATCH 2/2] ALSA: hda: support for wallclock timestamps Pierre-Louis Bossart
2012-06-13 20:26 [PATCH 0/2] RFC: support for audio wall clock Pierre-Louis Bossart
2012-06-13 20:26 ` [PATCH 2/2] ALSA: hda: support for wallclock timestamps Pierre-Louis Bossart
2012-06-14 3:32 ` Wang Xingchao
2012-06-14 4:57 ` Pierre-Louis Bossart
2012-06-14 8:15 ` Clemens Ladisch
2012-06-14 17:01 ` Pierre-Louis Bossart
2012-06-14 7:38 ` Takashi Iwai
2012-06-15 10:02 ` Pierre-Louis Bossart
2012-06-15 10:34 ` Takashi Iwai
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).