fix(efpsrt): carry embedded data end to end, with per-track stream routing, on efp v0.4.0 - #778
Conversation
v0.4.0 gives each EFP stream its own `embedded_<stream-id>` src pad and puts `stream-id` on its caps. At v0.3.0 efpdemux cached a single `embedded` pad and returned it for every stream and data type, so #700 rejected `num_data_tracks > 1` on the input block rather than publish outputs that could never carry a buffer. That limit is now obsolete and is removed. Data tracks are filled in arrival order, the way audio outputs already are, so a track's number does not identify which sender stream it came from. The pad caps carry `stream-id` for that, which is what the whole change upstream was for. The output property description is corrected rather than extended: at v0.3.0 efpmux read both caps fields with `unwrap_or(0)`, so omitting one silently addressed stream 0 and the data was buffered forever. v0.4.0 rejects such caps and drops data addressed to a stream that carries no media, so the description now says that instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The embedded-data channel added in #700 carried nothing in a real flow. `build` linked each `data_input_<i>` identity to the `embed_%u` pad it had just requested from efpmux, but `gst_bin_add` drops any link whose peer is outside the bin, and the pipeline builder adds every element a block returns before it links anything (`gst/pipeline/construction.rs`, then `gst/pipeline/linking.rs`). The link was gone by the time data flowed, and the appsrc feeding it stopped with not-linked. Report the link through `internal_links` instead, so the pipeline builder makes it after the elements are in the bin. The pad is still requested in `build` so it exists with a deterministic name, which is what `linking.rs`'s `static_pad` lookup then finds. The video and audio chains were never affected: they link from caps probes, by which time everything is already in the pipeline. Only the data chain linked eagerly. `efpsrt_data_roundtrip_test` is the guard, and the reason this surfaced: it drives both block builders, joins them over a real SRT connection, and asserts the bytes pushed at `data_in_0` come out of `data_out_0` with the data type and stream ID they were addressed with. #691 asked for exactly this check and it had not been run. `output_block_requests_and_links_an_embed_pad_per_data_track` is the test that gave false confidence: it asserted the src pad's peer straight out of `build`, which was true and meaningless. It now asserts the reported link instead, and says why in its doc comment. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The round trip #691 asked for is now written and run — and it found that the channel #700 shipped carried nothing in a real flow. Pushed as The bug
It is plain GStreamer, not anything EFP-specific. With Video and audio were never affected — they link from caps probes, by which time everything is already in the pipeline. Only the data chain linked eagerly, which is exactly the thing #700's body flagged as worth review ("the embed pad is requested eagerly in Fix: report the link through Why nothing caught it
This is the failure mode CLAUDE.md names: a test has to exercise the code it guards. Asserting graph structure at build time is not the same as asserting the graph the pipeline ends up with. The test is rewritten to assert the reported link, and says in its own doc comment why the old form passed while the channel was dead. The new test
It asserts the bytes match and that the output pad caps carry Reverting just the fix, with the test untouched: So it is a guard, not a demonstration. Two things the test had to pin, because a caller cannot see themBoth are properties of efpmux that the block's shape does not reveal, and both are now constants with comments in the test:
Full run
@borisasadanin — this is the check you offered to run on #691, and it did not need a deployed instance after all. It also means the answer to "does the embedded channel work end to end" was no until this commit, on code that was already merged. Worth knowing before you build on it. |
…_ids Data outputs were filled in arrival order, so which sender stream reached `data_out_0` could differ between runs of the same flow. For provenance, where the point of the embedded channel is saying which stream a manifest describes, a receiver had to read `stream-id` off the pad caps and route downstream itself. `data_stream_ids` takes one EFP stream ID per data track, so `data_out_0` means the same thing every time. Empty is the default and keeps the arrival-order behaviour, so no existing flow changes. A list that does not name exactly one stream per track is rejected at build, as are stream 0 (reserved, never carries media), duplicates (one pad cannot feed two outputs) and anything that is not a 0-255 integer. Quietly ignoring a malformed list would reintroduce the surprise the property exists to remove. Pinned tracks are matched before unpinned ones, so a mixed configuration cannot have an unpinned track swallow a pad another track was configured to receive. This is only possible because gst-plugin-efp v0.4.0 puts `stream-id` on the embedded pad caps; at v0.3.0 there was nothing to match on. `data_stream_ids_pins_each_track_to_its_sender_stream` drives both block builders over a real SRT connection with two media streams, each carrying its own embedded data, and a deliberately reversed list, so arrival order cannot produce the expected result by luck. The test does not depend on which media track the sender numbers 1: it asserts how the receiver routes streams to outputs, not how the sender assigns them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Repins
gst-plugin-efptov0.4.0, removes the data-track limit that #700 putin as a stopgap, and fixes the embedded-data channel, which carried nothing in
a real flow. Follow-up to #700 and Eyevinn/efp#1.
The channel was dead in a real pipeline
Writing the wire round trip #691 asked for turned up a defect in the code #700
already merged.
efpsrt_output'sbuildlinkeddata_input_<i>to theembed_%upad it had just requested.gst_bin_adddrops any link whose peer isoutside the bin, and the pipeline builder adds every element a block returns
before it links anything (
gst/pipeline/construction.rs:179, thengst/pipeline/linking.rs), so that link was gone by the time data flowed andthe source feeding
data_in_0stopped withnot-linked.Plain GStreamer, nothing EFP-specific —
funnelandidentityreproduce it:Video and audio were never affected: they link from caps probes, by which time
everything is in the pipeline. Only the data chain linked eagerly, which is the
thing #700's own body flagged as worth review. Requesting the pad eagerly is
fine; linking eagerly is not.
The fix reports the link through
internal_linksand lets the pipeline buildermake it. The pad is still requested in
build, so it exists with adeterministic name for
linking.rs'sstatic_padlookup to find.Nothing caught this because every test in
efpsrt_embedded_data_test.rsinspects
build's output.output_block_requests_and_links_an_embed_pad_per_data_trackasserted the srcpad's peer straight out of the builder — true, and meaningless, since it
described a link that existed only for the length of the call. It now asserts
the reported link, and its doc comment says why the old form passed while the
channel was dead.
What changed upstream
efpdemuxnow publishes oneembedded_<stream-id>src pad per EFP stream thatcarries data, with
stream-idon the caps. Atv0.3.0it cached a singleembeddedpad and returned it for every stream and data type, and its capscarried no
stream-idat all — the channel was write-only.That was the whole reason #700 rejected
num_data_tracks > 1on the inputblock: publishing
data_out_1would have advertised an output that no buffercould ever reach. The limit is now obsolete, so it goes, along with the
get_external_padsclamp that hid the extra pads.Track numbering:
data_stream_idsData outputs were filled in arrival order, so which sender stream reached
data_out_0could differ between runs of the same flow.data_stream_idstakesone EFP stream ID per data track (
"2,1"), so a track means the same thing everytime. Empty is the default and keeps arrival-order filling, so no existing flow
changes.
A list that does not name exactly one stream per track is rejected at build, as
are stream 0 (reserved, never carries media), duplicates, and anything that is
not a 0-255 integer. Ignoring a malformed list would put back the surprise the
property exists to remove. Pinned tracks match before unpinned ones, so a mixed
configuration cannot have an unpinned track swallow a pad another track was
configured to receive.
This is only possible because v0.4.0 puts
stream-idon the embedded pad caps;at v0.3.0 there was nothing to match on.
A description that had become wrong
#700's output-side description recorded
v0.3.0behaviour:v0.4.0rejects caps missing either field and drops data addressed to a streamthat carries no media, so that text would now actively mislead. Corrected rather
than extended.
This is a behaviour change for operators. A flow whose data source sends
incomplete
application/x-efp-embeddedcaps used to get silence and anunbounded buffer inside efpmux; it now gets a pipeline error. That is the point
of the upstream fix, but it is visible.
Tests
Ran locally on macOS against the newly tagged
v0.4.0:cargo test --features efp --test efpsrt_embedded_data_test— 9 passed.cargo test --features efp— full backend suite green, 574 unit tests plusevery integration target.
cargo fmt --all --checkandcargo clippy --features efp --all-targets -D warningsclean.backend/tests/efpsrt_data_roundtrip_test.rsis new and is the guard for thefix above. It drives
EfpSrtOutputBuilderandEfpSrtInputBuilder, installseach into a pipeline the way
construction.rsdoes — add every element, thenapply
internal_links— joins them over a real SRT connection on loopback, andreads
data_out_0. It asserts the bytes match and that the pad caps carrydata-typeandstream-id.It ran in CI on Linux, not skipped:
Reverting only the fix, with the test untouched:
The two tests that guarded the removed limit are replaced by their opposites:
input_block_builds_a_data_output_per_track_beyond_the_firstbuilds three dataoutputs, and
input_block_advertises_every_data_output_it_buildsasserts allthree reach the flow graph. Both fail against
v0.3.0's behaviour, which iswhat makes the repin load-bearing rather than cosmetic.
data_stream_ids_pins_each_track_to_its_sender_streamis the guard for therouting: two media streams each carrying their own embedded data, over SRT, with
a deliberately reversed list, so arrival order cannot produce the expected
result by luck. It does not depend on which media track the sender numbers 1 —
it asserts how the receiver routes streams to outputs, not how the sender
assigns them. Validation is covered through the real builder rather than the
parser in isolation.
Not verified: Windows and macOS. The macOS numbers are from a local run;
CI covers Linux only, as it does for every EFP change here.
Pin
Cargo.lockresolvesv0.4.0tod6fd3bdb05195a48fffab2f38ed88ec4037f373c,the tagged commit.
🤖 Generated with Claude Code