The stream key, the connection, the binding of tracks to layers, the FourCCs and their payloads, time, groups, bounds and the reconnect request; §6's cto member; the reasons. The corpus's second implementation agrees on every object. Co-Authored-By: Claude Opus 5.5 <[email protected]>
537 lines
32 KiB
Markdown
537 lines
32 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.
|
||
|
||
## 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 revision maps that carriage and implements
|
||
none of it:
|
||
|
||
- **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.
|
||
- **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's packet layout, its fragmentation of a message across
|
||
packets, and its interaction with SMPTE ST 2038's carriage of ancillary data in an MPEG
|
||
transport stream were not available to this revision.
|
||
|
||
## 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.
|