ltc-tools
ltc-tools copied to clipboard
jltcntp - incorrect 'received' timestamps
Whist monitoring reference clock LTC stream sent to Chrony I see that there is a very definite structure... even whilst the local clock is supposedly locked.
Looking at the code it looks like it stamps the 'received' message with the time that the SHM data was assembled (tv.tv_sec), when it should use the time that the audio data was captures AND it should use: ltc_off_t off_start the approximate sample in the stream corresponding to the start of the LTC frame.
https://github.com/x42/ltc-tools/blob/master/jltcntp.c#L251
if (!shm->valid)
{
struct timeval tv;
gettimeofday(&tv, NULL);
shm->clockTimeStampSec = time.tv_sec - offset;
shm->clockTimeStampUSec = time.tv_usec;
shm->receiveTimeStampSec = tv.tv_sec;
shm->receiveTimeStampUSec = tv.tv_usec;
shm->valid = 1;
}
This particular tool was contributed by @dimitry-ishenko (https://github.com/x42/ltc-tools/pull/6). I'm afraid that I cannot be of assistance. Perhaps Dimitry can.
Perhaps, or perhaps we can work it out ;-)
It look like the audio samples/buffer is returned from Jack in 'process()' which would be a good place to take a timestamp. https://github.com/x42/ltc-tools/blob/master/jltcntp.c#L94
However I don't (yet) understand how libltc handles multiple/consecutive buffers being pushed with 'ltc_decoder_write_float()'. I think that we want to know which buffer contained the 'LTCFrameExt' so that we can trace back to the timestamp (taken above).
All of my mental capacity is currently tied up in another project, but @mungewell if you don't mind identifying a potential solution, I will try to fit it in next week-ish
@dimitry-ishenko Understood, I am just tinkering as I find this interesting. There is no particular time-frame for a solution.
As solution might lie in 'ltc_off_t posinfo'
void ltc_decoder_write(LTCDecoder *d,
ltcsnd_sample_t *buf, size_t size,
ltc_off_t posinfo);
It appears that this is (just) added to the returned offset to let user compensate for shorter buffers in a larger frame, however for NTP we'd be streaming forever and eventually this number would overflow.
Maybe we could use this to count samples since 'xx:x0:00:00' local-time (or UTC) or something. I will see what i can come up with and submit a patch.
Moving "gettimeofday(&tv, NULL)" into the "process()" function improves the accuracy of the refclocks into Chrony by an order of 2 magnitudes, these are now varying but about +/-150us.
I believe that horizontal bands seen here are due to sampling rate of Jack (48kHz = 20.8us). With 80bit per frame each bit in stream is 416us, so it might be possible to improve/refine the computation of 'off_start' (assuming that the problem is based on coding and NOT on my external hardware/generator).
Note: Drop Frame does not work, burnt a little time before I released I had accidentally changed mode on generator....
I ran a longer test, first with 'LTC' feeding/correcting Chrony and then with 'MON' just being monitored.
===============================================================================
Date (UTC) Time Refid DP L P Raw offset Cooked offset Disp.
===============================================================================
2020-07-21 02:53:29.041315 LTC 12 N 0 -4.131500e-02 -4.131500e-02 5.000e-08
...
2020-07-21 08:43:34.000000 LTC 1 N 0 0.000000e+00 -6.124937e-07 5.000e-08
2020-07-21 08:46:09.600408 MON 12 N 0 3.995920e-01 3.995918e-01 5.000e-08
...
2020-07-21 17:52:39.005668 MON 13 N 0 -5.668000e-03 -5.668007e-03 5.000e-08
The results are promising, precision in tracking appears less than 100us and drift is OK (but can maybe be improved)
Thanks for the support and suggestions from @x42
I added the jack_gettime() and this improved the graph a bit, have to keep working on the 'next' cause.
I did some additional logging whilst changing the buffer size and sample rate.
Increase in either/both of these made an improvement as well. The logging includes per-packet timing data so I'll parse through those to see if there's anything notable.
20200723_rates.zip
@mungewell jack_get_time()
sounds like a good idea. Beyond that the there is drift and jumps associated with drop-frame discontinuity. Now that all of my attention have been diverted to this project, I will look into switching to jack_get_time()
.
Looking at jack_get_time
implementation on Linux it gets its time from clock_gettime
(lacking an HPET):
#define HAVE_CLOCK_GETTIME 1
#ifndef HAVE_CLOCK_GETTIME
static jack_time_t jack_get_microseconds_from_system (void)
{
jack_time_t jackTime;
struct timeval tv;
gettimeofday (&tv, NULL);
jackTime = (jack_time_t) tv.tv_sec * 1000000 + (jack_time_t) tv.tv_usec;
return jackTime;
}
#else
static jack_time_t jack_get_microseconds_from_system (void)
{
jack_time_t jackTime;
struct timespec time;
#ifdef CLOCK_MONOTONIC_RAW
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
#else
clock_gettime(CLOCK_MONOTONIC, &time);
#endif
jackTime = (jack_time_t) time.tv_sec * 1e6 +
(jack_time_t) time.tv_nsec / 1e3;
return jackTime;
}
#endif /* HAVE_CLOCK_GETTIME */
And documentation for clock_gettime(2)
says:
CLOCK_MONOTONIC A nonsettable system-wide clock that represents monotonic time since—as described by POSIX—"some unspecified point in the past". On Linux, that point corresponds to the number of seconds that the system has been running since it was booted.
So, there is no guarantee that jack_get_time()
will return time since epoch... and so cannot be used directly.
I do like your idea of moving gettimeofday()
into the process()
function to get more precise received time.
@x42 @mungewell do you guys know if all drop-frame timecodes follow the same strategy? I.e., drop 2 frames every minute other than every 10th minute.
I know it is the case for 29.97df, but what about eg 59.94df and 23.976df?
Are there any other drop-frame timecodes that should be taken into account?