ltc-tools icon indicating copy to clipboard operation
ltc-tools copied to clipboard

jltcntp - incorrect 'received' timestamps

Open mungewell opened this issue 4 years ago • 10 comments

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. before

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;
            }

mungewell avatar Jul 18 '20 23:07 mungewell

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.

x42 avatar Jul 19 '20 05:07 x42

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).

mungewell avatar Jul 19 '20 17:07 mungewell

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 avatar Jul 19 '20 18:07 dimitry-ishenko

@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.

mungewell avatar Jul 19 '20 18:07 mungewell

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. better3

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....

mungewell avatar Jul 21 '20 00:07 mungewell

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

20200721_chrony.tar.gz

The results are promising, precision in tracking appears less than 100us and drift is OK (but can maybe be improved) ltc_precision mon_drift

mungewell avatar Jul 21 '20 18:07 mungewell

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. with_j_time_t

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. rates 20200723_rates.zip

mungewell avatar Jul 23 '20 23:07 mungewell

@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().

dimitry-ishenko avatar Dec 20 '20 15:12 dimitry-ishenko

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.

dimitry-ishenko avatar Dec 20 '20 16:12 dimitry-ishenko

@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?

dimitry-ishenko avatar Dec 20 '20 20:12 dimitry-ishenko