# Encoder Packet Timing in OBS Studio: Understanding CTS, FER, FERC, and PIR Timestamps

> **OBS Studio tracks four nanosecond-precision timestamps—CTS, FER, FERC, and PIR—to measure rendering latency, encoder pipeline delay, and end-to-end frame delivery time.**

- Repository: [OBS Project/obs-studio](https://github.com/obsproject/obs-studio)
- Tags: internals
- Published: 2026-03-03

---

**OBS Studio tracks four nanosecond-precision timestamps—CTS, FER, FERC, and PIR—to measure rendering latency, encoder pipeline delay, and end-to-end frame delivery time.**

Encoder packet timing in OBS Studio provides granular visibility into the video pipeline through the `encoder_packet_time` structure defined in [`libobs/obs-encoder.h`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-encoder.h). The obsproject/obs-studio repository implements a four-point timestamp system that captures frame composition, encode request issuance, encode completion, and packet interleaving events. These nanosecond-resolution measurements enable developers to calculate precise latency metrics and diagnose pipeline stalls in custom output plugins.

## The Four Timestamp Fields

OBS Studio records a high-resolution timeline for every video frame using four distinct events. Each field uses the monotonic nanosecond timebase provided by `os_gettime_ns()`.

### CTS (Composition Timestamp)

**CTS** marks the moment the raw frame is rendered and copied into the GPU texture queue. This timestamp is captured from the texture-frame timestamp (`tf.timestamp`) immediately before the encode request is issued. CTS serves as the baseline for all latency calculations, representing when the frame first entered the OBS rendering pipeline.

### FER (Frame Encode Request)

**FER** records the time the encode request is issued to the encoder, such as when `encode_texture2` is invoked. In [`libobs/obs-video-gpu-encode.c`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-video-gpu-encode.c), this is captured as `fer_ts` right before the encoder callback is invoked. FER separates the rendering phase from the encoding phase, allowing developers to isolate GPU render time from encoder initialization overhead.

### FERC (Frame Encode Request Complete)

**FERC** indicates when the encoder finishes processing the request, either returning a packet or failing. The timestamp is filled immediately after the encoder call returns—`ept->ferc = os_gettime_ns()` on success, or `0` on error (see lines 73‑78 of [`obs-video-gpu-encode.c`](https://github.com/obsproject/obs-studio/blob/main/obs-video-gpu-encode.c)). The difference `FERC − FER` yields the actual encode duration, including any internal queuing delay when the encoder operates asynchronously.

### PIR (Packet Interleave Request)

**PIR** captures the moment the encoded packet is queued for interleaving into the output stream. This timestamp is set later in the output pipeline—typically in the MP4 muxer or RTMP output—when the packet is passed to `obs_output`. Together with CTS, the difference `PIR − CTS` represents the full end‑to‑end latency experienced by the viewer, from frame render to muxer ingestion.

## Structure Definition and Data Flow

The timing data is stored in a dedicated structure that travels with the encoder packet through the pipeline.

```c
/* libobs/obs-encoder.h – definition of the timing struct */
struct encoder_packet_time {
    int64_t pts;      /* PTS used to match frames with packets */
    uint64_t ccts;    /* Composition timestamp – when the frame was rendered */
    uint64_t fer;     /* Frame Encode Request – when encode was started */
    uint64_t ferc;    /* Frame Encode Request Complete – when encode finished */
    uint64_t pir;     /* Packet Interleave Request – when packet entered the muxer */
};

```

During GPU‑accelerated encoding, the core populates the fields sequentially:

```c
/* libobs/obs-video-gpu-encode.c – enqueue timing data */
if (tf.timestamp) {
    struct encoder_packet_time *ept = da_push_back_new(encoder->encoder_packet_times);
    ept->pts = encoder->cur_pts;      // PTS for this frame
    ept->cts = tf.timestamp;          // CTS (render time)
    ept->fer = fer_ts;                // FER (encode request)
    if (success)
        ept->ferc = os_gettime_ns(); // FERC (encode complete)
    else
        ept->ferc = 0;                // error case
    /* PIR will be filled later when the packet is handed to the output */
}

```

## Accessing Timing Data in Plugins

Custom output plugins can inspect these timestamps via the packet callback API defined in [`libobs/obs-output.h`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-output.h).

```c
static void my_output_packet_cb(void *data,
                                struct encoder_packet *pkt,
                                struct encoder_packet_time *pkt_time,
                                void *param)
{
    blog(LOG_INFO,
         "Frame PTS=%" PRId64 " CTS=%" PRIu64 " FER=%" PRIu64
         " FERC=%" PRIu64 " PIR=%" PRIu64,
         pkt_time->pts, pkt_time->cts, pkt_time->fer,
         pkt_time->ferc, pkt_time->pir);
    /* Your output logic … */
}

```

Register the callback when initializing your output:

```c
obs_output_set_video_encoder(output, encoder);
obs_output_set_video_callback(output,
    (obs_output_video_cb)my_output_packet_cb,
    NULL, NULL);

```

## Calculating Latency Metrics

The nanosecond timestamps enable precise latency calculations without floating‑point drift.

**End‑to‑end latency** (render to muxer):

```c
int64_t latency_us = (int64_t)((pkt_time->pir - pkt_time->cts) / 1000);
blog(LOG_INFO, "Observed latency: %"PRId64" µs", latency_us);

```

**Encoder pipeline duration** (request to completion):

```c
uint64_t encode_duration_ns = pkt_time->ferc - pkt_time->fer;

```

**Render‑to‑encode delay** (composition to request):

```c
uint64_t render_delay_ns = pkt_time->fer - pkt_time->cts;

```

## Summary

- **CTS** marks when a frame is rendered and copied to the GPU texture queue, serving as the latency baseline.
- **FER** captures the moment the encode request is issued to the encoder, separating render time from encode initialization.
- **FERC** records when the encoder finishes processing, enabling calculation of actual encode duration including internal queuing.
- **PIR** indicates when the packet enters the muxer, allowing calculation of total end‑to‑end latency when compared with CTS.
- All timestamps use the nanosecond timebase `os_gettime_ns()` defined in [`libobs/obs-encoder.h`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-encoder.h) and populated in [`libobs/obs-video-gpu-encode.c`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-video-gpu-encode.c).

## Frequently Asked Questions

### What is the difference between FER and FERC in OBS Studio encoder timing?

**FER** (Frame Encode Request) marks the exact moment OBS issues the encode command to the encoder, while **FERC** (Frame Encode Request Complete) records when the encoder returns the processed packet or fails. The difference between these two timestamps represents the actual time the encoder spent processing the frame, including any internal queuing delays within the encoder itself.

### How do I calculate total streaming latency using OBS encoder packet timestamps?

To calculate end-to-end latency, subtract the **CTS** (Composition Timestamp) from the **PIR** (Packet Interleave Request). CTS represents when the frame was first rendered, and PIR marks when the encoded packet entered the muxer. The formula `(pir - cts) / 1000` yields the total latency in microseconds, covering rendering, encoding, and pipeline queuing time.

### Where are encoder packet timestamps stored in the OBS Studio source code?

The timestamp structure is defined in [`libobs/obs-encoder.h`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-encoder.h) as `struct encoder_packet_time`, which contains fields for PTS, CTS, FER, FERC, and PIR. The population logic resides in [`libobs/obs-video-gpu-encode.c`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-video-gpu-encode.c), where the core captures CTS from texture frames, FER before encoder invocation, and FERC upon encoder completion. Output modules such as the MP4 muxer later populate the PIR field when packets enter the interleaving stage.

### Can custom OBS plugins access encoder timing data for latency monitoring?

Yes, custom output plugins can access these timestamps through the packet callback API defined in [`libobs/obs-output.h`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-output.h). When registering a video callback with `obs_output_set_video_callback`, the callback receives a `struct encoder_packet_time *` parameter containing all four timestamps. This allows plugins to log latency metrics, embed timing metadata into custom containers, or implement adaptive bitrate algorithms based on real-time encoder performance data.