The 33-Bit PTS Rollover at Hour 26.5: Captions, Sync, and the Wrap Nobody Tests

At 90 kHz, a 33-bit PTS counter wraps every 233 / 90,000 seconds. That is 95,443.7 seconds, or 26 hours, 30 minutes, 43.7 seconds. Most webcast reliability work happens in the first hour: encoder failover, CDN failover, caption reconnect. The wrap at hour 26.5 is rarely in the test plan, and it is where long-running contribution feeds, caption sidecars, and origin packagers quietly disagree about what time it is.

This article is about the mechanics of that wrap, the places it breaks, and the packet-level evidence that tells you which layer broke. It is not about a specific vendor. The behavior described here follows from the MPEG-2 systems timestamp field width, the RTP timestamp field width, and the way caption formats carry their own clocks.

What the 33-bit PTS actually is

MPEG-2 systems defines the presentation timestamp as a 33-bit value carried in the PES header, clocked at 90 kHz. The field is split across the PTS/DTS marker bits in the PES header, but the arithmetic is a plain 33-bit unsigned counter. The same 90 kHz clock drives the PCR in the transport stream adaptation field, which is a 42-bit value: 33 bits of 90 kHz base plus 9 bits of 27 MHz extension.

Two consequences follow directly from the field widths:

  • The PTS wraps at 233 = 8,589,934,592 ticks, which is 26:30:43.717 at 90 kHz.
  • The PCR base wraps at the same 33-bit boundary, but the PCR extension continues to count 27 MHz cycles, so the PCR as a whole wraps at 242 / 27,000,000 seconds, which is roughly 45.8 hours. The base and the extension do not wrap together.

That second point matters. A receiver that reconstructs time from PCR base alone will see a wrap at 26.5 hours. A receiver that reconstructs from the full 42-bit PCR will see a wrap at about 45.8 hours. If your packager and your caption engine use different reconstructions, they will disagree for a window in the middle of a long event.

Why hour 26.5 is the wrong number to test

If you test the wrap by running a 27-hour soak, you are testing one specific alignment: PTS starting near zero at t=0. In production, the PTS at the start of a contribution feed is whatever the encoder’s clock happened to be. A feed that starts at PTS 0x7FFF0000 will wrap within minutes. A feed that starts at PTS 0x00010000 will wrap after 26.5 hours.

The correct test is not a duration. It is a PTS offset. You want to start a feed with the encoder’s PTS initialized near 233 minus a few seconds, so the wrap happens inside a short test window. Most broadcast encoders expose this as a PTS offset or a “timecode start” setting. If yours does not, you can inject the condition at the packager by rewriting PTS values in the PES headers with a tool like tsp or a custom ffmpeg bitstream filter, but the cleanest test is at the encoder.

Where the wrap breaks: three failure classes

1. Caption timestamps that do not wrap

CEA-608 and CEA-708 captions are carried inside the video user data or as a separate caption service in the transport stream. The caption payload itself does not carry a 33-bit PTS. It carries a caption timing reference that is interpreted relative to the video PTS at the point of insertion. The caption engine, however, usually maintains its own internal clock, often derived from wall clock or from a monotonic counter.

When the video PTS wraps, the caption engine’s internal clock does not. If the caption engine computes caption display time as video_pts + caption_offset using a 64-bit accumulator, it will produce a caption time that is 233 ticks ahead of the video. The result is captions that appear roughly 26.5 hours late, or that are dropped entirely because the computed time is outside the player’s window.

WebVTT is a different case. WebVTT cues carry absolute timestamps in the form HH:MM:SS.mmm, as defined in the W3C WebVTT specification. There is no 33-bit field and no 90 kHz clock. A WebVTT file generated from a wrapped PTS will contain timestamps that jump backward by 26.5 hours at the wrap point. Players that seek within the file will handle this inconsistently: some will treat the backward jump as a discontinuity, some will clamp, and some will simply fail to display cues after the wrap.

The fix is to normalize caption timestamps to a monotonic timeline before they reach the player. That means the caption engine must be told the PTS wrap point, or must derive it from the video stream, and must add 233 to its accumulator at the wrap. This is a one-line change in most caption engines, but it requires that the engine know the wrap happened.

2. A/V sync drift across the wrap

Audio and video are separate elementary streams with separate PTS values. If both wrap at the same instant, the relative offset is preserved and sync is fine. If they wrap at different instants, or if one stream’s PTS is rewritten by a packager while the other is not, the offset changes by 233 ticks, which is 26.5 hours of audio-video offset. The player will either drop one stream or produce a permanent lip-sync error.

In practice, audio and video PTS wrap at the same instant only if they share a common clock and were initialized together. In a contribution encoder, they usually do. In a packager that remuxes from a contribution feed into HLS or DASH segments, the packager may rewrite PTS to start at zero for each segment. If the packager rewrites video PTS but not audio PTS, or vice versa, the wrap point differs between the two streams.

The diagnostic is straightforward. Extract PTS values from both elementary streams over a window that includes the wrap and plot the difference. A constant difference means sync is preserved. A step change of 233 ticks means one stream wrapped and the other did not.

3. Segment boundary and discontinuity handling

HLS and DASH both have explicit discontinuity signaling. RFC 8216 defines EXT-X-DISCONTINUITY for HLS, and the DASH specification defines a similar mechanism. A PTS wrap is a discontinuity in the timestamp domain, but it is not necessarily a discontinuity in the media. If the packager does not signal it, the player may attempt to interpolate across the wrap and produce a visible glitch or a stall.

The correct behavior is to signal a discontinuity at the segment boundary that contains the wrap, and to ensure that the segment’s internal timestamps are consistent. RFC 8216 states that each media segment must carry the continuation of the encoded bitstream from the previous segment, with timestamps and continuity counters continuing uninterrupted, except for segments explicitly signaled as discontinuities. A PTS wrap is exactly the case that requires the discontinuity tag.

If you are using fMP4 segments, the tfdt box carries the base media decode time. A wrap in the PTS domain must be reflected in the tfdt values, and the player must be told that the timeline is discontinuous. If the tfdt values simply continue past 233 while the PTS values wrap, the player will see a mismatch between the container timeline and the elementary stream timeline.

What the transport protocols do

RTP, as defined in RFC 3550, uses a 32-bit timestamp field with a media-specific clock rate. For video at 90 kHz, the RTP timestamp wraps every 232 / 90,000 seconds, which is about 13.25 hours. That is a different wrap point from the 33-bit PTS. If you are carrying MPEG-TS over RTP, you have two independent wrap points: the 33-bit PTS inside the TS payload and the 32-bit RTP timestamp in the RTP header.

RFC 3550 does not define a rollover mechanism for the RTP timestamp. The timestamp is intended to be used for relative timing within a session, and receivers are expected to handle wraparound by comparing timestamps modulo 232. The specification’s guidance on timestamp arithmetic is that receivers should use modular arithmetic and should not assume monotonicity across the entire session.

SRT and RIST carry timestamps in their own headers. SRT uses a 32-bit timestamp in the SRT data packet header, with a configurable clock rate. RIST uses a similar approach. Neither protocol defines a 33-bit PTS rollover, because neither carries MPEG-TS PTS directly. The PTS rollover is a property of the MPEG-TS payload, not of the transport.

WebRTC is a different case. WebRTC uses RTP timestamps, and the RTP timestamp is derived from the media clock. For video, the clock rate is typically 90 kHz, so the RTP timestamp wraps at about 13.25 hours. WebRTC receivers handle this with modular arithmetic. The 33-bit PTS rollover is not visible to WebRTC unless the WebRTC endpoint is carrying MPEG-TS, which is unusual.

QUIC, as defined in RFC 9000, does not carry media timestamps. It is a transport protocol for streams of bytes. If you are carrying media over QUIC, the media timestamps are inside the payload, and the QUIC layer does not interpret them. The 33-bit PTS rollover is therefore a payload-layer concern, not a QUIC concern.

Packet-level forensics: how to find the wrap

The first step is to capture the contribution feed at the point where the wrap occurs. If you are using SRT or RIST, capture at the receiver. If you are using MPEG-TS over UDP, capture at the packager input. The capture should include at least 30 seconds before and after the wrap.

Extract PTS values from the PES headers. With tsp, you can use tsp -I pcap -P pcr -P pes -O file to dump PCR and PTS values. With ffprobe, you can use ffprobe -show_packets -select_streams v to get packet timestamps, but note that ffprobe reports timestamps in the container’s timebase, which may already have been normalized by the demuxer. For raw PES analysis, tsp or a custom parser is more reliable.

Plot the PTS values over time. A wrap will appear as a sudden drop of approximately 233 ticks. If the drop is exactly 233, the stream is behaving correctly and the wrap is the only discontinuity. If the drop is a different value, or if there are multiple drops, there is a timestamp rewrite happening somewhere in the chain.

For caption forensics, extract the caption payload and compare its timing reference to the video PTS. If the caption timing reference does not wrap when the video PTS wraps, the caption engine is the problem. If the caption timing reference wraps but the player does not display the captions, the problem is in the player’s handling of the discontinuity.

For A/V sync forensics, extract PTS values from both audio and video elementary streams and compute the difference. A step change of 233 ticks at the wrap point indicates that one stream wrapped and the other did not. A gradual drift indicates a clock rate mismatch, which is a different problem.

Mitigation: a decision table

The right mitigation depends on where the wrap is handled. The following table summarizes the conditions and the corresponding action.

Condition Action
Encoder PTS initialized near zero, event shorter than 26.5 hours No action required. The wrap will not occur during the event.
Encoder PTS initialized near zero, event longer than 26.5 hours Configure the encoder to initialize PTS at a value that places the wrap outside the event window, or ensure the packager handles the wrap.
Encoder PTS initialized at an arbitrary value, event crosses the wrap Ensure the packager signals a discontinuity at the wrap and that the caption engine normalizes timestamps.
Caption engine uses wall clock or monotonic clock Normalize caption timestamps to the video PTS timeline, or configure the caption engine with the PTS wrap point.
Packager rewrites PTS to start at zero per segment Ensure the rewrite is consistent across audio and video, and that the discontinuity is signaled.
Player does not handle discontinuity Test the player with a synthetic discontinuity. If it fails, use a player that handles it, or avoid the wrap by re-initializing PTS.

Testing the wrap without waiting 26.5 hours

The most practical test is to generate a synthetic stream with a PTS that wraps within a few seconds. You can do this with ffmpeg by using the setts bitstream filter or by generating a raw PES stream and rewriting the PTS values. The following approach works with ffmpeg:

ffmpeg -f lavfi -i testsrc2=size=1280x720:rate=30 -f lavfi -i sine=frequency=1000:sample_rate=48000 -t 10 -c:v libx264 -c:a aac -muxdelay 0 -muxpreload 0 -output_ts_offset 95443 -f mpegts test.ts

The -output_ts_offset flag sets the initial PTS offset in seconds. Setting it to 95443 places the initial PTS near the wrap point, so the wrap occurs within the 10-second test. You can then inspect the output with tsp or ffprobe to see how the wrap is handled.

For caption testing, generate a WebVTT file with timestamps that cross the wrap point and mux it with the video. Then play the result in the players you support and observe whether the captions display correctly after the wrap.

For A/V sync testing, generate separate audio and video streams with different PTS offsets and mux them together. Then check whether the sync is preserved across the wrap.

What the standards do and do not say

MPEG-2 systems defines the 33-bit PTS field and the 90 kHz clock. It does not define a rollover mechanism. The expectation is that the PTS is a modulo-233 counter and that receivers handle the wrap. In practice, many receivers do not.

RFC 3550 defines the RTP timestamp as a 32-bit field with a media-specific clock rate. It does not define a rollover mechanism, but it does describe the timestamp as a sampling instant and expects receivers to use modular arithmetic. The RTP timestamp wrap is a separate event from the PTS wrap.

RFC 8216 defines the HLS discontinuity mechanism. It states that media segments must carry the continuation of the encoded bitstream, with timestamps continuing uninterrupted, except for segments explicitly signaled as discontinuities. A PTS wrap is a discontinuity in the timestamp domain, and the specification’s language supports signaling it as such.

The W3C WebVTT specification defines cue timestamps as absolute times in the form HH:MM:SS.mmm. It does not define a rollover mechanism, because WebVTT is not tied to a 33-bit clock. The implication is that WebVTT timestamps should be monotonic within a file, and that a backward jump is a discontinuity that the player must handle.

FAQ

Does the 33-bit PTS rollover affect all MPEG-TS streams?

Yes, if the stream runs long enough. The PTS is a 33-bit field, so it wraps every 26.5 hours regardless of the encoder or packager. Whether the wrap causes a visible problem depends on how the packager and player handle it.

Is the RTP timestamp rollover the same as the PTS rollover?

No. The RTP timestamp is a 32-bit field with a media-specific clock rate. For video at 90 kHz, it wraps every 13.25 hours. The PTS is a 33-bit field at 90 kHz, wrapping every 26.5 hours. They are independent counters and can wrap at different times.

Do SRT and RIST handle the PTS rollover?

SRT and RIST carry their own timestamps in their headers, but they do not interpret the MPEG-TS PTS. The PTS rollover is a payload-layer concern. SRT and RIST will deliver the packets correctly, but the packager or player must handle the PTS wrap.

How do I know if my caption engine handles the wrap?

Test it. Generate a stream with a PTS that wraps within a few seconds, add captions that cross the wrap point, and observe whether the captions display correctly. If they do not, the caption engine is not normalizing timestamps.

Can I avoid the wrap by re-initializing PTS?

Yes, if your encoder supports it. Re-initializing PTS at a value that places the wrap outside the event window avoids the problem entirely. This is the simplest mitigation for events shorter than 26.5 hours. For longer events, you must handle the wrap.

What is the best way to signal a PTS wrap in HLS?

Use EXT-X-DISCONTINUITY at the segment boundary that contains the wrap. Ensure that the segment’s internal timestamps are consistent and that the player is told that the timeline is discontinuous. RFC 8216 defines the tag and its semantics.

The takeaway

The 33-bit PTS rollover is a predictable event that most webcast pipelines are not tested for. The failure modes are caption timing errors, A/V sync drift, and segment boundary glitches. The fixes are known: normalize caption timestamps, signal discontinuities, and ensure that audio and video wrap together. The test is not a 27-hour soak; it is a synthetic stream with a PTS offset that places the wrap inside a short window. If you run long events, put the wrap in your test plan.