# moq-transcode

Just-in-time live transcoding for hang broadcasts.

Consume a source broadcast, publish a derivative broadcast next to it: the
derivative catalog advertises lower renditions (rungs) of the source video plus
relative references back to the source renditions, so a player picks from the
combined ladder and the transcoder never proxies what it doesn't re-encode.

Nothing is encoded until someone asks, Cloudflare-style just-in-time per rung:

- **Subscribe** to a rung and the transcoder decodes the source live, resizing
  and re-encoding group for group until the last subscriber leaves. Every
  active rung of a source shares one subscription and one decoder, so decode
  cost scales with sources, not ladder depth.
- **Fetch** a specific group and the transcoder fetches that same group from
  the source and transcodes just that group. Output groups mirror source group
  sequence numbers 1:1, so group N of every rung is the same content as source
  group N and rendition switches land cleanly.

The codec work is [`moq-video`](../moq-video): hardware where available (NVDEC +
NVENC on Linux, VideoToolbox on macOS, Media Foundation on Windows), openh264 as
the H.264 software fallback. H.264, H.265, and 8-bit 4:2:0 AV1 source renditions
are eligible when a matching decoder is available. On an NVIDIA GPU the pipeline
is fully GPU-resident: NVDEC decodes and scales in hardware and NVENC encodes the
CUDA frame in place, with no CPU copies. macOS also resizes on the GPU. Windows
uses the Direct3D11 video processor by default; set
`Config::resize.acceleration` to `resize::Acceleration::Cpu` to force a download
and CPU resize.

## Library

One entry point: `run(source, output, config)`. The caller owns session setup
and where the derivative is announced; the transcoder only fills the output
broadcast.

```rust
let mut config = moq_transcode::Config::default();
// The derivative is announced at `<source>/transcode.hang`, so the source
// renditions are referenced one level up.
config.source = Some(moq_net::PathRelativeOwned::from("..".to_string()));

let output = origin.create_broadcast(
    format!("{path}/transcode.hang"),
    moq_net::broadcast::Route::new().with_announce(true),
)?;

moq_transcode::run(source, output, config).await?;
```

Only renditions strictly below the source survive the ladder: a 480p source is
never transcoded up to 720p, and a same-height rung is only offered when it
undercuts a known source bitrate.

## Example

Publish something first (e.g. `moq publish camera` from
[`moq-cli`](../moq-cli)), then:

```bash
cargo run -p moq-transcode --example transcode -- \
    --url http://localhost:4443/anon --source my-broadcast
```

The derivative appears at `my-broadcast/transcode.hang`.
