Some checks failed
wai-app and Create / wai-app · does this change touch it (push) Successful in 25s
wai release binaries build on Windows / the seven release binaries · windows (push) Waiting to run
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 24s
WAI operation catalogue / does this change touch it (push) Successful in 29s
wai-fleet service / does this change touch it (push) Successful in 26s
wai-sink-android native sink library / does this change touch it (push) Successful in 24s
WAI Studio / does this change touch it (push) Successful in 18s
wai-sink-swift native sink package / does this change touch it (push) Successful in 19s
wai-pack / wai-pack · does this change touch it (push) Successful in 54s
joule-moq-http live HTTP egress / joule-moq-http · does this change touch it (push) Successful in 1m35s
wai-ledger service / does this change touch it (push) Successful in 1m0s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 1m26s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 1m33s
wai-audit collector and player integration / does this change touch it (push) Successful in 1m18s
wai-quantum for JavaScript / does this change touch it (push) Successful in 1m23s
wai-quantum studio and desktop / does this change touch it (push) Successful in 1m22s
wai-quantum studio and desktop / desktop app · windows (push) Waiting to run
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 11s
WAI examples / WAI examples · does this change touch them (push) Successful in 9s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (push) Waiting to run
WAI examples / WAI examples · android-app · linux-arm64 (push) Waiting to run
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 59s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 2m17s
joule-code conformance gate / Build + round-trip C1-C4 vectors (push) Successful in 5m37s
wai-fleet service / service · linux (push) Successful in 5m27s
wai-pack / wai-pack · linux (push) Successful in 3m41s
wai-ledger service / service · linux (push) Successful in 4m9s
wai-audit collector and player integration / collector · linux (push) Successful in 5m10s
wai-quantum studio and desktop / studio · browser (push) Successful in 8m34s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 17m8s
wai-quantum for JavaScript / javascript · linux (push) Successful in 11m52s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 11m50s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 3m42s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 9m19s
WAI Studio / Studio in WebKit · linux (push) Successful in 25m54s
WAI examples / WAI examples · portable · linux (push) Successful in 12m1s
wai-fleet service / service · macos (push) Successful in 38s
joule-code conformance gate / joule-moq-relay, its own energy on a counter (macos) (push) Successful in 1m21s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 7m55s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 9m49s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 49s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 11m42s
wai-pack / wai-pack · macos (push) Successful in 3m47s
wai-ledger service / service · macos (push) Successful in 1m9s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 1m57s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 4m39s
joule-code conformance gate / joule-moq-relay, every target (push) Successful in 8m59s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 15m58s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 8m53s
wai-pack / wai-pack · independent verifier (Python) (push) Has started running
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 11m20s
wai-pack / wai-pack · linux-arm64 (push) Successful in 5m7s
wai-pack / wai-pack · the floor through a media prober (push) Failing after 1m19s
wai-pack / wai-pack · dependency licences (push) Successful in 2m43s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 16m17s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 8m24s
joule-moq-http live HTTP egress / joule-moq-http, every target (push) Successful in 3m49s
joule-code conformance gate / joule-moq-ingest, every target (push) Successful in 16m55s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 4m6s
An injector publishes no media, and its splice instants are its automation system's, not the programme's Group boundaries. Decision D42, from cue-injection §7 and live-ads §14.3 together: - The clock records the join of a principal that publishes events alone: live-rooms §7.2 asks a join for a grant, not for media. joule-moq-room [ads] injectors names the keys; POST /grant with the role injector grants one meta and cue and no media (403 not_an_injector for another key), and the clock signs its join when its events track appears, as it does an audience member's. The stitcher reads each injector's events track as it reads the source's (offer_event_of: not_a_cue_author, wrong_principal), and the room's records carry its chain. - The cue keeps its instant, which its SCTE 35 section states; the placement moves. A break whose cue falls inside a Group is filled for each session from the next boundary, as a session that joins it running (§7.4): the receipt's start_offset_ms is the distance to that Group, §14.2 places the session there, and rule 1 holds. A break still open is placed over its avail. - A placement that fills nothing changes no ingredient, so rule 1 no longer holds it to a boundary (live-ads §14.3; wai-rs stitch::compose and ad_signalling_verify.py, the check empty_fill_unbound). The corpus gains filled-nothing-inside-a-group and filled-from-the-next-group, and regenerates; the Python verifier passes it, and catches a verifier without each of its 64 checks. tests/ads_room.rs an_injectors_break_is_placed_from_the_next_group: an SCTE 104 spliceStart_normal with auto-return, translated and signed by joule-moq-ingest's injector engine 300 ms before a Group boundary; the clock records the injector's join, the session's receipt states the offset to the next Group, which carries the fill's first creative held to the stitcher's receipt, and the room's records verify whole. Co-Authored-By: Claude Opus 5.5 <[email protected]>
550 lines
33 KiB
Markdown
550 lines
33 KiB
Markdown
# WAI Extension: Cue Injection
|
||
|
||
> Status: Draft. Registers the record `wai.live.injection`: SCTE 104 automation, translated
|
||
> to SCTE 35 sections and signed as a room's `cue` events. A broadcast automation system
|
||
> drives its injector over the API SCTE 104 defines; here the injector is a WAI ingest
|
||
> gateway. It answers each request as that standard says, builds the SCTE 35 section the
|
||
> request asks for, derives the cue the room's ad breaks are made of
|
||
> ([live-events](live-events.md) §4.3, [live-ads](live-ads.md) §4.3), and signs that cue, by
|
||
> its own principal, when the splice comes on the room clock. Before each cue its chain
|
||
> holds the cue's **injection record**, `WIJ1`: the SCTE 104 message whole, when it arrived,
|
||
> and the parameters it was translated with, so that a verifier recomputes the cue, its
|
||
> section and its instant from the record alone. No new transport, and nothing in the cue's
|
||
> layout changes: the record rides in the `meta` kind. The reference implementation is the
|
||
> `scte` module of `joule-moq-ingest` in `joule-code/joule-code-rs/crates/`; the corpus is
|
||
> [`live-conformance/injection/`](../live-conformance/injection/), and its second
|
||
> implementation is `live-conformance/injection_verify.py`.
|
||
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
||
>
|
||
> Texts read for this revision: ANSI/SCTE 104 2023 (its §7, §8, §9, §12, §14 and Appendix
|
||
> A to C); ANSI/SCTE 35-1 2023r2 (its §9.6 to §9.9, §10.2 and §10.3, and the sample
|
||
> messages of §14). SMPTE ST 2010, which carries SCTE 104 messages in a video signal's
|
||
> vertical ancillary data, and SMPTE EG 40, which SCTE 104 names for frame-time arithmetic,
|
||
> were not available to this revision (§5.6, §10). A section number of one of these texts is
|
||
> that revision's.
|
||
|
||
## 1. Scope
|
||
|
||
In scope: the injector's API connection and its responses (§3); the messages it takes, their
|
||
checks, and when their requests are processed (§4); the SCTE 35 section each request builds
|
||
(§5); the cues a request yields, and what it withdraws (§6); the injection record, the
|
||
order in which an injector signs, and the rules it holds its own chain to (§7); how a
|
||
verifier checks an injector's chain (§8); the reasons (§9); and the carriage of SCTE 104 in
|
||
a video signal, as a mapping note (§10).
|
||
|
||
Out of scope: a transport (SCTE 104 runs over TCP, Appendix A there); the PAMS interface of
|
||
SCTE 104 §10 and §11, which provisions injectors and is not an automation system's; the
|
||
splicing of SCTE 35 downstream; ad decisions, which [live-ads](live-ads.md) §7 makes; and
|
||
the SCTE 35-2 event-based signalling a cue's form 2 carries, which no SCTE 104 request
|
||
builds.
|
||
|
||
Relationship to the core spec: `wai.live.injection` is a **record** ([SPEC](../SPEC.md) §5):
|
||
a signed record a WAI tool emits and verifies. It names no payload format and is not
|
||
dispatchable. Relationship to [live-events](live-events.md): this extension changes nothing
|
||
in it. A cue is a `cue` event as it is, of form 1 or 0 (§4.3 there); the injection record is
|
||
the payload of a `meta` event (§4.2 there), bound to its cue by the chain, as live-ads §4.2
|
||
binds a break annotation and [timed-text](timed-text.md) binds a caption's annotation.
|
||
Relationship to [live-ads](live-ads.md): an injector's cues are breaks as any other
|
||
principal's are; this extension makes their timing and their SCTE 35 messages recomputable
|
||
from what the automation system sent.
|
||
|
||
## 2. Terms
|
||
|
||
- **Automation system (AS)**, **injector**, **API connection**, **request**, **response**:
|
||
SCTE 104's terms (§5.2 and §8.1 there). An injector here is a gateway with a principal of
|
||
the room and a grant that permits `cue` and `meta` (live-rooms §4.1).
|
||
- **Message**: one `multiple_operation_message()` (SCTE 104 §8.2.3), or one
|
||
`single_operation_message()` (§8.2.2).
|
||
- **Normal request**, **Supplemental request**: as SCTE 104 §8.3.1 classes the requests of
|
||
its Table 8-4. A **group** is a Normal request and the Supplemental requests after it.
|
||
- **Room clock**, **principal**, **chain**, **cue**: as in live-events §2 and §4.3. A cue's
|
||
**type** is out (1), in (2), chapter (3) or break (4).
|
||
- **Splice instant**: the room-clock instant, µs, a request's splice is for (§5.3, §5.4).
|
||
- **Translation**: the function of §4 to §6 from a message, the instant it arrived and the
|
||
parameters (§4.2) to its requests' sections, cues and withdrawals.
|
||
|
||
Integers of the SCTE 104 and SCTE 35 bit streams are big-endian, as those standards write
|
||
them; integers of the injection record are little-endian, as live-events' are. A digest is
|
||
SHA-256 (FIPS 180-4).
|
||
|
||
## 3. The API connection
|
||
|
||
An injector listens for API connections on TCP, at port 5167 unless it is configured
|
||
otherwise (SCTE 104 §9.1, Appendix A). Each message on a connection begins with its opID, or
|
||
0xFFFF, and its `messageSize`, the size of the whole message: a reader takes four bytes, then
|
||
the rest of `messageSize`.
|
||
|
||
- **Initialisation.** The first message of a connection is an `init_request`. The injector
|
||
answers it with an `init_response`, result 100 (Successful Response), protocol version 0.
|
||
While another connection holds the injector initialised and open, an `init_request` is
|
||
answered result 110 (Injector is already in use), and the injector takes nothing more from
|
||
it. A `multiple_operation_message()` that arrives before `init_request` is answered with an
|
||
`inject_response` of result 124 (`not_initialized`).
|
||
- **Alive.** An `alive_request` on an initialised connection is answered with an
|
||
`alive_response`, result 100, carrying the injector's `time()` (SCTE 104 §12.4: seconds since
|
||
1980-01-06T00:00:00Z with the leap seconds of §4.2 counted, and µs); on another, result 124.
|
||
- **Requests.** A `multiple_operation_message()` is answered with one `inject_response`
|
||
(SCTE 104 §9.6.2): its `message_number`, and the result of §4 to §7: 100, 122 where §5.3
|
||
issues a splice whose pre-roll was too small, or the result code of the refusal (§9). Every
|
||
response echoes the request's `AS_index`, `message_number` and `DPI_PID_index`, with
|
||
`protocol_version` 0 and `result_extension` 0xFFFF but where §4.3 names the opID at fault.
|
||
- **Completion.** When the last cue a message yields has been signed or dropped (§7.3), and
|
||
at least one was signed, the injector sends one `inject_complete_response` (SCTE 104
|
||
§9.6.3) whose `cue_message_count` is the number of cues signed for it. A message that yields
|
||
no cue is completed by its `inject_response` alone, as SCTE 104 §9.6.3 has a Normal request
|
||
that issues no section completed.
|
||
- **Order.** A request's `inject_response` precedes its `inject_complete_response`.
|
||
- **Others.** A `general_response` and the user-defined opIDs 0x0005 and 0x0006 are not
|
||
answered. Another `single_operation_message()` opID is answered with a `general_response`
|
||
of result 125 whose `result_extension` is the opID.
|
||
- **DPI_PID_index.** An injector configured with the values it serves answers a message of
|
||
another value result 126 (`dpi_pid_unknown`).
|
||
|
||
## 4. Requests
|
||
|
||
### 4.1 The message
|
||
|
||
A `multiple_operation_message()` (SCTE 104 Table 8-2) is read in its order, and refused at
|
||
the first of these that holds:
|
||
|
||
1. `Reserved` is not 0xFFFF: it is a `single_operation_message()` (§3).
|
||
2. `messageSize` is not the message's size, a field runs past the message, or bytes follow
|
||
its last request: `message_size` (114).
|
||
3. `protocol_version` or `SCTE35_protocol_version` is not 0: `version_unsupported` (127).
|
||
4. `timestamp()`'s `time_type` is not 0 or 1: `time_type_unsupported` (123). A `time_type` 1
|
||
whose instant precedes the room's `t0` is `timestamp_before_room` (115).
|
||
5. `num_ops` is 0: `no_operation` (115).
|
||
6. The requests do not group (§4.3): `supplemental_first` (115), `operation_unsupported`
|
||
(124) or `opid_unknown` (125).
|
||
7. A request's data does not read as §5 reads it, or holds a value §5 does not allow: the
|
||
reason §5 names.
|
||
8. The message yields more than 255 cues: `too_many_cues` (115).
|
||
|
||
A message refused yields nothing: no section, no cue, no withdrawal.
|
||
|
||
### 4.2 When its requests are processed
|
||
|
||
The **processing instant** `P` of a message, on the room clock, is:
|
||
|
||
- `time_type` 0: the instant its last byte arrived, `R` (SCTE 104 §8.2.3.4: "all Normal
|
||
request timing is relative to the arrival time of the last byte of the message");
|
||
- `time_type` 1: the UTC instant its `UTC_seconds` and `UTC_microseconds` state, less the
|
||
room's `t0`; where that instant has passed when the message arrives, `R`. `UTC_seconds`
|
||
counts from 1980-01-06T00:00:00Z with the intervening leap seconds included (§12.5.1), so
|
||
its Unix instant is (`UTC_seconds` + 315 964 800 − `leap`) s, where `leap` is the leap
|
||
seconds the injector is configured with (18 since 2017-01-01); `UTC_microseconds` holds the
|
||
most significant 16 bits of the µs, so its µs are the field shifted left by 8.
|
||
|
||
A VITC timestamp (`time_type` 2) and a GPI (3) need a video signal's time code and a contact
|
||
closure, which a gateway that takes compressed contribution does not have; they are refused
|
||
(§4.1 step 4).
|
||
|
||
The **parameters** of a translation are the room's `t0`, `leap`, and the frame rate
|
||
`fps_num`/`fps_den` that §5.6's `duration_extension_frames` is counted in.
|
||
|
||
### 4.3 Groups
|
||
|
||
The requests of a message, in order, form groups (SCTE 104 §8.2.3.1, §8.3.1):
|
||
|
||
- a Normal request this revision implements opens a group: `splice_request_data()`
|
||
(0x0101), `splice_null_request_data()` (0x0102), `time_signal_request_data()` (0x0104);
|
||
- a Supplemental request this revision implements joins the group before it:
|
||
`insert_descriptor_request_data()` (0x0108), `insert_DTMF_descriptor_request_data()`
|
||
(0x0109), `insert_avail_descriptor_request_data()` (0x010A),
|
||
`insert_segmentation_descriptor_request_data()` (0x010B), `insert_tier_data()` (0x010F),
|
||
`insert_time_descriptor()` (0x0110); one before any Normal request is
|
||
`supplemental_first`;
|
||
- a request SCTE 104 defines and this revision does not implement (§4.4) is
|
||
`operation_unsupported`, and its opID is the response's `result_extension`;
|
||
- any other opID, reserved or user-defined, is `opid_unknown`, its opID the
|
||
`result_extension`.
|
||
|
||
### 4.4 What this revision does not implement
|
||
|
||
`inject_section_data_request()` (0x0100) carries a command image whose `pts_time` the
|
||
automation system computed on a clock of its own; the injector's program clock is the room
|
||
clock (§5.2), so the image's times cannot be held to its cue. `proprietary_command_request`
|
||
(0x010C) has no cue. Encryption (0x0107, 0x0300, 0x0301), component mode (0x0106,
|
||
deprecated by SCTE 35-1), schedules (0x0103, 0x0105, 0x010D, 0x010E), audio descriptors and
|
||
provisioning (0x0111, 0x0112) and the alternate break duration (0x0113) are not implemented.
|
||
Each is refused `operation_unsupported` (124).
|
||
|
||
## 5. Sections
|
||
|
||
### 5.1 The section
|
||
|
||
Each group builds one `splice_info_section()` (SCTE 35-1 Table 5), but a `splice_cancel`,
|
||
which builds none (§5.3):
|
||
|
||
| field | value |
|
||
|---|---|
|
||
| `table_id` | 0xFC |
|
||
| `section_syntax_indicator`, `private_indicator` | 0, 0 |
|
||
| `sap_type` | 3: not specified (SCTE 104 states no stream access point) |
|
||
| `section_length` | the bytes after it, at most 4 093; past that, `section_too_long` (115) |
|
||
| `protocol_version` | 0 |
|
||
| `encrypted_packet`, `encryption_algorithm`, `pts_adjustment` | 0, 0, 0 |
|
||
| `cw_index` | 0xFF: present and undefined in the clear (§9.6.1), as §14's samples write it |
|
||
| `tier` | the group's `insert_tier_data()`, or 0xFFF without one (SCTE 104 §8.2.3) |
|
||
| `splice_command_length` | the command's length |
|
||
| `splice_command_type` and the command | §5.3 to §5.5 |
|
||
| `descriptor_loop_length`, the descriptors | §5.6, in the order of the group's requests |
|
||
| `alignment_stuffing` | none |
|
||
| `CRC_32` | the MPEG-2 CRC (ISO/IEC 13818-1 Annex A: polynomial 0x04C11DB7, register preset to all ones, no reflection, no final inversion) over every byte before it |
|
||
|
||
Every reserved bit is 1 (SCTE 35-1's definition of reserved). A field of 1 bit that SCTE 104
|
||
carries in a byte (a flag) is 0 or 1; another value is `field_range` (115).
|
||
|
||
### 5.2 The program clock
|
||
|
||
The program's 90 kHz clock is the room clock: a splice instant `S` µs is `pts_time`
|
||
⌊`S` · 9 / 100⌋ modulo 2^33, with `pts_adjustment` 0. A device that restamps the program's
|
||
times adds its delta to `pts_adjustment`, as SCTE 35-1 §9.6.1 has it, and the cue's `t` stays
|
||
the instant on the room clock.
|
||
|
||
### 5.3 `splice_request_data()` to `splice_insert()`
|
||
|
||
`splice_request_data()` (SCTE 104 Table 9-5) is 14 bytes, or 15 with `not_an_entry_flag`;
|
||
another length is `message_size`. Its `splice_insert_type` gives the command (Table 9-7
|
||
there), `splice_event_cancel_indicator` 0:
|
||
|
||
| `splice_insert_type` | `out_of_network_indicator` | `splice_immediate_flag` | `break_duration()` | splice instant `S` |
|
||
|---|---|---|---|---|
|
||
| 1 `spliceStart_normal` | 1 | 1 where `pre_roll_time` is 0, else 0 | where `break_duration` is not 0 | `P` + `pre_roll_time` ms |
|
||
| 2 `spliceStart_immediate` | 1 | 1 | where `break_duration` is not 0 | `P` |
|
||
| 3 `spliceEnd_normal` | 0 | 1 where `pre_roll_time` is 0, else 0 | none | `P` + `pre_roll_time` ms |
|
||
| 4 `spliceEnd_immediate` | 0 | 1 | none | `P` |
|
||
| 5 `splice_cancel` | — | — | — | — |
|
||
|
||
- `splice_event_id`, `unique_program_id`, `avail_num` and `avails_expected` are copied;
|
||
`program_splice_flag` is 1; `event_id_compliance_flag` is 1, since the injector is not told
|
||
whether the automation system's ids keep SCTE 35-1 §9.9.3.
|
||
- `splice_time()` (Table 14 there), where the immediate flag is 0: `time_specified_flag` 1,
|
||
`pts_time` §5.2's of `S`.
|
||
- `break_duration()` (Table 15 there): `auto_return` 1 where `auto_return_flag` is not 0,
|
||
`duration` `break_duration` × 9 000 ticks (tenths of seconds).
|
||
- `not_an_entry_flag` is not passed (SCTE 104 §9.3.1.1).
|
||
- A `spliceStart_normal` whose `pre_roll_time` is not 0 and is below 4 000 ms (SCTE 104
|
||
§12.3) is issued, and answered 122 (`preroll_too_small`).
|
||
- `splice_cancel` builds no section and yields no cue: it withdraws (§6.3). The automation
|
||
system sends it before the splice it cancels (SCTE 104 §9.3.1.2); §7.3 says what the
|
||
injector does with one that comes later.
|
||
- A `splice_insert_type` of 0 or above 5 is `splice_type` (121).
|
||
|
||
### 5.4 `time_signal_request_data()` to `time_signal()`
|
||
|
||
Two bytes, `pre-roll_time`; another length is `message_size`. The command is `time_signal()`
|
||
with `splice_time()` of `S` = `P` + `pre-roll_time` ms, `time_specified_flag` 1.
|
||
|
||
### 5.5 `splice_null_request_data()` to `splice_null()`
|
||
|
||
No data; data is `message_size`. The command is `splice_null()`, empty, and `S` is `P`.
|
||
|
||
### 5.6 Descriptors
|
||
|
||
Each Supplemental request adds what follows to its group's section, in the order of the
|
||
requests:
|
||
|
||
- **`insert_avail_descriptor_request_data()`**: `num_provider_avails`, then that many
|
||
`provider_avail_id`s (a length of 1 + 4 × the count, or `message_size`); one
|
||
`avail_descriptor()` (tag 0x00, length 8, `CUEI`, the id) per id, none for a count of 0.
|
||
- **`insert_DTMF_descriptor_request_data()`**: `pre-roll`, `dtmf_length` (at most 7, the
|
||
descriptor's three-bit `dtmf_count`, or `field_range`), the characters (a length of
|
||
2 + `dtmf_length`, or `message_size`); a `DTMF_descriptor()` (tag 0x01, `CUEI`, `preroll`,
|
||
`dtmf_count` and five reserved bits, the characters).
|
||
- **`insert_time_descriptor()`**: 12 bytes, or `message_size`; a `time_descriptor()` (tag
|
||
0x03, length 16, `CUEI`, `TAI_seconds`, `TAI_ns`, `UTC_offset`).
|
||
- **`insert_descriptor_request_data()`**: `descriptor_count`, then that many descriptor images,
|
||
each a tag, a length and that many bytes, filling the request exactly (or `message_size`);
|
||
each copied whole. An image whose tag is one SCTE 35-1 defines (0x00 to 0x04) and whose
|
||
identifier is `CUEI` is `descriptor_image_standard` (115): SCTE 104 §9.8.5.1 has a standard
|
||
descriptor come through its own request, and the injector derives cues from what such a
|
||
request states, never from an image.
|
||
- **`insert_tier_data()`**: two bytes whose high nibble is 0 (or `field_range`), the
|
||
section's `tier`; a second in one group is `field_range`.
|
||
- **`insert_segmentation_descriptor_request_data()`** (SCTE 104 Table 9-29): read through
|
||
`segments_expected`; then one of four tails, by the bytes that remain: none; 1
|
||
(`duration_extension_frames`); 6 (it, and `delivery_not_restricted_flag`,
|
||
`web_delivery_allowed_flag`, `no_regional_blackout_flag`, `archive_allowed_flag`,
|
||
`device_restrictions`); or 9 (those, and `insert_sub_segment_info`, `sub_segment_num`,
|
||
`sub_segments_expected`); another is `message_size`. Without the delivery fields,
|
||
`delivery_not_restricted_flag` is 1. `device_restrictions` above 3 is `field_range`. Its
|
||
`segmentation_descriptor()` (SCTE 35-1 Table 20): tag 0x02, `CUEI`,
|
||
`segmentation_event_id`; then, where `segmentation_event_cancel_indicator` is 1, that bit,
|
||
`segmentation_event_id_compliance_indicator` 1 and six reserved bits, and nothing more;
|
||
otherwise the cancel bit 0, the compliance bit 1, six reserved bits,
|
||
`program_segmentation_flag` 1, `segmentation_duration_flag` where `duration` is not 0,
|
||
`delivery_not_restricted_flag` and, where it is 0, the four restriction fields (where it is
|
||
1, five reserved bits), `segmentation_duration` (40 bits) where flagged,
|
||
`segmentation_upid_type`, `segmentation_upid_length` and the UPID, `segmentation_type_id`,
|
||
`segment_num`, `segments_expected`, and, where `insert_sub_segment_info` is 1,
|
||
`sub_segment_num` and `sub_segments_expected`, which SCTE 35-1 allows only for the types
|
||
0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x44 and 0x46 (`field_range` for another). A
|
||
descriptor longer than 254 bytes after its length is `descriptor_too_long` (115).
|
||
**Duration.** `segmentation_duration` is `duration` × 90 000 ticks plus
|
||
⌊`duration_extension_frames` × 90 000 × `fps_den` / `fps_num`⌋: SCTE 104 §9.8.7.1 counts
|
||
the extension in frame times, and this revision computes the frame time exactly from the
|
||
configured rate where SCTE 104 refers the arithmetic to SMPTE EG 40, which it did not read.
|
||
|
||
### 5.7 The standard's samples
|
||
|
||
Built by §5, the request whose fields are those of SCTE 35-1 §14.1's sample (a
|
||
`time_signal()` with a Provider Placement Opportunity Start of 307 s) gives that sample's
|
||
section byte for byte, its CRC included, at the instant whose `pts_time` is the sample's; the
|
||
corpus holds it. §14.2's `splice_insert()` sample is written byte for byte by the reference's
|
||
section writer from its fields, but no `splice_request_data()` states its `break_duration`,
|
||
which is not a whole number of tenths of a second.
|
||
|
||
## 6. Cues
|
||
|
||
### 6.1 From a `splice_insert()`
|
||
|
||
- `spliceStart_normal` and `spliceStart_immediate`: an **out** (1) at `S`, `cue_id`
|
||
`splice_event_id`, `duration_ms` `break_duration` × 100 (0 without one), form 1 with the
|
||
section. Where `auto_return_flag` is not 0 and `break_duration` is not 0, a second cue
|
||
follows it, a **derived in** (2) at `S` + `break_duration` × 100 000 µs, the same `cue_id`
|
||
and `duration_ms`, form 0: SCTE 35's `auto_return` has the splicer return without a
|
||
message, and the derived in states that return on the room clock. live-ads §4.3 then finds
|
||
the break's duration equal to its in's `duration_ms`.
|
||
- `spliceEnd_normal` and `spliceEnd_immediate`: an **in** (2) at `S`, `cue_id`
|
||
`splice_event_id`, `duration_ms` 0 (the duration is the room's to compute, live-ads §4.3),
|
||
form 1. It withdraws the derived in of its `cue_id` (§6.3): an early return ends the break
|
||
where the automation system says.
|
||
- `splice_cancel`: no cue (§6.3).
|
||
|
||
### 6.2 From a `time_signal()`
|
||
|
||
Each segmentation request of the group that is not cancelled yields a cue at `S`, `cue_id`
|
||
`segmentation_event_id`, form 1 with the section, by its `segmentation_type_id` (SCTE 35-1
|
||
Table 23):
|
||
|
||
| `segmentation_type_id` | cue | `duration_ms` |
|
||
|---|---|---|
|
||
| 0x22 Break Start; 0x30, 0x32 Provider and Distributor Advertisement Start; 0x34, 0x36 Provider and Distributor Placement Opportunity Start | out (1) | ⌊`segmentation_duration` / 90⌋, 0 without one |
|
||
| 0x23, 0x31, 0x33, 0x35, 0x37: their Ends | in (2) | 0 |
|
||
| 0x20 Chapter Start | chapter (3) | as for an out |
|
||
| any other | none | — |
|
||
|
||
An overlay or promotional opportunity does not replace the program, and an ad block holds
|
||
the placements that do, so neither is a break; a content, program or network boundary is not
|
||
one either. The cues of one group are its ins, then its outs, then its chapters, each in the
|
||
order of its requests: a section that ends one break and opens the next closes the first
|
||
before it opens the second. A cancelled segmentation request withdraws (§6.3). A
|
||
`splice_null()` yields no cue, nor do the segmentation descriptors of a `splice_insert()`,
|
||
whose cue is §6.1's.
|
||
|
||
### 6.3 Withdrawals
|
||
|
||
A `splice_cancel` withdraws the held cues (§7.3) of its `splice_event_id` that a splice
|
||
request yielded; a cancelled segmentation request, those of its `segmentation_event_id` that
|
||
a time signal yielded; a splice end, the held derived in of its `splice_event_id`.
|
||
|
||
### 6.4 The cue and its index
|
||
|
||
A cue is live-events §4.3's payload: `type`, `cue_id` (u32), `duration_ms` (u32), `form`, and,
|
||
for form 1, `scte_len` (u16) and the section. A message's cues, over its groups in order and
|
||
within each in §6.1's and §6.2's order, form one list; a cue's **index** is its position in it,
|
||
from 0.
|
||
|
||
## 7. The injection record and the injector's chain
|
||
|
||
### 7.1 Layout
|
||
|
||
The injection record is the payload of a `meta` event (live-events §4.2):
|
||
|
||
| offset | size | field | meaning |
|
||
|---|---|---|---|
|
||
| 0 | 4 | `magic` | `WIJ1` (ASCII) |
|
||
| 4 | 1 | `version` | 1 in this revision |
|
||
| 5 | 8 | `received` | u64: the room-clock instant, µs, the message's last byte arrived (`R`, §4.2) |
|
||
| 13 | 1 | `leap` | the leap seconds of the translation (§4.2) |
|
||
| 14 | 4 | `fps_num` | u32 |
|
||
| 18 | 4 | `fps_den` | u32, neither 0 |
|
||
| 22 | 1 | `index` | the index of the cue that follows (§6.4) |
|
||
| 23 | 2 | `message_len` | u16 |
|
||
| 25 | `message_len` | `message` | the `multiple_operation_message()`, verbatim |
|
||
|
||
The record holds nothing after `message`. The room's `t0`, the last parameter, is the room
|
||
descriptor's.
|
||
|
||
### 7.2 Signing
|
||
|
||
An injector signs each cue when the room clock reaches its splice instant, never before: a
|
||
withdrawal that comes in time then leaves nothing to undo in a signed chain. Among the cues
|
||
due at once it signs in order of instant, then of the arrival of their messages, then of
|
||
index. Each cue is two events of the injector's chain, at consecutive `seq`: its injection
|
||
record (a `meta` event), then the cue, both with `t` the cue's splice instant. An injector
|
||
takes a message's arrival instant after every instant it has signed at, so the `t` of its
|
||
chain never decreases (live-events §6.2).
|
||
|
||
A cue is signed as it was translated. The record and the cue together are at most the
|
||
principal's `max_payload` each (live-events §5); a message whose record or cue would pass it
|
||
is refused `injection_too_large` (115).
|
||
|
||
### 7.3 The injector's rules
|
||
|
||
An injector **holds** each cue a message yields until its splice instant, and keeps, for its
|
||
own chain, the room's breaks as live-ads §4.3 makes them: the open break, if any, and the end
|
||
of the last closed one. When a message is taken:
|
||
|
||
1. A message whose `message_number` is that of a message with cues still held is a repeat
|
||
(SCTE 104 §9.3: "the Injector can detect that these are duplicates of one another by
|
||
comparison of the `message_number` fields"); it is answered and changes nothing
|
||
(`repeated_message`, counted).
|
||
2. Its withdrawals (§6.3) remove held cues. A `splice_cancel` or a cancelled segmentation
|
||
request that removes none came too late, its splice already signed or never made: it is
|
||
answered 121 (`cancel_too_late`).
|
||
3. Its cues are held. An injector holds at most its configured number of cues (256 by
|
||
default); a message whose cues would pass it is answered 124 and changes nothing.
|
||
|
||
When a held cue's splice instant comes, in the order of §7.2, it is signed unless live-ads
|
||
§4.3 would refuse it in the injector's chain, in which case it is dropped and counted: an out
|
||
while a break is open, or before the end of the last closed one (`break_overlap`); an in with
|
||
no open break of its `cue_id` (`cue_in_unmatched`), or less than a millisecond after its out
|
||
(`break_zero_duration`). A chapter is always signed. A room with other cue authors orders its
|
||
cues by the merged order (live-rooms §9.2), which an injector does not see; it holds only its
|
||
own chain to these rules.
|
||
|
||
### 7.4 Carriage
|
||
|
||
The injector publishes its events on its principal's events track (live-rooms §6.1;
|
||
live-events §8.1), from its start, so a member can subscribe before the first cue. Each group's
|
||
first object is the join set of live-events §8.2: the out of the break open when the group
|
||
began, if one is. A relay whose door checks receipts (jwp-receipts §4.1) names the room's media
|
||
kinds in its rules, since an events track carries none.
|
||
|
||
An injector publishes no media, so its events track is all of its presence in the room: a room
|
||
whose clock records presence from what the relay carries records the injector's `join` when that
|
||
track appears (live-rooms §7.2 asks a `join` for a grant, not for media), and its cues are then a
|
||
present principal's. Its splice instants are its automation system's and fall where that system
|
||
puts them, not on the program's Group boundaries; a stitcher stitches such a break from the next
|
||
boundary, the offset stated in each session's receipt ([live-ads](live-ads.md) §14.3), and the
|
||
cue keeps its instant. The reference room service does both (live-ads §14.8).
|
||
|
||
## 8. Verification
|
||
|
||
A verifier of an injector's chain verifies each event as live-events §6 does (its signature,
|
||
`bad_signature`; its link, `chain_broken`). Then, in `seq` order, it refuses at the first of
|
||
these that holds:
|
||
|
||
1. A `meta` event whose payload begins `WIJ1` is an injection record: refused
|
||
`injection_malformed` where it is shorter than 25 bytes, its version is not 1, a frame-rate
|
||
field is 0, or `message_len` does not end it.
|
||
2. Its message is translated (§4 to §6) with `received`, `leap`, `fps_num`, `fps_den` and the
|
||
room's `t0`: refused `injection_untranslatable` where the translation refuses it.
|
||
3. The next event of the chain is a `cue`: otherwise the record is refused
|
||
`injection_unanswered`.
|
||
4. That cue's payload is the payload of the translation's cue `index`, and its `t` and the
|
||
record's `t` are that cue's instant: otherwise the cue is refused `injection_mismatch`
|
||
(an `index` past the translation's cues among them).
|
||
5. A `cue` of an injector's chain that does not follow its injection record is refused
|
||
`injection_missing`. A verifier is told which principals are injectors: the room's
|
||
configuration names them, and a cue of another principal is not held to this section.
|
||
|
||
A chain that verifies states, for each of its cues, the SCTE 104 message it came from and the
|
||
instant that message arrived, and the cue is that message's translation: a reader recomputes
|
||
the section a splicer acted on, the instant it splices at, and the break live-ads §4.3 makes
|
||
of it.
|
||
|
||
## 9. Reasons
|
||
|
||
Each refusal of a message with the SCTE 104 result code (Table 14-1 there) its response
|
||
carries, and each refusal of §8.
|
||
|
||
| reason | result | raised at |
|
||
|---|---|---|
|
||
| `not_initialized` | 124 | §3, a request before `init_request` |
|
||
| `injector_in_use` | 110 | §3, an `init_request` while another connection holds the injector |
|
||
| `dpi_pid_unknown` | 126 | §3 |
|
||
| `message_size` | 114 | §4.1 step 2; §5.3 to §5.6 |
|
||
| `version_unsupported` | 127 | §4.1 step 3 |
|
||
| `time_type_unsupported` | 123 | §4.1 step 4 |
|
||
| `timestamp_before_room` | 115 | §4.1 step 4 |
|
||
| `no_operation` | 115 | §4.1 step 5 |
|
||
| `supplemental_first` | 115 | §4.3 |
|
||
| `operation_unsupported` | 124 | §4.3, §4.4 |
|
||
| `opid_unknown` | 125 | §4.3; §3, a `single_operation_message()` of another opID |
|
||
| `too_many_cues` | 115 | §4.1 step 8 |
|
||
| `splice_type` | 121 | §5.3 |
|
||
| `preroll_too_small` | 122 | §5.3: issued, and answered so |
|
||
| `field_range` | 115 | §5.1, §5.6 |
|
||
| `descriptor_image_standard` | 115 | §5.6 |
|
||
| `descriptor_too_long` | 115 | §5.6 |
|
||
| `section_too_long` | 115 | §5.1 |
|
||
| `injection_too_large` | 115 | §7.2 |
|
||
| `repeated_message` | 100 | §7.3 step 1: counted, not refused |
|
||
| `cancel_too_late` | 121 | §7.3 step 2 |
|
||
| `break_overlap` | — | §7.3: a held cue dropped |
|
||
| `cue_in_unmatched` | — | §7.3: a held cue dropped |
|
||
| `break_zero_duration` | — | §7.3: a held cue dropped |
|
||
| `injection_malformed` | — | §8 step 1 |
|
||
| `injection_untranslatable` | — | §8 step 2 |
|
||
| `injection_unanswered` | — | §8 step 3 |
|
||
| `injection_mismatch` | — | §8 step 4 |
|
||
| `injection_missing` | — | §8 step 5 |
|
||
| `bad_signature`, `chain_broken` | — | §8, live-events §6 |
|
||
|
||
## 10. Carriage in a video signal (mapping)
|
||
|
||
SCTE 104 §7.2 carries the same messages within a serial digital video signal's vertical
|
||
ancillary data, by SMPTE ST 2010, and SCTE 104 Appendix C has a receiving injector process
|
||
each message and remove it from the video. This section maps that carriage. One ingest path
|
||
implements it: an uncompressed essence contribution, whose ancillary data stream
|
||
[essence-contribution](essence-contribution.md) §8.2 reads for ST 2010's packets and §10.6
|
||
hands to this injector:
|
||
|
||
- **Where it meets this injector.** A contribution that keeps the ancillary data of its
|
||
source carries ST 2010's packets beside the compressed video; an ingest path that reads them
|
||
hands each message it reassembles to §4 as an API connection would, with `R` the room-clock
|
||
time of the video frame whose ancillary data completed it, which live-ingest §7 maps. The Enhanced RTMP and WHIP ingests of live-ingest carry compressed
|
||
video alone, so none of their paths has ST 2010 data to read. The essence contribution's
|
||
path takes `R` as the instant the message's last packet arrived at the gateway: the
|
||
ancillary data stream's own arrival, not a frame's mapped time.
|
||
- **One-way carriage.** In a video signal no response returns (SCTE 104 §7.4, §13.1): the
|
||
injector answers nothing, and a message the automation system repeats for safety is a repeat
|
||
of §7.3 step 1.
|
||
- **Deferred processing.** SCTE 104 §7.5 has a proxy device replace a message's
|
||
`timestamp()` with a `time_type` of 0 when it places the message in the video at its
|
||
triggering frame; such a message is processed at `R` (§4.2).
|
||
- **What is not read.** ST 2010:2008's packet layout and its continuation of a message
|
||
across packets are read by essence-contribution §8.2. SMPTE ST 2038's carriage of
|
||
ancillary data in an MPEG transport stream was not read, and no transport stream path
|
||
reads ST 2010 data.
|
||
|
||
## 11. Status and conformance
|
||
|
||
| part | status | where |
|
||
|---|---|---|
|
||
| the API connection (§3) | implemented | `joule-moq-ingest` `scte`: init, alive, one connection at a time, responses and completions |
|
||
| translation (§4 to §6) | implemented | `scte::scte104`, `scte::scte35` |
|
||
| the injection record, signing, the rules (§7) | implemented | `scte::Engine`, `scte::Injector` |
|
||
| verification (§8) | implemented | `injection_verify.py`; the reference's tests |
|
||
| carriage in a video signal (§10) | specified | — |
|
||
|
||
The corpus ([`live-conformance/injection/`](../live-conformance/injection/vectors.json)) is
|
||
generated through the reference's own code paths from fixed seeds and regenerates byte for
|
||
byte (`tests/conformance_corpus.rs`): translations of every request and descriptor this
|
||
revision implements, the standard's sample (§5.7) among them; every refusal of §4 and §5 with
|
||
its result code; scenarios of messages on a clock with the chain the injector signs for them,
|
||
the breaks live-ads §4.3 makes of it, and what was dropped; and chains §8 refuses, each at its
|
||
event with its reason. `injection_verify.py`, written from this text and sharing no code with
|
||
the reference, translates every message, runs §7.3 over every scenario, verifies every chain
|
||
and derives its breaks, and agrees with the corpus on every vector. The reference's
|
||
end-to-end test (`tests/scte104_injection.rs`) drives an injector over TCP with an automation
|
||
system of its own, and a subscriber at a relay reads the chain from the events track.
|
||
|
||
## 12. Security and privacy considerations
|
||
|
||
- An API connection has no authentication of its own (SCTE 104 relies on the facility's
|
||
network). An injector listens on loopback by default; a deployment puts it on the
|
||
automation network alone, or behind a TLS tunnel whose client certificate names the
|
||
automation system.
|
||
- The injector signs what the automation system asked for, with its own key: its chain is the
|
||
injector's statement of what it was sent and when, not the automation system's, which signs
|
||
nothing. A reader that trusts an injector's cues trusts that key, as a reader of an ingest's
|
||
receipts trusts the first hop's (live-ingest §18).
|
||
- Signing at the splice instant means a cancelled splice leaves no event, and a reader of the
|
||
chain sees only the splices that happened. The cost is notice: a player learns of a break
|
||
when it begins, not at the automation system's pre-roll.
|
||
- A message's bytes are untrusted data: every field is bounded by the message's size and read
|
||
only as §4 to §6 read it, and a descriptor image is copied, never interpreted.
|