Skip to content

Commit 29af0df

Browse files
committed
Add: Enhance interlace auto-detection: update frame-info structure and handling for sequence gaps, ensuring accurate cadence re-learning and logging.
1 parent 2b07c7b commit 29af0df

File tree

9 files changed

+104
-27
lines changed

9 files changed

+104
-27
lines changed

doc/st40_validation_updates.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ This note captures all recent ST40/ST40p feature changes and the accompanying va
99
- **Frame-info log surfacing:** When tests pass `log_frame_info=True`, the harness dumps the frame-info file and a summary directly into the test log (no artifact download needed).
1010
- **ST40P test mutation knobs:** The GStreamer TX plugin accepts `tx-test-mode` with helpers for `no-marker`, `seq-gap`, `bad-parity`, and `paced` (optionally with `tx-test-pkt-count` and `tx-test-pacing-ns`). These drive targeted negative/edge-path tests.
1111
- **Interlaced & split-mode coverage:** RX/TX can be flagged interlaced independently; mismatch fails fast. Split-mode plus marker handling is now covered in tests.
12-
- **Interlace auto-detect option:** RX can infer progressive vs interlaced cadence from RTP F bits (`rx-auto-detect-interlaced=true` in GStreamer, `ST40P_RX_FLAG_AUTO_DETECT_INTERLACED` in C API). Use when cadence is unknown; RX updates `interlaced` after first field detection while still logging field_num/interlaced in frame-info.
12+
- **Interlace auto-detect option:** RX can infer progressive vs interlaced cadence from RTP F bits (`rx-auto-detect-interlaced=true` in GStreamer, `ST40P_RX_FLAG_AUTO_DETECT_INTERLACED` in C API). Use when cadence is unknown; RX updates `interlaced` after first field detection while still logging `second_field`/`interlaced` in frame-info.
13+
- **Auto-detect reset on discontinuity:** A sequence gap (`seq_discont>0`) clears the interlace detection state; RX re-learns cadence from subsequent F bits and continues logging `second_field`/`interlaced` per field.
1314
- **Warnings when cadence is unknown:** Pipeline RX and the GStreamer RX plugin emit a warning if neither `rx-interlaced` nor auto-detect is set, to avoid silent progressive defaults.
1415
- **Integration safety nets:** New noctx integration tests for ST40 interlaced flows, and expanded GStreamer validation tests across single-host and dual-host (VF/VF) paths.
1516

@@ -51,8 +52,10 @@ flowchart LR
5152
- Enable on RX when cadence is unknown: `rx-auto-detect-interlaced=true` in the GStreamer RX pipeline or set `ST40P_RX_FLAG_AUTO_DETECT_INTERLACED` in `st40p_rx_ops.flags` (C API). Keep `rx-interlaced=false` when auto-detecting.
5253
- TX still needs to emit F bits (set `tx_interlaced=true` in GStreamer or `interlaced=true` in TX ops) so RX can learn cadence.
5354
- Warnings: RX logs a warning if both `rx-interlaced=false` and auto-detect are disabled. Expect a GST_WARNING from `mtl_st40p_rx` and a pipeline warning from `st40p` if cadence is unknown.
54-
- Frame-info fields: `field_num` and `interlaced` are populated once F bits are observed (`0x2/0x3` for interlaced fields, `0x0` for progressive). These are visible in both C API frame_info and GStreamer frame-info dumps.
55+
- Frame-info fields: `second_field` (bool) and `interlaced` are populated once F bits are observed (bit1 set for interlaced; `second_field=true` when F==0x3). These are visible in both C API frame_info and GStreamer frame-info dumps.
5556
- Expect detection log line when auto-detect flips interlaced on RX (`detected interlaced stream (F=0x2/0x3)`); downstream tests assert on these fields rather than relying on caps only.
57+
- Discontinuity handling: a session-level sequence hole (e.g., `tx-test-mode=seq-gap` or real loss) clears detection state; expect `seq_discont>0` in frame-info and a subsequent “detected interlaced stream” log once new F bits arrive. This proves re-learn after reset.
58+
- Build verification: RX attach logs should show `rx_ancillary_session_attach(... flags 0x4 ... auto)` when `rx-auto-detect-interlaced=true` propagates; if flags stay `0x0`, rebuild the GStreamer plugin and ensure the test uses the fresh `--gst-plugin-path`.
5659

5760
## API and plugin changes (developer-facing)
5861

@@ -106,7 +109,7 @@ Across all single-host tests, frame-info is dumped and summarized in pytest logs
106109
- `st40i_split_loopback` — interlaced split-mode loopback with custom fps and framebuff; ensures split/interlace coexist.
107110
- `st40i_split_seq_gap_reports_loss` — crafts a manual RTP sequence gap and validates `seq_discont`/`seq_lost` are reported via the C API frame_info, mirroring the GStreamer gap test at a lower level.
108111
- [tests/integration_tests/noctx/testcases/st40p_auto_detect_tests.cpp](tests/integration_tests/noctx/testcases/st40p_auto_detect_tests.cpp)
109-
- `st40p_rx_auto_detect_interlace` — TX flags interlaced, RX starts progressive with `ST40P_RX_FLAG_AUTO_DETECT_INTERLACED`; asserts `interlaced`/`field_num` populate on RX frame_info.
112+
- `st40p_rx_auto_detect_interlace` — TX flags interlaced, RX starts progressive with `ST40P_RX_FLAG_AUTO_DETECT_INTERLACED`; asserts `interlaced`/`second_field` populate on RX frame_info.
110113

111114
These integration tests exercise the C pipeline APIs directly (outside the GStreamer harness) to prove split-mode, parity, marker, and sequence reporting work end-to-end without additional context setup.
112115

doc/validation_framework.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ The `tests/` directory contains test implementations organized by scenario type:
125125
- Split-mode packetized ANC with frame-info logging (sequence discontinuity, packet totals, RTP marker) and ring-size validation
126126
- Pacing sanity via RTP sender helpers and ramdisk-backed media fixtures (configure `ramdisk.media` in `configs/test_config.yaml`)
127127
- Redundant ST40p/ST40i GStreamer ANC cases with per-port seq-gap scheduling (real payloads, lifted packet caps) and frame-info checks for seq discontinuity/loss logging
128-
- Interlace auto-detect on RX (`rx-auto-detect-interlaced` / `ST40P_RX_FLAG_AUTO_DETECT_INTERLACED`) with warnings if neither interlaced nor auto-detect is set; frame-info now includes `field_num` and `interlaced` for detected cadence
128+
- Interlace auto-detect on RX (`rx-auto-detect-interlaced` / `ST40P_RX_FLAG_AUTO_DETECT_INTERLACED`) with warnings if neither interlaced nor auto-detect is set; frame-info now includes `second_field` (bool) and `interlaced` for detected cadence, and detection resets on `seq_discont` before re-learning from subsequent F bits
129129
- Backend-specific tests (DMA, kernel socket, etc.)
130130
- Integration tests (FFmpeg, GStreamer)
131131

ecosystem/gstreamer_plugin/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ its user data words so that downstream elements receive complete RFC8331 payload
503503
> **Note:** `rtp-ring-size` values are validated at runtime. If the supplied number is not a power of
504504
> two the element fails to initialize, matching the behavior enforced inside
505505
> `gst_mtl_st40p_rx_start()`.
506-
> **Interlace auto-detect:** When cadence is unknown, set `rx-auto-detect-interlaced=true` and leave `rx-interlaced=false`. The plugin warns if both are false. Frame-info includes `field_num` and `interlaced` once F bits are observed.
506+
> **Interlace auto-detect:** When cadence is unknown, set `rx-auto-detect-interlaced=true` and leave `rx-interlaced=false`. The plugin warns if both are false. Sequence discontinuities reset detection; frame-info shows `seq_discont` when it happens and `interlaced` re-populates after new F bits arrive.
507507
508508
#### 5.2.2. Example GStreamer Pipeline for Reception
509509

include/st40_pipeline_api.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,8 @@ struct st40_frame_info {
6060
/** TAI timestamp measured right after the RTP packet for this frame was received */
6161
uint64_t receive_timestamp;
6262

63-
/** RTP F bits (field number) for this frame: 0b00 progressive, 0b10 first field,
64-
* 0b11 second field. */
65-
uint8_t field_num;
63+
/** True if this frame represents the second interlaced field (F=0b11). */
64+
bool second_field;
6665
/** True if the frame was flagged as interlaced (F bits indicate field 1/2). */
6766
bool interlaced;
6867

lib/src/st2110/pipeline/st40_pipeline_rx.c

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,9 @@ static int rx_st40p_rtp_ready(void* priv) {
160160
frame_info->rtp_timestamp = rtp_timestamp;
161161
frame_info->timestamp = rtp_timestamp;
162162
frame_info->epoch = 0;
163-
frame_info->field_num = hdr->first_hdr_chunk.f & 0x3;
164-
frame_info->interlaced = (frame_info->field_num & 0x2) ? true : false;
163+
uint8_t f_bits = hdr->first_hdr_chunk.f & 0x3;
164+
frame_info->interlaced = (f_bits & 0x2) ? true : false;
165+
frame_info->second_field = frame_info->interlaced && (f_bits & 0x1);
165166
ctx->inflight_frame = framebuff;
166167
ctx->inflight_rtp_timestamp = rtp_timestamp;
167168
} else {
@@ -173,9 +174,12 @@ static int rx_st40p_rtp_ready(void* priv) {
173174

174175
/* Update field metadata if a later packet carries interlace info */
175176
uint8_t pkt_field = hdr->first_hdr_chunk.f & 0x3;
176-
if (!frame_info->field_num || frame_info->field_num != pkt_field) {
177-
frame_info->field_num = pkt_field;
178-
frame_info->interlaced = (pkt_field & 0x2) ? true : false;
177+
bool pkt_interlaced = (pkt_field & 0x2) ? true : false;
178+
bool pkt_second_field = pkt_interlaced && (pkt_field & 0x1);
179+
if ((frame_info->interlaced != pkt_interlaced) ||
180+
(frame_info->second_field != pkt_second_field)) {
181+
frame_info->interlaced = pkt_interlaced;
182+
frame_info->second_field = pkt_second_field;
179183
}
180184
}
181185

@@ -428,7 +432,7 @@ static int rx_st40p_init_fbs(struct st40p_rx_ctx* ctx, struct st40p_rx_ops* ops)
428432
frame_info->seq_lost = 0;
429433
frame_info->rtp_marker = false;
430434
frame_info->receive_timestamp = 0;
431-
frame_info->field_num = 0;
435+
frame_info->second_field = false;
432436
frame_info->interlaced = false;
433437
frame_info->priv = framebuff;
434438

@@ -608,7 +612,7 @@ int st40p_rx_put_frame(st40p_rx_handle handle, struct st40_frame_info* frame_inf
608612
frame_info->seq_lost = 0;
609613
frame_info->rtp_marker = false;
610614
frame_info->receive_timestamp = 0;
611-
frame_info->field_num = 0;
615+
frame_info->second_field = false;
612616
frame_info->interlaced = false;
613617
framebuff->stat = ST40P_RX_FRAME_FREE;
614618
ctx->stat_put_frame++;

lib/src/st2110/pipeline/st40_pipeline_tx.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ static int tx_st40p_frame_done(void* priv, uint16_t frame_idx,
158158
frame_info->epoch = meta->epoch;
159159
frame_info->rtp_timestamp = meta->rtp_timestamp;
160160
frame_info->interlaced = ctx->ops.interlaced;
161-
frame_info->field_num = ctx->ops.interlaced ? (meta->second_field ? 0x3 : 0x2) : 0x0;
161+
frame_info->second_field = ctx->ops.interlaced ? meta->second_field : false;
162162

163163
mt_pthread_mutex_lock(&ctx->lock);
164164
if (ST40P_TX_FRAME_IN_TRANSMITTING == framebuff->stat) {
@@ -333,7 +333,7 @@ static int tx_st40p_init_fbs(struct st40p_tx_ctx* ctx, struct st40p_tx_ops* ops)
333333
frame_info->seq_lost = 0;
334334
frame_info->rtp_marker = false;
335335
frame_info->receive_timestamp = 0;
336-
frame_info->field_num = 0;
336+
frame_info->second_field = false;
337337
frame_info->interlaced = false;
338338

339339
/* addr will be resolved later in tx_st40p_create_transport */

lib/src/st2110/st_rx_ancillary_session.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,11 @@ static int rx_ancillary_session_handle_pkt(struct mtl_main_impl* impl,
223223
/* hole in seq id packets going into the session check if the seq_id of the session is
224224
* consistent */
225225
if (seq_id != (uint16_t)(s->session_seq_id + 1)) {
226+
if (s->interlace_auto && s->interlace_detected) {
227+
s->interlace_detected = false;
228+
dbg("%s(%d,%d), reset interlace detect after seq discont %u->%u\n", __func__,
229+
s->idx, s_port, s->session_seq_id, seq_id);
230+
}
226231
dbg("%s(%d,%d), session seq_id %u out of order %d\n", __func__, s->idx, s_port,
227232
seq_id, s->session_seq_id);
228233
s->stat_pkts_out_of_order++;

tests/integration_tests/noctx/testcases/st40p_auto_detect_tests.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ class St40pAutoDetectStrategy : public FrameTestStrategy {
2020

2121
if (info->interlaced) {
2222
saw_interlaced = true;
23-
last_field = info->field_num;
24-
EXPECT_TRUE(last_field == 0x2 || last_field == 0x3)
25-
<< "Unexpected field bits: " << static_cast<int>(last_field);
23+
last_second_field = info->second_field;
24+
second_field_sampled = true;
2625
}
2726
}
2827

2928
bool saw_interlaced = false;
30-
uint8_t last_field = 0;
29+
bool last_second_field = false;
30+
bool second_field_sampled = false;
3131
};
3232

3333
} // namespace
@@ -62,6 +62,6 @@ TEST_F(NoCtxTest, st40p_rx_auto_detect_interlace) {
6262
ASSERT_GT(handler->rxFrames(), 0u) << "No frames received";
6363
EXPECT_EQ(handler->txFrames(), handler->rxFrames()) << "TX/RX frame count mismatch";
6464
EXPECT_TRUE(strategy->saw_interlaced) << "Auto-detect did not see interlaced F bits";
65-
EXPECT_TRUE(strategy->last_field == 0x2 || strategy->last_field == 0x3)
66-
<< "Auto-detected field bits invalid";
65+
EXPECT_TRUE(strategy->second_field_sampled)
66+
<< "Auto-detect did not surface field cadence metadata";
6767
}

tests/validation/tests/single/gstreamer/anc_format/test_anc_format.py

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,7 +1606,7 @@ def test_st40i_interlace_flag_mismatch(
16061606

16071607

16081608
@pytest.mark.nightly
1609-
def test_st40p_interlace_auto_detect(
1609+
def test_st40p_interlace_auto_detect_reset(
16101610
hosts,
16111611
build,
16121612
media,
@@ -1617,8 +1617,8 @@ def test_st40p_interlace_auto_detect(
16171617
media_file,
16181618
):
16191619
"""
1620-
Validate that RX auto-detect accepts interlaced TX even when the receiver does not
1621-
declare interlace upfront, relying on RTP F bits to learn cadence.
1620+
Validate that RX auto-detect accepts interlaced TX and, after a forced sequence gap
1621+
reset, re-learns cadence via RTP F bits without explicit interlace hints.
16221622
16231623
.. rubric:: Pass Criteria
16241624
- Pipeline succeeds with TX interlaced and RX auto-detect enabled.
@@ -1651,6 +1651,8 @@ def test_st40p_interlace_auto_detect(
16511651
tx_interlaced=True,
16521652
)
16531653

1654+
frame_info_path = _frame_info_path(os.path.dirname(output_file_path))
1655+
16541656
rx_config = GstreamerApp.setup_gstreamer_st40p_rx_pipeline(
16551657
build=build,
16561658
nic_port_list=interfaces_list[1],
@@ -1661,12 +1663,13 @@ def test_st40p_interlace_auto_detect(
16611663
rx_framebuff_cnt=3,
16621664
rx_interlaced=False,
16631665
rx_auto_detect_interlaced=True,
1666+
frame_info_path=frame_info_path,
16641667
)
16651668

16661669
expectation = "RX auto-detect resolves interlaced TX without explicit cadence hint"
16671670

16681671
try:
1669-
with _test_summary("test_st40p_interlace_auto_detect", expectation):
1672+
with _test_summary("test_st40p_interlace_auto_detect_reset", expectation):
16701673
assert GstreamerApp.execute_test(
16711674
build=build,
16721675
tx_command=tx_config,
@@ -1679,9 +1682,72 @@ def test_st40p_interlace_auto_detect(
16791682
sleep_interval=5,
16801683
log_frame_info=True,
16811684
)
1685+
1686+
# Force a sequence gap to reset auto-detect state and prove cadence re-learns
1687+
gap_frame_info_path = _frame_info_path(os.path.dirname(output_file_path))
1688+
tx_config_gap = GstreamerApp.setup_gstreamer_st40p_tx_pipeline(
1689+
build=build,
1690+
nic_port_list=interfaces_list[0],
1691+
input_path=input_file_path,
1692+
tx_payload_type=113,
1693+
tx_queues=4,
1694+
tx_framebuff_cnt=3,
1695+
tx_fps=50,
1696+
tx_did=67,
1697+
tx_sdid=2,
1698+
tx_interlaced=True,
1699+
tx_split_anc_by_pkt=True,
1700+
tx_test_mode="seq-gap",
1701+
tx_test_pkt_count=200,
1702+
)
1703+
1704+
rx_config_gap = GstreamerApp.setup_gstreamer_st40p_rx_pipeline(
1705+
build=build,
1706+
nic_port_list=interfaces_list[1],
1707+
output_path=output_file_path,
1708+
rx_payload_type=113,
1709+
rx_queues=4,
1710+
timeout=15,
1711+
rx_framebuff_cnt=3,
1712+
rx_interlaced=False,
1713+
rx_auto_detect_interlaced=True,
1714+
frame_info_path=gap_frame_info_path,
1715+
)
1716+
1717+
media_create.remove_file(output_file_path, host=host)
1718+
1719+
reset_expectation = "RX auto-detect re-learns cadence after seq gap reset with frame-info logged"
1720+
1721+
with _test_summary(
1722+
"test_st40p_interlace_auto_detect_reset_gap", reset_expectation
1723+
):
1724+
assert GstreamerApp.execute_test(
1725+
build=build,
1726+
tx_command=tx_config_gap,
1727+
rx_command=rx_config_gap,
1728+
input_file=input_file_path,
1729+
output_file=output_file_path,
1730+
test_time=test_time,
1731+
host=host,
1732+
tx_first=False,
1733+
sleep_interval=5,
1734+
log_frame_info=True,
1735+
)
1736+
1737+
gap_log = run(f"cat {gap_frame_info_path}", host=host)
1738+
gap_entries = _parse_frame_info_entries(gap_log.stdout_text or "")
1739+
assert (
1740+
gap_entries
1741+
), "Seq-gap auto-detect reset produced no frame-info entries"
1742+
assert any(
1743+
entry.get("seq_discont", 0) > 0 for entry in gap_entries
1744+
), "Seq-gap auto-detect reset did not log any discontinuity after gap"
16821745
finally:
16831746
media_create.remove_file(input_file_path, host=host)
16841747
media_create.remove_file(output_file_path, host=host)
1748+
media_create.remove_file(frame_info_path, host=host)
1749+
if "gap_frame_info_path" in locals():
1750+
media_create.remove_file(gap_frame_info_path, host=host)
16851751

16861752

16871753
@pytest.mark.nightly

0 commit comments

Comments
 (0)