Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 1 addition & 35 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions doc/bin/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ Publish (read from stdin unless noted):
- `avc3` - raw H.264 Annex-B
- `fmp4` - fragmented MP4 / CMAF
- `ts` - MPEG-TS (H.264 / H.265 video; AAC, MP2, AC-3, or E-AC-3 audio)
- `flv` - FLV / RTMP (H.264 video, AAC audio)
- `hls --playlist <url>` - HLS playlist ingest
- `capture` - capture local devices directly (camera H.264 + microphone Opus; requires the `capture` build feature; does not read stdin)

Expand All @@ -171,6 +172,7 @@ Subscribe (`--format`):
- `fmp4` - fragmented MP4 / CMAF
- `mkv` - Matroska / WebM
- `ts` - MPEG-TS
- `flv` - FLV / RTMP (H.264 video, AAC audio)

### MPEG-TS

Expand All @@ -196,6 +198,25 @@ rather than mis-described. The catalog describes the codec honestly so a
subscriber that can decode it (typically TS gear) picks it up; browsers cannot
play these codecs and should skip the rendition.

### FLV

Ingest an FLV stream from FFmpeg and play one back out:

```bash
# Publish: remux a file to FLV and pipe it in
ffmpeg -i input.mp4 -c copy -f flv - | \
moq-cli publish --url https://relay.example.com --broadcast my-stream flv

# Subscribe: pull FLV back out and play it
moq-cli subscribe --url https://relay.example.com --broadcast my-stream --format flv | ffplay -
```

FLV is the classic RTMP container: H.264 video carried as length-prefixed NALU
with an out-of-band avcC, and AAC audio carried raw with an out-of-band
AudioSpecificConfig. Both pass straight through to the catalog `description`. The
enhanced E-RTMP FourCC payloads (HEVC, AV1, Opus) and the older codecs (VP6, MP3)
are not supported.

## Authentication

Pass a JWT token via the URL:
Expand Down
16 changes: 0 additions & 16 deletions doc/bin/relay/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,22 +96,6 @@ cert = "cert.pem"
key = "key.pem"
```

### \[web.health]

Thresholds for the `/health` load-shedding probe (CPU, RAM, network, load
average), or an external `api` to defer the decision to. All keys are optional;
an unset threshold is not enforced, and with none set `/health` is a pure
liveness probe. See [HTTP Endpoints](/bin/relay/http) for the full reference and
value syntax.

```toml
[web.health]
cpu = 75 # percent; `75` or `75%`
ram = "80%" # percent of total, or absolute (`32GB`)
tx = "500MB" # bytes/s; `b` = bits, `B` = bytes (`4Gb`)
load5 = "80%" # load average; raw (`6.0`) or percent of cores; Unix only
```

### \[auth]

Authentication configuration.
Expand Down
61 changes: 8 additions & 53 deletions doc/bin/relay/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,64 +79,19 @@ curl http://localhost:4443/certificate.sha256

### GET /health

A liveness and load-shedding probe for upstream load balancers.

- Returns `200` with the body `ok` when every configured threshold passes.
- Returns `503` with the body `overloaded`, followed by one line per breached threshold, otherwise.

With no thresholds configured it's a pure liveness probe (always `200`).
Each threshold is independent and only enforced when set, so you can mix and match.
It's unauthenticated so probes don't need a token.
A liveness probe for upstream load balancers. Always returns `200` with the
body `ok\n` (a trailing newline). It's unauthenticated so probes don't need a
token.

```bash
curl -i http://localhost:4443/health
# HTTP/1.1 503 Service Unavailable
# overloaded
# cpu 82.1% exceeds 75%
# tx 612.0MB/s exceeds 500.0MB/s
```

Thresholds are read from the host via the cross-platform [`sysinfo`](https://crates.io/crates/sysinfo) crate.
Metrics are sampled in the background every `interval` seconds (default 2).

```toml
[web.health]
# Return 503 when global CPU usage exceeds this percentage. Accepts `75` or `75%`.
cpu = 75

# Return 503 when memory usage exceeds a percentage of total RAM (`80%`)
# or an absolute used-bytes amount (`32GB`, `32GiB`).
ram = "80%"

# Return 503 when aggregate received/transmitted throughput exceeds this rate.
# A unit is required; lowercase `b` is bits, uppercase `B` is bytes (`4Gb`, `500MB`).
# `/s` is always implied. Useful for shedding before you saturate the NIC.
rx = "4Gb"
tx = "500MB"

# Return 503 when the load average exceeds these limits. Each accepts a raw
# value (`6.0`) or a percentage of CPU cores (`80%`, i.e. a load of
# `0.8 * cores` — so `100%` is one runnable task per core). Unix only;
# these keys are rejected on Windows (which has no load average).
load1 = "8.0"
load5 = "80%"
load15 = "4.0"

# Seconds between metric samples. Defaults to 2, floored at 1.
interval = 2

# Defer the decision to another service. On each request the relay GETs this
# URL (5s timeout); a non-2xx response or an unreachable service counts as a
# breach (fail closed). Merges with the local thresholds above, so you can use
# it alone to simply proxy the verdict.
api = "http://localhost:9876/health"
# HTTP/1.1 200 OK
# ok
```

Every key also has a CLI flag (`--web-health-cpu 75`, `--web-health-ram 80%`,
`--web-health-rx 4Gb`, `--web-health-tx 500MB`, `--web-health-load1 8.0`,
`--web-health-load5 80%`, `--web-health-load15 4.0`, `--web-health-interval 2`,
`--web-health-api http://localhost:9876/health`) and a matching
`MOQ_WEB_HEALTH_*` environment variable.
Host overload monitoring (CPU, RAM, network, load average) belongs in a
separate process that watches the host, not in the relay itself. Point your
load balancer at that process for load shedding.

## See Also

Expand Down
5 changes: 4 additions & 1 deletion doc/lib/rs/crate/moq-mux.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ Media muxers and demuxers for converting existing media formats into MoQ broadca

## Overview

`moq-mux` provides tools for importing media from various container formats:
`moq-mux` provides tools for importing media from (and exporting it back to) various container formats:

- **fMP4/CMAF** - Fragmented MP4 and Common Media Application Format
- **MPEG-TS** - Transport stream (import and export)
- **Matroska / WebM** - EBML container (import and export)
- **FLV** - Flash Video / RTMP container (H.264 + AAC; import and export)
- **HLS** - HTTP Live Streaming playlists
- **Annex B** - H.264/H.265 raw NAL unit streams

Expand Down
12 changes: 10 additions & 2 deletions rs/moq-cli/src/publish.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use clap::Subcommand;
use hang::moq_net;
use moq_hls::import as hls;
use moq_mux::container::{fmp4, ts};
use moq_mux::container::{flv, fmp4, ts};

#[derive(Subcommand, Clone)]
pub enum PublishFormat {
Avc3,
Fmp4,
/// MPEG-TS (transport stream) read from stdin.
Ts,
/// FLV (Flash Video / RTMP) read from stdin.
Flv,
// NOTE: No aac support because it needs framing.
Hls {
/// URL or file path of an HLS playlist to ingest.
Expand Down Expand Up @@ -81,16 +83,18 @@ enum PublishDecoder {
Avc3(Box<moq_mux::codec::h264::Import>),
Fmp4(Box<fmp4::Import>),
Ts(Box<ts::Import>),
Flv(Box<flv::Import>),
Hls(Box<hls::Import>),
}

impl PublishDecoder {
/// Decode a chunk of bytes from stdin (Avc3, Fmp4, or Ts).
/// Decode a chunk of bytes from stdin (Avc3, Fmp4, Ts, or Flv).
fn decode_buf(&mut self, buffer: &mut bytes::BytesMut) -> anyhow::Result<()> {
match self {
Self::Avc3(d) => Ok(d.decode_stream(buffer, None)?),
Self::Fmp4(d) => Ok(d.decode(buffer)?),
Self::Ts(d) => Ok(d.decode(buffer)?),
Self::Flv(d) => Ok(d.decode(buffer)?),
Self::Hls(_) => unreachable!(),
}
}
Expand Down Expand Up @@ -138,6 +142,10 @@ impl Publish {
let ts = ts::Import::new(broadcast.clone(), catalog.clone());
Source::Stream(PublishDecoder::Ts(Box::new(ts)))
}
PublishFormat::Flv => {
let flv = flv::Import::new(broadcast.clone(), catalog.clone());
Source::Stream(PublishDecoder::Flv(Box::new(flv)))
}
PublishFormat::Hls { playlist } => {
let hls = hls::Import::new(broadcast.clone(), catalog.clone(), hls::Config::new(playlist.clone()))?;
Source::Stream(PublishDecoder::Hls(Box::new(hls)))
Expand Down
23 changes: 22 additions & 1 deletion rs/moq-cli/src/subscribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub enum SubscribeFormat {
H265,
/// MPEG-TS (transport stream) container.
Ts,
Flv,
}

/// `clap` adapter for [`CatalogFormat`] (which is `#[non_exhaustive]` and so
Expand Down Expand Up @@ -151,7 +152,7 @@ impl SubscribeArgs {
match self.format {
SubscribeFormat::H264 => Some(VideoCodecKind::H264),
SubscribeFormat::H265 => Some(VideoCodecKind::H265),
SubscribeFormat::Fmp4 | SubscribeFormat::Mkv | SubscribeFormat::Ts => None,
SubscribeFormat::Fmp4 | SubscribeFormat::Mkv | SubscribeFormat::Ts | SubscribeFormat::Flv => None,
}
}

Expand Down Expand Up @@ -249,6 +250,7 @@ impl Subscribe {
SubscribeFormat::H264 => self.run_h264().await,
SubscribeFormat::H265 => self.run_h265().await,
SubscribeFormat::Ts => self.run_ts().await,
SubscribeFormat::Flv => self.run_flv().await,
}
}

Expand Down Expand Up @@ -331,4 +333,23 @@ impl Subscribe {

Ok(())
}

async fn run_flv(self) -> anyhow::Result<()> {
let mut stdout = tokio::io::stdout();

// FLV emits the file header plus AVC/AAC sequence headers, then one tag per
// frame interleaved by timestamp. Avc3 sources are transcoded to avc1 shape
// internally (synthesizing avcC from inline parameter sets). Only H.264 video
// and AAC audio are supported; `fragment_duration` does not apply to FLV.
let mut flv = moq_mux::container::flv::Export::with_catalog_format(self.broadcast, self.catalog)
.await?
.with_latency(self.args.max_latency);

while let Some(chunk) = flv.next().await? {
stdout.write_all(&chunk).await?;
stdout.flush().await?;
}

Ok(())
}
}
3 changes: 2 additions & 1 deletion rs/moq-ffi/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,10 @@ async fn dynamic_track_request_can_publish_media() {
let broadcast = MoqBroadcastProducer::new().unwrap();
let dynamic = broadcast.dynamic().unwrap();
let consumer = broadcast.consume().unwrap();
let catalog_consumer = consumer.subscribe_catalog().unwrap();
let catalog_consumer = consumer.subscribe_catalog().await.unwrap();
let media_consumer = consumer
.subscribe_media("requested-audio".into(), crate::media::Container::Legacy, 10_000)
.await
.unwrap();

let track = tokio::time::timeout(TIMEOUT, dynamic.requested_track())
Expand Down
Loading
Loading