Boolean adjFreq(clockid_t clkid, int adj) { struct timex tx; int err; if (adj > ADJ_FREQ_MAX) adj = ADJ_FREQ_MAX; else if (adj < -ADJ_FREQ_MAX) adj = -ADJ_FREQ_MAX; memset(&tx, 0, sizeof(tx)); tx.modes = ADJ_FREQUENCY; tx.freq = (long) (adj * 65.536); err = clock_adjtime(clkid, &tx); if (err < 0) { ERROR("failed adjust the PTP clock: %s\n", strerror(errno)); return FALSE; } NOTIFY("adjusted system clock by %d\n", adj); return TRUE; }
/* wake up when the system time changes underneath us */ static int sntp_clock_watch_setup(SNTPContext *sntp) { struct itimerspec its = { .it_value.tv_sec = TIME_T_MAX }; _cleanup_close_ int fd = -1; sd_event *e; sd_event_source *source; int r; assert(sntp); assert(sntp->event_receive); fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC); if (fd < 0) { log_error("Failed to create timerfd: %m"); return -errno; } if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0) { log_error("Failed to set up timerfd: %m"); return -errno; } e = sd_event_source_get_event(sntp->event_receive); r = sd_event_add_io(e, &source, fd, EPOLLIN, sntp_clock_watch, sntp); if (r < 0) { log_error("Failed to create clock watch event source: %s", strerror(-r)); return r; } sd_event_source_unref(sntp->event_clock_watch); sntp->event_clock_watch = source; if (sntp->clock_watch_fd >= 0) close(sntp->clock_watch_fd); sntp->clock_watch_fd = fd; fd = -1; return 0; } static int sntp_adjust_clock(SNTPContext *sntp, double offset, int leap_sec) { struct timex tmx = {}; int r; /* * For small deltas, tell the kernel to gradually adjust the system * clock to the NTP time, larger deltas are just directly set. * * Clear STA_UNSYNC, it will enable the kernel's 11-minute mode, which * syncs the system time periodically to the hardware clock. */ if (offset < NTP_MAX_ADJUST && offset > -NTP_MAX_ADJUST) { tmx.modes |= ADJ_STATUS | ADJ_OFFSET | ADJ_TIMECONST | ADJ_MAXERROR | ADJ_ESTERROR; tmx.status = STA_PLL; tmx.offset = offset * 1000 * 1000; tmx.constant = log2i(sntp->poll_interval_usec / USEC_PER_SEC) - 6; tmx.maxerror = 0; tmx.esterror = 0; log_debug(" adjust (slew): %+f sec\n", (double)tmx.offset / USEC_PER_SEC); } else { tmx.modes = ADJ_SETOFFSET; d_to_tv(offset, &tmx.time); sntp->jumped = true; log_debug(" adjust (jump): %+f sec\n", tv_to_d(&tmx.time)); } switch (leap_sec) { case 1: tmx.status |= STA_INS; break; case -1: tmx.status |= STA_DEL; break; } r = clock_adjtime(CLOCK_REALTIME, &tmx); if (r < 0) return r; log_debug(" status : %04i %s\n" " time now : %li.%06li\n" " constant : %li\n" " offset : %+f sec\n" " freq offset : %+li (%+.3f ppm)\n", tmx.status, tmx.status & STA_UNSYNC ? "" : "sync", tmx.time.tv_sec, tmx.time.tv_usec, tmx.constant, (double)tmx.offset / USEC_PER_SEC, tmx.freq, (double)tmx.freq / 65536); return 0; } static bool sntp_sample_spike_detection(SNTPContext *sntp, double offset, double delay) { unsigned int i, idx_cur, idx_new, idx_min; double jitter; double j; /* store the current data in our samples array */ idx_cur = sntp->samples_idx; idx_new = (idx_cur + 1) % ELEMENTSOF(sntp->samples); sntp->samples_idx = idx_new; sntp->samples[idx_new].offset = offset; sntp->samples[idx_new].delay = delay; sntp->packet_count++; jitter = sntp->samples_jitter; /* calculate new jitter value from the RMS differences relative to the lowest delay sample */ for (idx_min = idx_cur, i = 0; i < ELEMENTSOF(sntp->samples); i++) if (sntp->samples[i].delay > 0 && sntp->samples[i].delay < sntp->samples[idx_min].delay) idx_min = i; j = 0; for (i = 0; i < ELEMENTSOF(sntp->samples); i++) j += square(sntp->samples[i].offset - sntp->samples[idx_min].offset); sntp->samples_jitter = sqrt(j / (ELEMENTSOF(sntp->samples) - 1)); /* ignore samples when resyncing */ if (sntp->poll_resync) return false; /* always accept offset if we are farther off than the round-trip delay */ if (fabs(offset) > delay) return false; /* we need a few samples before looking at them */ if (sntp->packet_count < 4) return false; /* do not accept anything worse than the maximum possible error of the best sample */ if (fabs(offset) > sntp->samples[idx_min].delay) return true; /* compare the difference between the current offset to the previous offset and jitter */ return fabs(offset - sntp->samples[idx_cur].offset) > 3 * jitter; }
int main(int argc, char *argv[]) { struct ptp_clock_caps caps; struct ptp_extts_event event; struct ptp_extts_request extts_request; struct ptp_perout_request perout_request; struct timespec ts; struct timex tx; static timer_t timerid; struct itimerspec timeout; struct sigevent sigevent; char *progname; int c, cnt, fd; char *device = DEVICE; clockid_t clkid; int adjfreq = 0x7fffffff; int adjtime = 0; int capabilities = 0; int extts = 0; int gettime = 0; int oneshot = 0; int periodic = 0; int perout = -1; int pps = -1; int settime = 0; progname = strrchr(argv[0], '/'); progname = progname ? 1+progname : argv[0]; while (EOF != (c = getopt(argc, argv, "a:A:cd:e:f:ghp:P:st:v"))) { switch (c) { case 'a': oneshot = atoi(optarg); break; case 'A': periodic = atoi(optarg); break; case 'c': capabilities = 1; break; case 'd': device = optarg; break; case 'e': extts = atoi(optarg); break; case 'f': adjfreq = atoi(optarg); break; case 'g': gettime = 1; break; case 'p': perout = atoi(optarg); break; case 'P': pps = atoi(optarg); break; case 's': settime = 1; break; case 't': adjtime = atoi(optarg); break; case 'h': usage(progname); return 0; case '?': default: usage(progname); return -1; } } fd = open(device, O_RDWR); if (fd < 0) { fprintf(stderr, "opening %s: %s\n", device, strerror(errno)); return -1; } clkid = get_clockid(fd); if (CLOCK_INVALID == clkid) { fprintf(stderr, "failed to read clock id\n"); return -1; } if (capabilities) { if (ioctl(fd, PTP_CLOCK_GETCAPS, &caps)) { perror("PTP_CLOCK_GETCAPS"); } else { printf("capabilities:\n" " %d maximum frequency adjustment (ppb)\n" " %d programmable alarms\n" " %d external time stamp channels\n" " %d programmable periodic signals\n" " %d pulse per second\n", caps.max_adj, caps.n_alarm, caps.n_ext_ts, caps.n_per_out, caps.pps); } } if (0x7fffffff != adjfreq) { memset(&tx, 0, sizeof(tx)); tx.modes = ADJ_FREQUENCY; tx.freq = ppb_to_scaled_ppm(adjfreq); if (clock_adjtime(clkid, &tx)) { perror("clock_adjtime"); } else { puts("frequency adjustment okay"); } } if (adjtime) { memset(&tx, 0, sizeof(tx)); tx.modes = ADJ_SETOFFSET; tx.time.tv_sec = adjtime; tx.time.tv_usec = 0; if (clock_adjtime(clkid, &tx) < 0) { perror("clock_adjtime"); } else { puts("time shift okay"); } } if (gettime) { if (clock_gettime(clkid, &ts)) { perror("clock_gettime"); } else { printf("clock time: %ld.%09ld or %s", ts.tv_sec, ts.tv_nsec, ctime(&ts.tv_sec)); } } if (settime) { clock_gettime(CLOCK_REALTIME, &ts); if (clock_settime(clkid, &ts)) { perror("clock_settime"); } else { puts("set time okay"); } } if (extts) { memset(&extts_request, 0, sizeof(extts_request)); extts_request.index = 0; extts_request.flags = PTP_ENABLE_FEATURE; if (ioctl(fd, PTP_EXTTS_REQUEST, &extts_request)) { perror("PTP_EXTTS_REQUEST"); extts = 0; } else { puts("external time stamp request okay"); } for (; extts; extts--) { cnt = read(fd, &event, sizeof(event)); if (cnt != sizeof(event)) { perror("read"); break; } printf("event index %u at %lld.%09u\n", event.index, event.t.sec, event.t.nsec); fflush(stdout); } /* Disable the feature again. */ extts_request.flags = 0; if (ioctl(fd, PTP_EXTTS_REQUEST, &extts_request)) { perror("PTP_EXTTS_REQUEST"); } } if (oneshot) { install_handler(SIGALRM, handle_alarm); /* Create a timer. */ sigevent.sigev_notify = SIGEV_SIGNAL; sigevent.sigev_signo = SIGALRM; if (timer_create(clkid, &sigevent, &timerid)) { perror("timer_create"); return -1; } /* Start the timer. */ memset(&timeout, 0, sizeof(timeout)); timeout.it_value.tv_sec = oneshot; if (timer_settime(timerid, 0, &timeout, NULL)) { perror("timer_settime"); return -1; } pause(); timer_delete(timerid); } if (periodic) { install_handler(SIGALRM, handle_alarm); /* Create a timer. */ sigevent.sigev_notify = SIGEV_SIGNAL; sigevent.sigev_signo = SIGALRM; if (timer_create(clkid, &sigevent, &timerid)) { perror("timer_create"); return -1; } /* Start the timer. */ memset(&timeout, 0, sizeof(timeout)); timeout.it_interval.tv_sec = periodic; timeout.it_value.tv_sec = periodic; if (timer_settime(timerid, 0, &timeout, NULL)) { perror("timer_settime"); return -1; } while (1) { pause(); } timer_delete(timerid); } if (perout >= 0) { if (clock_gettime(clkid, &ts)) { perror("clock_gettime"); return -1; } memset(&perout_request, 0, sizeof(perout_request)); perout_request.index = 0; perout_request.start.sec = ts.tv_sec + 2; perout_request.start.nsec = 0; perout_request.period.sec = 0; perout_request.period.nsec = perout; if (ioctl(fd, PTP_PEROUT_REQUEST, &perout_request)) { perror("PTP_PEROUT_REQUEST"); } else { puts("periodic output request okay"); } } if (pps != -1) { int enable = pps ? 1 : 0; if (ioctl(fd, PTP_ENABLE_PPS, enable)) { perror("PTP_ENABLE_PPS"); } else { puts("pps for system time request okay"); } } close(fd); return 0; }
int main(int argc, char *argv[]) { struct ptp_clock_caps caps; struct ptp_extts_event event; struct ptp_extts_request extts_request; struct ptp_perout_request perout_request; struct ptp_pin_desc desc; struct timespec ts; struct timex tx; static timer_t timerid; struct itimerspec timeout; struct sigevent sigevent; struct ptp_clock_time *pct; struct ptp_sys_offset *sysoff; char *progname; unsigned int i; int c, cnt, fd; char *device = DEVICE; clockid_t clkid; int adjfreq = 0x7fffffff; int adjtime = 0; int capabilities = 0; int extts = 0; int gettime = 0; int index = 0; int list_pins = 0; int oneshot = 0; int pct_offset = 0; int n_samples = 0; int periodic = 0; int perout = -1; int pin_index = -1, pin_func; int pps = -1; int seconds = 0; int settime = 0; int64_t t1, t2, tp; int64_t interval, offset; progname = strrchr(argv[0], '/'); progname = progname ? 1+progname : argv[0]; while (EOF != (c = getopt(argc, argv, "a:A:cd:e:f:ghi:k:lL:p:P:sSt:T:v"))) { switch (c) { case 'a': oneshot = atoi(optarg); break; case 'A': periodic = atoi(optarg); break; case 'c': capabilities = 1; break; case 'd': device = optarg; break; case 'e': extts = atoi(optarg); break; case 'f': adjfreq = atoi(optarg); break; case 'g': gettime = 1; break; case 'i': index = atoi(optarg); break; case 'k': pct_offset = 1; n_samples = atoi(optarg); break; case 'l': list_pins = 1; break; case 'L': cnt = sscanf(optarg, "%d,%d", &pin_index, &pin_func); if (cnt != 2) { usage(progname); return -1; } break; case 'p': perout = atoi(optarg); break; case 'P': pps = atoi(optarg); break; case 's': settime = 1; break; case 'S': settime = 2; break; case 't': adjtime = atoi(optarg); break; case 'T': settime = 3; seconds = atoi(optarg); break; case 'h': usage(progname); return 0; case '?': default: usage(progname); return -1; } } fd = open(device, O_RDWR); if (fd < 0) { fprintf(stderr, "opening %s: %s\n", device, strerror(errno)); return -1; } clkid = get_clockid(fd); if (CLOCK_INVALID == clkid) { fprintf(stderr, "failed to read clock id\n"); return -1; } if (capabilities) { if (ioctl(fd, PTP_CLOCK_GETCAPS, &caps)) { perror("PTP_CLOCK_GETCAPS"); } else { printf("capabilities:\n" " %d maximum frequency adjustment (ppb)\n" " %d programmable alarms\n" " %d external time stamp channels\n" " %d programmable periodic signals\n" " %d pulse per second\n" " %d programmable pins\n" " %d cross timestamping\n", caps.max_adj, caps.n_alarm, caps.n_ext_ts, caps.n_per_out, caps.pps, caps.n_pins, caps.cross_timestamping); } } if (0x7fffffff != adjfreq) { memset(&tx, 0, sizeof(tx)); tx.modes = ADJ_FREQUENCY; tx.freq = ppb_to_scaled_ppm(adjfreq); if (clock_adjtime(clkid, &tx)) { perror("clock_adjtime"); } else { puts("frequency adjustment okay"); } } if (adjtime) { memset(&tx, 0, sizeof(tx)); tx.modes = ADJ_SETOFFSET; tx.time.tv_sec = adjtime; tx.time.tv_usec = 0; if (clock_adjtime(clkid, &tx) < 0) { perror("clock_adjtime"); } else { puts("time shift okay"); } } if (gettime) { if (clock_gettime(clkid, &ts)) { perror("clock_gettime"); } else { printf("clock time: %ld.%09ld or %s", ts.tv_sec, ts.tv_nsec, ctime(&ts.tv_sec)); } } if (settime == 1) { clock_gettime(CLOCK_REALTIME, &ts); if (clock_settime(clkid, &ts)) { perror("clock_settime"); } else { puts("set time okay"); } } if (settime == 2) { clock_gettime(clkid, &ts); if (clock_settime(CLOCK_REALTIME, &ts)) { perror("clock_settime"); } else { puts("set time okay"); } } if (settime == 3) { ts.tv_sec = seconds; ts.tv_nsec = 0; if (clock_settime(clkid, &ts)) { perror("clock_settime"); } else { puts("set time okay"); } } if (extts) { memset(&extts_request, 0, sizeof(extts_request)); extts_request.index = index; extts_request.flags = PTP_ENABLE_FEATURE; if (ioctl(fd, PTP_EXTTS_REQUEST, &extts_request)) { perror("PTP_EXTTS_REQUEST"); extts = 0; } else { puts("external time stamp request okay"); } for (; extts; extts--) { cnt = read(fd, &event, sizeof(event)); if (cnt != sizeof(event)) { perror("read"); break; } printf("event index %u at %lld.%09u\n", event.index, event.t.sec, event.t.nsec); fflush(stdout); } /* Disable the feature again. */ extts_request.flags = 0; if (ioctl(fd, PTP_EXTTS_REQUEST, &extts_request)) { perror("PTP_EXTTS_REQUEST"); } } if (list_pins) { int n_pins = 0; if (ioctl(fd, PTP_CLOCK_GETCAPS, &caps)) { perror("PTP_CLOCK_GETCAPS"); } else { n_pins = caps.n_pins; } for (i = 0; i < n_pins; i++) { desc.index = i; if (ioctl(fd, PTP_PIN_GETFUNC, &desc)) { perror("PTP_PIN_GETFUNC"); break; } printf("name %s index %u func %u chan %u\n", desc.name, desc.index, desc.func, desc.chan); } } if (oneshot) { install_handler(SIGALRM, handle_alarm); /* Create a timer. */ sigevent.sigev_notify = SIGEV_SIGNAL; sigevent.sigev_signo = SIGALRM; if (timer_create(clkid, &sigevent, &timerid)) { perror("timer_create"); return -1; } /* Start the timer. */ memset(&timeout, 0, sizeof(timeout)); timeout.it_value.tv_sec = oneshot; if (timer_settime(timerid, 0, &timeout, NULL)) { perror("timer_settime"); return -1; } pause(); timer_delete(timerid); } if (periodic) { install_handler(SIGALRM, handle_alarm); /* Create a timer. */ sigevent.sigev_notify = SIGEV_SIGNAL; sigevent.sigev_signo = SIGALRM; if (timer_create(clkid, &sigevent, &timerid)) { perror("timer_create"); return -1; } /* Start the timer. */ memset(&timeout, 0, sizeof(timeout)); timeout.it_interval.tv_sec = periodic; timeout.it_value.tv_sec = periodic; if (timer_settime(timerid, 0, &timeout, NULL)) { perror("timer_settime"); return -1; } while (1) { pause(); } timer_delete(timerid); } if (perout >= 0) { if (clock_gettime(clkid, &ts)) { perror("clock_gettime"); return -1; } memset(&perout_request, 0, sizeof(perout_request)); perout_request.index = index; perout_request.start.sec = ts.tv_sec + 2; perout_request.start.nsec = 0; perout_request.period.sec = 0; perout_request.period.nsec = perout; if (ioctl(fd, PTP_PEROUT_REQUEST, &perout_request)) { perror("PTP_PEROUT_REQUEST"); } else { puts("periodic output request okay"); } } if (pin_index >= 0) { memset(&desc, 0, sizeof(desc)); desc.index = pin_index; desc.func = pin_func; desc.chan = index; if (ioctl(fd, PTP_PIN_SETFUNC, &desc)) { perror("PTP_PIN_SETFUNC"); } else { puts("set pin function okay"); } } if (pps != -1) { int enable = pps ? 1 : 0; if (ioctl(fd, PTP_ENABLE_PPS, enable)) { perror("PTP_ENABLE_PPS"); } else { puts("pps for system time request okay"); } } if (pct_offset) { if (n_samples <= 0 || n_samples > 25) { puts("n_samples should be between 1 and 25"); usage(progname); return -1; } sysoff = calloc(1, sizeof(*sysoff)); if (!sysoff) { perror("calloc"); return -1; } sysoff->n_samples = n_samples; if (ioctl(fd, PTP_SYS_OFFSET, sysoff)) perror("PTP_SYS_OFFSET"); else puts("system and phc clock time offset request okay"); pct = &sysoff->ts[0]; for (i = 0; i < sysoff->n_samples; i++) { t1 = pctns(pct+2*i); tp = pctns(pct+2*i+1); t2 = pctns(pct+2*i+2); interval = t2 - t1; offset = (t2 + t1) / 2 - tp; printf("system time: %lld.%u\n", (pct+2*i)->sec, (pct+2*i)->nsec); printf("phc time: %lld.%u\n", (pct+2*i+1)->sec, (pct+2*i+1)->nsec); printf("system time: %lld.%u\n", (pct+2*i+2)->sec, (pct+2*i+2)->nsec); printf("system/phc clock time offset is %" PRId64 " ns\n" "system clock time delay is %" PRId64 " ns\n", offset, interval); } free(sysoff); } close(fd); return 0; }
/* wake up when the system time changes underneath us */ static int manager_clock_watch_setup(Manager *m) { struct itimerspec its = { .it_value.tv_sec = TIME_T_MAX }; int r; assert(m); m->event_clock_watch = sd_event_source_unref(m->event_clock_watch); safe_close(m->clock_watch_fd); m->clock_watch_fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC); if (m->clock_watch_fd < 0) { log_error("Failed to create timerfd: %m"); return -errno; } if (timerfd_settime(m->clock_watch_fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0) { log_error("Failed to set up timerfd: %m"); return -errno; } r = sd_event_add_io(m->event, &m->event_clock_watch, m->clock_watch_fd, EPOLLIN, manager_clock_watch, m); if (r < 0) { log_error("Failed to create clock watch event source: %s", strerror(-r)); return r; } return 0; } static int manager_adjust_clock(Manager *m, double offset, int leap_sec) { struct timex tmx = {}; int r; assert(m); /* * For small deltas, tell the kernel to gradually adjust the system * clock to the NTP time, larger deltas are just directly set. */ if (fabs(offset) < NTP_MAX_ADJUST) { tmx.modes = ADJ_STATUS | ADJ_NANO | ADJ_OFFSET | ADJ_TIMECONST | ADJ_MAXERROR | ADJ_ESTERROR; tmx.status = STA_PLL; tmx.offset = offset * NSEC_PER_SEC; tmx.constant = log2i(m->poll_interval_usec / USEC_PER_SEC) - 4; tmx.maxerror = 0; tmx.esterror = 0; log_debug(" adjust (slew): %+.3f sec\n", offset); } else { tmx.modes = ADJ_STATUS | ADJ_NANO | ADJ_SETOFFSET; /* ADJ_NANO uses nanoseconds in the microseconds field */ tmx.time.tv_sec = (long)offset; tmx.time.tv_usec = (offset - tmx.time.tv_sec) * NSEC_PER_SEC; /* the kernel expects -0.3s as {-1, 7000.000.000} */ if (tmx.time.tv_usec < 0) { tmx.time.tv_sec -= 1; tmx.time.tv_usec += NSEC_PER_SEC; } m->jumped = true; log_debug(" adjust (jump): %+.3f sec\n", offset); } /* * An unset STA_UNSYNC will enable the kernel's 11-minute mode, * which syncs the system time periodically to the RTC. * * In case the RTC runs in local time, never touch the RTC, * we have no way to properly handle daylight saving changes and * mobile devices moving between time zones. */ if (m->rtc_local_time) tmx.status |= STA_UNSYNC; switch (leap_sec) { case 1: tmx.status |= STA_INS; break; case -1: tmx.status |= STA_DEL; break; } r = clock_adjtime(CLOCK_REALTIME, &tmx); if (r < 0) return r; touch("/var/lib/systemd/clock"); m->drift_ppm = tmx.freq / 65536; log_debug(" status : %04i %s\n" " time now : %li.%03llu\n" " constant : %li\n" " offset : %+.3f sec\n" " freq offset : %+li (%i ppm)\n", tmx.status, tmx.status & STA_UNSYNC ? "unsync" : "sync", tmx.time.tv_sec, (unsigned long long) (tmx.time.tv_usec / NSEC_PER_MSEC), tmx.constant, (double)tmx.offset / NSEC_PER_SEC, tmx.freq, m->drift_ppm); return 0; } static bool manager_sample_spike_detection(Manager *m, double offset, double delay) { unsigned int i, idx_cur, idx_new, idx_min; double jitter; double j; assert(m); m->packet_count++; /* ignore initial sample */ if (m->packet_count == 1) return false; /* store the current data in our samples array */ idx_cur = m->samples_idx; idx_new = (idx_cur + 1) % ELEMENTSOF(m->samples); m->samples_idx = idx_new; m->samples[idx_new].offset = offset; m->samples[idx_new].delay = delay; /* calculate new jitter value from the RMS differences relative to the lowest delay sample */ jitter = m->samples_jitter; for (idx_min = idx_cur, i = 0; i < ELEMENTSOF(m->samples); i++) if (m->samples[i].delay > 0 && m->samples[i].delay < m->samples[idx_min].delay) idx_min = i; j = 0; for (i = 0; i < ELEMENTSOF(m->samples); i++) j += square(m->samples[i].offset - m->samples[idx_min].offset); m->samples_jitter = sqrt(j / (ELEMENTSOF(m->samples) - 1)); /* ignore samples when resyncing */ if (m->poll_resync) return false; /* always accept offset if we are farther off than the round-trip delay */ if (fabs(offset) > delay) return false; /* we need a few samples before looking at them */ if (m->packet_count < 4) return false; /* do not accept anything worse than the maximum possible error of the best sample */ if (fabs(offset) > m->samples[idx_min].delay) return true; /* compare the difference between the current offset to the previous offset and jitter */ return fabs(offset - m->samples[idx_cur].offset) > 3 * jitter; }