Skip to content

Timestamp Clock for Camera Testing

A large millisecond clock in your local time, UTC or any time zone, timed to your screen’s refresh, with a time code scripts can read. Film it to check camera and ADAS logger timestamps, sync several cameras or find dropped frames.

Turn your phone sideways and tap Fullscreen for the largest target.

DISPLAY — Hz · — ms · DROPPED 0
—

Display & timing
measuring

Local / UTC time lets you match photos to camera timestamps or sync several cameras. Accuracy depends on your system clock (NTP).

Dropped frames · last 30 s

0 dropped

In short

To check a camera’s timestamps, film this clock and compare the time shown in each recorded frame with the timestamp the camera or logger gave that frame: camera timestamp − screen clock = how late the timestamp is. The clock shows the moment each frame is on screen, exact to one refresh (16.7 ms at 60 Hz, 8.3 ms at 120 Hz), and checks this device’s clock against UTC. A row of black and white cells encodes the same time and the frame number (CTK-TIME v1) so a script can read every frame of a long recording.

How to Check Camera Timestamps

  1. Sync this computer’s clock, then open the clock and go fullscreen. The panel says how far the clock is from UTC.
  2. Point the camera under test at the screen so the digits and the time code fill most of the frame width. Use an exposure shorter than one refresh if you can.
  3. Record the way you normally log: camera software, data logger or the vehicle’s recording system.
  4. Pick a frame, read the clock in it (or decode the time code) and note the timestamp your system stored for that frame.
  5. Subtract: camera timestamp − screen clock. The calculator beside the clock does it and wraps across minute boundaries.

Example: the frame shows 14:32:07.483 and the logger stamped it 14:32:07.521, so the timestamp is 38 ms late. That figure contains the camera’s exposure and readout, transfer and logging delay, and your monitor’s own input lag, plus or minus one refresh. Repeat with several frames: a constant result is a fixed offset you can correct, a drifting one means the camera’s clock is not synced.

What the Clock Shows, Exactly

A screen can only change once per refresh, so a clock on it moves in steps. CamTestKit draws each frame with the time that frame is expected on screen, not the moment the code ran, and holds it for the whole refresh, so every camera frame catches one complete reading.

DisplayClock step
60 Hz16.7 ms
120 Hz8.3 ms
144 Hz6.9 ms
240 Hz4.2 ms

Three things sit between the clock and true time. The step above. Your monitor’s input lag, typically 5–30 ms, which a browser cannot measure; it is constant, so compare cameras on the same monitor. And this computer’s clock: the page checks it against Cloudflare’s time and shows the difference with its uncertainty. If you cannot sync the computer, turn on Correct to UTC to shift the clock by the measured offset.

Syncing Several Cameras

Point every camera at the same screen, record together, then compare the clock each camera recorded at the same moment. With a surround-view or stereo rig, the difference between cameras is their sync error; it does not depend on your monitor’s lag, because every camera sees the same screen.

Choose Three clocks to place copies at the top, middle and bottom of the screen. A rolling-shutter camera reads its rows one after another, so the lower clocks can show a later time than the upper ones in the same frame; the difference shows how long the camera takes to read out.

Finding Dropped and Duplicated Frames

Step through a recording frame by frame. At 30 fps on a 60 Hz screen the clock should advance by about 33 ms and the frame number by 2 every frame. A jump of twice that is a dropped frame; the same reading in two frames is a duplicate. The computer can miss a refresh too, which also repeats a reading, so check the panel’s dropped-frame log for that moment before blaming the camera.

The CTK-TIME Time Code

The row of cells under the digits is a fixed format, so a script can read thousands of frames without OCR. It is one row of 68 equal square cells between two white bars that span exactly the row’s width; white is 1, black is 0, read left to right.

CellsContent
0–1Start guard: 1 0
2–45UTC time in epoch milliseconds, 44 bits, most significant first (enough until the year 2527)
46–57Frame number modulo 4096, Gray-coded, so consecutive frames differ in one cell
58–65CRC-8 checksum (polynomial 0x07, initial 0) of the 7 bytes holding cells 2–57
66–67End guard: 0 1

If the exposure spans two refreshes, the frame shows two codes on top of each other. The checksum then fails and the frame is rejected rather than misread; take the time from the neighbouring frames. This decoder needs only Python and NumPy:

import numpy as np
from datetime import datetime, timezone

def crc8(data: bytes) -> int:
    c = 0
    for b in data:
        c ^= b
        for _ in range(8):
            c = ((c << 1) ^ 0x07) & 0xFF if c & 0x80 else (c << 1) & 0xFF
    return c

def read_cells(gray, x0, x1, y):
    """Sample the 68 cells along row y of a grayscale frame.
    x0, x1: left and right ends of the white bars."""
    w = (x1 - x0) / 68
    v = np.array([gray[int(y), int(x0 + (i + 0.5) * w)] for i in range(68)], float)
    return [int(x > (v.min() + v.max()) / 2) for x in v]

def decode(cells):
    """Return (UTC datetime, frame number mod 4096), or None for a bad frame."""
    if len(cells) != 68 or cells[:2] != [1, 0] or cells[-2:] != [0, 1]:
        return None
    payload = int("".join(map(str, cells[2:58])), 2)
    if crc8(payload.to_bytes(7, "big")) != int("".join(map(str, cells[58:66])), 2):
        return None  # two codes blended, or a misread
    ms, g = payload >> 12, payload & 0xFFF
    frame = g
    while g := g >> 1:  # Gray code to binary
        frame ^= g
    return datetime.fromtimestamp(ms / 1000, timezone.utc), frame

The code always carries UTC, even when the digits show local time. With Correct to UTC on, it carries the corrected time.

Timestamp Clock FAQ

How accurate is an on-screen millisecond clock?

Each reading is exact to one screen refresh: 16.7 ms at 60 Hz, 8.3 ms at 120 Hz. On top of that come the monitor’s input lag, usually 5–30 ms and constant for a given monitor, and the computer’s clock error, which this page measures against UTC and shows with its uncertainty. For absolute accuracy below a few milliseconds, use a GPS-disciplined LED clock.

Can I use it to test ADAS camera timestamps?

Yes. Film the clock with the vehicle camera while the logger records, then compare the clock in each frame with the frame’s logged timestamp. The difference is the timestamp delay plus the monitor’s input lag, within one refresh. Comparing several cameras against the same screen gives their sync error without the monitor’s lag.

Why does the last digit skip numbers?

The screen changes only once per refresh, so at 60 Hz the clock advances in steps of about 16.7 ms. Each reading is the exact time of its refresh; the digits in between are never on screen.

Why does my recording show two times on top of each other?

The exposure lasted longer than one refresh, so the camera saw two readings. Shorten the exposure, add light, or read the time code: its checksum rejects blended frames, so a script can skip them and use the neighbouring frames.

Should the clock show UTC or local time?

Use UTC to compare with GPS or PTP time, or with any logger that records UTC. Local time, the default, is easier to read for a quick check. Time shown also lists every time zone, for a logger set to the time of another region; daylight saving time is applied automatically. The time code always carries UTC, whichever the digits show.