Skip to content

Commit a417f69

Browse files
authored
Merge pull request #15 from mondain/feature/ctedash
Add CTE LL-DASH ingest publisher
2 parents a84f586 + 6356cac commit a417f69

31 files changed

Lines changed: 10903 additions & 57 deletions

CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ add_library(openmoq_publisher_lib STATIC
149149
src/cmsf_packager.cpp
150150
src/live_srt_ingest.cpp
151151
src/live_srt_config.cpp
152+
src/live_dash_ingest.cpp
152153
src/moq_draft.cpp
153154
src/mp4_box.cpp
154155
src/publisher_api.cpp
@@ -235,6 +236,12 @@ if(OPENMOQ_BUILD_TESTS)
235236
target_link_libraries(openmoq-publisher-live-srt-config-tests PRIVATE openmoq_publisher_lib)
236237
add_test(NAME openmoq-publisher-live-srt-config-tests COMMAND openmoq-publisher-live-srt-config-tests)
237238

239+
add_executable(openmoq-publisher-live-dash-tests
240+
tests/live_dash_ingest_test.cpp
241+
)
242+
target_link_libraries(openmoq-publisher-live-dash-tests PRIVATE openmoq_publisher_lib)
243+
add_test(NAME openmoq-publisher-live-dash-tests COMMAND openmoq-publisher-live-dash-tests)
244+
238245
add_executable(openmoq-publisher-transport-tests
239246
tests/moqt_session_test.cpp
240247
)

README.md

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ It turns MP4 input into CMSF-style publishable objects, builds draft-aware MOQT
1414
- Preserves HEVC signaling and normalizes `hev1` to `hvc1` when needed.
1515
- Builds publish plans with catalog, optional MSF media timeline and SAP event timeline metadata, and media objects.
1616
- Emits generated objects and catalog metadata to disk for inspection.
17-
- Supports draft-aware MOQT framing for drafts 14, 16, and 18.
17+
- Supports draft-aware MOQT framing for drafts 14, 16, 17, and 18.
1818
- Publishes over Raw QUIC or WebTransport when picoquic and picotls are available.
19+
- Accepts live CTE LL-DASH/CMAF ingest over HTTP/1.1 chunked `POST` or `PUT` requests.
1920

2021
## Quick Start
2122

@@ -53,7 +54,7 @@ OPENMOQ_PICOQUIC_TRACE=1 ./build/openmoq-publisher \
5354
--paced
5455
```
5556

56-
Live ingest examples (choose one path, not both):
57+
Live ingest examples (choose one path):
5758

5859
1. SRT ingest path (`--live-source srt`)
5960

@@ -136,9 +137,58 @@ ffmpeg -i /home/ubuntu/bbb_sunflower_1080p_30fps_normal.mp4 \
136137
--forward 0
137138
```
138139

140+
3. CTE LL-DASH ingest path (`--live-source dash`)
141+
142+
Start the publisher with an HTTP/1.1 chunked CMAF ingest listener and a MoQ relay target:
143+
144+
```bash
145+
./build/openmoq-publisher \
146+
--live-source dash \
147+
--dash-listen 0.0.0.0:8080 \
148+
--dash-path /ingest \
149+
--endpoint https://127.0.0.1:4433/moq \
150+
--transport webtransport \
151+
--namespace live \
152+
--draft 18 \
153+
--publish-catalog \
154+
--forward 1 \
155+
--insecure
156+
```
157+
158+
Send CMAF/fMP4 bytes with chunked transfer encoding. Multiple concurrent paths under the prefix are accepted, for example `/ingest/video` and `/ingest/audio`; each path gets path-prefixed track names in the MoQ catalog.
159+
160+
```bash
161+
curl -X PUT \
162+
-H 'Transfer-Encoding: chunked' \
163+
-H 'Content-Type: video/iso.segment' \
164+
--data-binary @live-video.cmaf \
165+
http://127.0.0.1:8080/ingest/video
166+
```
167+
168+
FFmpeg can push live DASH/CMAF requests directly to the ingest prefix. This example creates two video representations plus audio; FFmpeg writes representation requests such as `/ingest/video0`, `/ingest/video1`, and `/ingest/video2`.
169+
170+
```bash
171+
ffmpeg -re \
172+
-f lavfi -i "testsrc2=size=1280x720:rate=25" \
173+
-f lavfi -i "anullsrc=r=48000:cl=stereo" \
174+
-filter_complex "[0:v]split=2[v1][v2];[v1]scale=1280:720[v720];[v2]scale=640:360[v360]" \
175+
-map "[v720]" -c:v:0 libx264 -b:v:0 1500k -g 50 -keyint_min 50 -sc_threshold 0 \
176+
-map "[v360]" -c:v:1 libx264 -b:v:1 500k -g 50 -keyint_min 50 -sc_threshold 0 \
177+
-map 1:a -c:a aac -b:a 128k \
178+
-f dash -seg_duration 2 -use_template 1 -use_timeline 0 \
179+
-init_seg_name 'video$RepresentationID$' \
180+
-media_seg_name 'video$RepresentationID$' \
181+
-adaptation_sets "id=0,streams=v id=1,streams=a" \
182+
-multiple_requests 1 -streaming 1 -remove_at_exit 0 \
183+
-window_size 20 -extra_window_size 20 \
184+
http://127.0.0.1:8080/ingest/
185+
```
186+
187+
Use `--forward 1` when the relay should receive objects immediately. Use `--forward 0` for await-subscribe mode, where media is sent after the relay forwards subscriber interest for the published tracks. A printed `connection_id=` confirms transport and MOQT setup only; it does not confirm namespace acceptance or a downstream subscription.
188+
139189
`--live-source both` is intentionally not supported.
140190

141-
On Windows, replace `./build/openmoq-publisher` with `build\Release\openmoq-publisher.exe` or the matching build configuration path.
191+
On Windows, replace `./build/openmoq-publisher` with `build\Release\openmoq-publisher.exe` or the matching build configuration path. The DASH ingest listener itself is currently supported on Unix-like platforms; Windows builds report the mode as unsupported.
142192

143193
## Documentation
144194

@@ -154,6 +204,7 @@ On Windows, replace `./build/openmoq-publisher` with `build\Release\openmoq-publ
154204
| CAT4MOQ auth example | [examples/auth/README.md](examples/auth/README.md) |
155205
| Protocol mapping | [docs/protocol-mapping.md](docs/protocol-mapping.md) |
156206
| WebTransport compliance | [docs/webtransport-compliance.md](docs/webtransport-compliance.md) |
207+
| macOS DASH shutdown behavior | [docs/macos-accept-shutdown-quirk.txt](docs/macos-accept-shutdown-quirk.txt) |
157208
| Transport plan | [docs/transport-plan.md](docs/transport-plan.md) |
158209
| Project status and roadmap | [docs/status.md](docs/status.md) |
159210

@@ -171,6 +222,6 @@ Localized Publisher API guides are available in [Spanish](docs/publisher-api.es.
171222

172223
## Current Status
173224

174-
The publisher can generate publish plans, emit inspectable output, and publish over picoquic-backed Raw QUIC and WebTransport transports. Draft 14 is the primary target, draft 16 is maintained as a compatibility profile, and draft 18 support is implemented for version selection, setup/request framing codec paths, and request-stream response correlation while interop hardening continues.
225+
The publisher can generate publish plans, emit inspectable output, and publish over picoquic-backed Raw QUIC and WebTransport transports. Draft 14 is the primary target, draft 16 is maintained as a compatibility profile, and drafts 17 and 18 provide the newer VI64 and request-stream protocol profiles. Draft-18 subscriber interest is accepted on fragmented request-stream reads and answered on the same request stream. The draft-19 text is archived under `docs/superpowers/specs/` for review only; `--draft 19` is not implemented.
175226

176227
For the detailed roadmap, see [docs/status.md](docs/status.md).
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
# CTE LL-DASH Ingest Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** Add CTE LL-DASH as a live publishing mechanism by accepting multiple concurrent HTTP/1.1 chunked CMAF ingest paths and forwarding resulting live objects through the existing MoQ publisher.
6+
7+
**Architecture:** Implement a small HTTP/1.1 ingest server that accepts `POST`/`PUT` requests with `Transfer-Encoding: chunked`, decodes request bodies incrementally, and feeds decoded bytes into a CMAF live adapter. The adapter reuses `StreamingMp4Reader`, `extract_tracks`, `build_live_catalog`, `build_live_fragment`, and `Publisher::publish_live_objects(...)`, keeping the new surface limited to one live ingest module and CLI wiring.
8+
9+
**Tech Stack:** C++20, POSIX sockets on non-Windows builds, existing MP4/CMAF parser and MoQ publisher API, CMake tests.
10+
11+
---
12+
13+
## File Structure
14+
15+
- Create `include/openmoq/publisher/live_dash_ingest.h`
16+
- Public config for the DASH ingest listener.
17+
- Queue-backed live object source builder.
18+
- Testable chunked-transfer decoder and CMAF stream adapter.
19+
- Create `src/live_dash_ingest.cpp`
20+
- HTTP request parser.
21+
- Chunked transfer decoder.
22+
- Multi-path connection handler.
23+
- CMAF init/media object production.
24+
- Create `tests/live_dash_ingest_test.cpp`
25+
- Unit tests for chunked decoding, CMAF object production, and concurrent path behavior.
26+
- Modify `include/openmoq/publisher/cli_options.h`
27+
- Add `LiveSourceKind::kDash` and DASH listener options.
28+
- Modify `src/cli_options.cpp`
29+
- Parse `--live-source dash`, `--dash-listen`, `--dash-path`, and `--dash-queue-depth`.
30+
- Modify `src/main.cpp`
31+
- Start DASH ingest mode and call `Publisher::publish_live_objects(...)`.
32+
- Modify `CMakeLists.txt`
33+
- Compile the new module and add the new test executable.
34+
- Modify `docs/quickstart.md` and `README.md`
35+
- Document the new ingest mode after implementation.
36+
37+
## Task 1: Plan and Baseline
38+
39+
- [ ] **Step 1: Save this plan**
40+
41+
Create `docs/ctedash-implementation-plan.md` with this content.
42+
43+
- [ ] **Step 2: Verify baseline branch**
44+
45+
Run:
46+
47+
```bash
48+
git status -sb
49+
```
50+
51+
Expected: branch is `feature/ctedash`; only this plan file is new before implementation starts.
52+
53+
## Task 2: Chunked Transfer Decoder
54+
55+
**Files:**
56+
- Create: `include/openmoq/publisher/live_dash_ingest.h`
57+
- Create: `src/live_dash_ingest.cpp`
58+
- Test: `tests/live_dash_ingest_test.cpp`
59+
- Modify: `CMakeLists.txt`
60+
61+
- [ ] **Step 1: Write failing tests**
62+
63+
Add tests that feed chunked bytes split across arbitrary input boundaries:
64+
65+
```cpp
66+
ok &= expect_chunked_decodes("4\r\nWiki\r\n5\r\npedia\r\n0\r\n\r\n", "Wikipedia");
67+
ok &= expect_chunked_decodes("4;token=value\r\nWiki\r\n0\r\n\r\n", "Wiki");
68+
ok &= expect_chunked_rejects("FFFFFFFFFFFFFFFFF\r\nx\r\n0\r\n\r\n");
69+
```
70+
71+
- [ ] **Step 2: Run test to verify it fails**
72+
73+
Run:
74+
75+
```bash
76+
cmake --build build --target openmoq-publisher-live-dash-tests
77+
./build/openmoq-publisher-live-dash-tests
78+
```
79+
80+
Expected: build or test fails because the new module does not exist yet.
81+
82+
- [ ] **Step 3: Implement minimal decoder**
83+
84+
Implement `ChunkedBodyDecoder::append(...)` and `ChunkedBodyDecoder::take_decoded()` with explicit states for size line, data, data CRLF, trailers, complete, and error. Enforce a configurable max chunk size and reject integer overflow.
85+
86+
- [ ] **Step 4: Run test to verify it passes**
87+
88+
Run the same test command. Expected: all chunked decoder tests pass.
89+
90+
## Task 3: CMAF Live Object Adapter
91+
92+
**Files:**
93+
- Modify: `include/openmoq/publisher/live_dash_ingest.h`
94+
- Modify: `src/live_dash_ingest.cpp`
95+
- Test: `tests/live_dash_ingest_test.cpp`
96+
97+
- [ ] **Step 1: Write failing tests**
98+
99+
Use existing MP4 test helpers to build:
100+
101+
- one init segment containing `ftyp+moov`
102+
- two `moof+mdat` media pairs on path `/ingest/video`
103+
- two `moof+mdat` media pairs on path `/ingest/audio`
104+
105+
Assert:
106+
107+
- no media object is emitted before init is available
108+
- a catalog object is emitted first
109+
- media objects retain path-specific track names
110+
- path queues can interleave without blocking each other
111+
112+
- [ ] **Step 2: Run test to verify it fails**
113+
114+
Run:
115+
116+
```bash
117+
cmake --build build --target openmoq-publisher-live-dash-tests
118+
./build/openmoq-publisher-live-dash-tests
119+
```
120+
121+
Expected: tests fail because the adapter does not exist.
122+
123+
- [ ] **Step 3: Implement adapter**
124+
125+
Implement a queue-backed `LiveDashIngestSession`:
126+
127+
- `ingest(path, bytes)` appends bytes to that path's `StreamingMp4Reader`.
128+
- `ftyp` and `moov` are buffered as init bytes.
129+
- tracks are extracted after `moov`.
130+
- `moof` is held until the following `mdat`.
131+
- each complete `moof+mdat` becomes a `LiveObject`.
132+
- group IDs increment independently per track/path.
133+
- `source()` returns a `LiveObjectSource` with all known tracks plus `catalog`.
134+
135+
- [ ] **Step 4: Run test to verify it passes**
136+
137+
Run the same test command. Expected: adapter tests pass.
138+
139+
## Task 4: HTTP Server and Multiple Concurrent Paths
140+
141+
**Files:**
142+
- Modify: `src/live_dash_ingest.cpp`
143+
- Test: `tests/live_dash_ingest_test.cpp`
144+
145+
- [ ] **Step 1: Write failing tests**
146+
147+
Start the ingest server on loopback port `0`, open two client sockets, and send chunked `PUT` requests to `/ingest/video` and `/ingest/audio`. Assert both paths produce objects and that a malformed request gets a `400` response without killing the listener.
148+
149+
- [ ] **Step 2: Run test to verify it fails**
150+
151+
Run:
152+
153+
```bash
154+
cmake --build build --target openmoq-publisher-live-dash-tests
155+
./build/openmoq-publisher-live-dash-tests
156+
```
157+
158+
Expected: tests fail because server handling is not implemented.
159+
160+
- [ ] **Step 3: Implement server**
161+
162+
Implement `LiveDashIngestServer`:
163+
164+
- bind/listen on configured host/port
165+
- expose actual bound port for tests
166+
- accept each connection in its own worker thread
167+
- parse request line and headers
168+
- allow only `POST` and `PUT`
169+
- require path prefix match
170+
- decode `Transfer-Encoding: chunked`
171+
- feed decoded bytes into the shared session
172+
- send `204 No Content` on clean completion and `400`/`405` on invalid input
173+
- stop cleanly when requested
174+
175+
- [ ] **Step 4: Run test to verify it passes**
176+
177+
Run the same test command. Expected: HTTP server tests pass.
178+
179+
## Task 5: CLI and Publisher Wiring
180+
181+
**Files:**
182+
- Modify: `include/openmoq/publisher/cli_options.h`
183+
- Modify: `src/cli_options.cpp`
184+
- Modify: `src/main.cpp`
185+
- Test: `tests/cli_options_test.cpp`
186+
187+
- [ ] **Step 1: Write failing CLI tests**
188+
189+
Assert:
190+
191+
```cpp
192+
--live-source dash --dash-listen 127.0.0.1:8080 --dash-path /ingest --endpoint https://relay.example.com:443/moq
193+
```
194+
195+
parses successfully, while missing `--dash-listen` or missing `--endpoint` fails.
196+
197+
- [ ] **Step 2: Run test to verify it fails**
198+
199+
Run:
200+
201+
```bash
202+
cmake --build build --target openmoq-publisher-cli-tests
203+
./build/openmoq-publisher-cli-tests
204+
```
205+
206+
Expected: tests fail because the CLI mode is not implemented.
207+
208+
- [ ] **Step 3: Implement CLI and main wiring**
209+
210+
Add:
211+
212+
- `LiveSourceKind::kDash`
213+
- `--dash-listen <host:port>`
214+
- `--dash-path <prefix>` defaulting to `/ingest`
215+
- `--dash-queue-depth <count>` defaulting to `128`
216+
217+
In `main.cpp`, start `LiveDashIngestServer`, then publish `server.source()` with `Publisher::publish_live_objects(...)`.
218+
219+
- [ ] **Step 4: Run test to verify it passes**
220+
221+
Run the CLI tests. Expected: all CLI tests pass.
222+
223+
## Task 6: Docs and Final Verification
224+
225+
**Files:**
226+
- Modify: `README.md`
227+
- Modify: `docs/quickstart.md`
228+
229+
- [ ] **Step 1: Document usage**
230+
231+
Add a short CTE LL-DASH section with the publisher command and a chunked HTTP producer example.
232+
233+
- [ ] **Step 2: Run complete verification**
234+
235+
Run:
236+
237+
```bash
238+
cmake --build build
239+
ctest --test-dir build --output-on-failure
240+
git diff --check
241+
```
242+
243+
Expected: build succeeds, tests pass, and whitespace check passes.
244+
245+
- [ ] **Step 3: Review C++ changes**
246+
247+
Review `git diff HEAD` for correctness, concurrency, ownership, parser overflow handling, and test coverage before commit.
248+

0 commit comments

Comments
 (0)