Interop ABI 1.4 carries what an encoder did per coded frame in one
normalised section, PEL_SEC_ENC_TELEMETRY. The layout, every bit and every
validation rule are on the API page,
encoder-telemetry.md; the decision is
ADR-0174. This page answers three
operational questions: where the field names live, how the VMAFx name check
runs, and what a caller outside FFmpeg fills in.
Five adapters ship: x265 CSV, SVT-AV1 stat files and VVenC verbose log
buffers read text, FFmpeg AV_PKT_DATA_QUALITY_STATS packet side data
reads one binary packet, and the libaom adapter reads in-process C values.
All read caller-provided values and write caller-provided frame records; the
library performs no file I/O or allocation in its record loops. One transient
C numeric-locale handle per call makes text adapters process-locale
independent. Callers supply the coded bit depth because none of these formats
records it.
yes means the parser fills the field and sets its presence bit. caller
means the adapter fills the field from caller-supplied bit depth or frame indices. -
means the bit stays clear and its value stays zero: not reported, not a
reported zero. These rows are the exact masks returned by
pel_tlm_adapter_caps().
| Field | x265 CSV level 1 | SVT-AV1 stat file | VVenC log (-v 4) | FFmpeg quality stats | libaom C values |
|---|---|---|---|---|---|
display_index |
yes | yes | yes | - | caller |
decode_index |
yes | - | yes | - | caller |
frame_bytes |
yes | yes | yes | - | yes |
header_bits |
- | - | - | - | - |
residual_bits |
- | - | - | - | - |
avg_qp |
yes | yes | yes | yes | yes |
avg_qp_norm |
yes | yes | yes | yes | yes |
psnr_y |
- | yes | yes | yes | yes |
psnr_u |
- | yes | yes | yes | yes |
psnr_v |
- | yes | yes | yes | yes |
ssim_y |
- | yes | - | - | - |
intra_fraction |
- | - | - | - | - |
inter_fraction |
- | - | - | - | - |
skip_fraction |
- | - | - | - | - |
picture_type |
yes | - | yes | yes | yes |
key_frame |
yes | - | yes | - | yes |
reference |
yes | - | - | - | - |
shown |
yes | - | yes | - | - |
scene_cut |
yes | - | - | - | - |
qp_map |
- | - | - | - | - |
bits_map |
- | - | - | - | - |
mode_map |
- | - | - | - | - |
coded_bit_depth |
caller | caller | caller | caller | caller |
x265's POC, Encode Order, Bits, QP, Type and Scenecut columns
supply its fields. SVT-AV1's Picture Number, Average QP, per-plane PSNR,
luma SSIM and byte count supply its fields. VVenC's POC lines supply
display_index, decode_index (emission order), frame_bytes, avg_qp,
per-plane PSNR, slice picture type and key-frame flag. FFmpeg's quality,
picture_type and error_count SSE planes supply its fields. VVenC's reference flag is
not reported: in VVenC v1.14.0 EncGOP.cpp:2932, lowercase slice characters
are emitted only when
!pic->isReferenced && pic->refCounter == 0 && !m_maxParallelFrames. When
parallel frame encoding is enabled (the default multi-threaded mode),
non-reference B-frames remain uppercase, so uppercase does not reliably signal a
reference frame. libaom uses caller-associated frame flags, byte size, last
quantizer and optional per-plane PSNR; its QP and metric bits are optional per
call. Reference and shown status are not inferred; see the
libaom API contract.
A malformed, truncated or field-shifted row or packet returns an error before
any output record is written.
The x265 Bits value must be divisible by eight. In x265 4.3 commit
e9b88125dc21393b3fd8d68e98083bdfb89778a8, the frame encoder derives the
access-unit count as bytes << 3,
the encoder copies that count to
frameStats->bits,
and the CSV writer emits that field as
Bits.
A non-multiple therefore cannot be an x265 4.3 frame byte count and is rejected
instead of rounded.
The VVenC bits value must likewise be divisible by eight. In VVenC v1.14.0
EncGOP.cpp:2884, the GOP encoder derives const uint32_t uibits = numRBSPBytes * 8;
before logging. Any non-multiple of eight cannot be a valid VVenC frame bit count
and returns PEL_ERR_INVALID instead of being rounded.
The registry
libpelorus/schema/telemetry-fields.json
lists all 31 keys with their bit, JSON type, C type, unit, range and whether
the decode side shares them (decoder: shared or encoder_only). The
PEL_TLM_F_<KEY> defines in
interop.h must match it bit for
bit. A rename is a change in three places: the registry, interop.h and the
API page.
VMAFx's decode-side stream metadata (VMAFx/vmafx#2271) uses the shared names
with the same type and unit. The check is
scripts/check-telemetry-field-parity.py;
the fast suite runs it twice:
| Meson test | Command | Result today |
|---|---|---|
telemetry-field-parity |
python3 -I scripts/check-telemetry-field-parity.py |
pass: "pelorus and vmafx agree (17 shared fields)", after the registry and interop.h checks pass |
telemetry-field-parity-self-test |
python3 -I scripts/check-telemetry-field-parity.py --self-test |
pass: a planted failure for every rule is rejected, a matching pair passes |
Pinned VMAFx revision: 243904ab13ea58ebce86d62e727b3289042fbf54 (VMAFx pull request 2773,
api/stream-metadata-fields.json). Its file is copied unchanged, apart from a
source member VMAFx/vmafx@243904ab13ea58ebce86d62e727b3289042fbf54:api/stream-metadata-fields.json,
to libpelorus/test/fixtures/vmafx-stream-metadata-fields.json. A difference
fails with both names printed, for example missing in vmafx: frame_bytes or
type/unit differs: avg_qp. An empty or malformed file, or a source that
names no 40-hex commit, exits 2 and is never a pass. A missing file exits 77;
the test is not on the declared skip list (.config/test-skips.json), so
scripts/check-test-skips.py fails that SKIP. To move the pin, copy the newer
VMAFx file, set source to its commit and update this paragraph.
pelorus/telemetry.h includes only pelorus/interop.h and the C standard
headers; the telemetry test compiles it with no FFmpeg include path. A
Pelorus-side producer that drives an encoder without FFmpeg:
- Fills
PelorusEncTelemetryInput.framewithadapter = PEL_TLM_ADAPTER_EXTERNAL, the codec,granularity, and apresent_maskbit for exactly the fields it has. A field it does not have keeps its bit clear and its value zero: that is "not reported", which is different from a reported zero. - Points
qp_map,bits_mapandmode_mapat its own arrays for the maps it reports (map_cols * map_rowselements, at most 1 048 576). They are read during the call and never kept. - Sizes one buffer with
pel_enc_telemetry_blob_size()for its largest frame and callspel_enc_telemetry_pack()per frame. Nothing is allocated, and an invalid record returnsPEL_ERR_INVALIDorPEL_ERR_RANGEwithout writing.
VMAFx's codec-adapter package (VMAFx/vmafx#2147) uses steps 1 and 2 and
pel_enc_telemetry_validate() for its own records. It does not pack: a blob
under the Pelorus UUID has one writer, Pelorus, and a VMAFx-origin annotation
uses VMAFx's own UUID (interop-abi.md,
VMAFx ADR-1113).
pel_qp_normalize() is the identity for H.26x slice_qp. For SVT-AV1's
0..63 encoder QP it maps through the AV1 quantizer-to-qindex table and the
luma AC QTX table. For coded bit depth bd, the conversion is
qstep_8bit = ac_q / (8 << (bd-8)), followed by
avg_qp_norm = 6 * log2(qstep_8bit / 0.625). The luma AC quantizer is chosen
because avg_qp_norm is defined as the frame's luma AC step; chroma deltas and
DC quantizers do not define that common scale.
SVT-AV1 v4.2.0 writes Average QP as its rounded mean qindex divided by four,
so the stat-file adapter first multiplies that field by four (clamped to
qindex 255) and inversely interpolates SVT-AV1's 64-entry encoder-QP mapping.
The record's avg_qp and qp_scale therefore agree in the
av1_encoder_qp domain. The source field's integer rounding limits this
reconstruction to 0.5 encoder-QP; in particular, the valid fixed-QP-63 value
Average QP: 64 reconstructs encoder QP 63 instead of failing the 0..63
range check. Research 0174 E4
pins the SVT-AV1 calculation and mapping.
Fractional encoder QP is interpolated in the log-step domain: Pelorus computes
the normalized value at the two adjacent integer-QP table entries and linearly
interpolates those normalized values. This is equivalent to interpolating
log2(qstep_8bit), not ac_q, and keeps fractional normalized QP linear on the
same logarithmic scale as the integer anchors. Raw AV1 and VP9 qindex scales
remain unsupported in this round. Research 0174 E3
records the pinned libaom sources and the 192-value comparison.