# Streaming Protocols timps serves the same underlying [hub](Architecture.md#the-hub-srchubc--srchubh) sources over five different wire protocols. This page covers each one's transport, codecs, authentication, and the client-compatibility details the code goes out of its way to get right. ## Quick reference | Protocol | Default port | Transport | Video codec | Audio codec | Auth | TLS | | --- | --- | --- | --- | --- | --- | --- | | RTSP | 554 (RTSPS 322) | TCP-interleaved or UDP unicast | H.264 always; H.265 if `videoN.codec=h265` | AAC (RFC 3640), G.711 PCMU/PCMA (RFC 3551), or Opus (RFC 7587, `USE_STREAM_OPUS`) | RTSP Digest (no `qop`) or Basic | via `USE_TLS` + `rtsp.tls`, shares the HTTPS cert/key | | HTTP fMP4 | 8880 (`/stream.mp4`) | Chunked HTTP, `video/mp4`, MSE-oriented | H.264 always; H.265 if the stream's codec is H.265 | **AAC only** — the muxer has no G.711 support | localhost bypass, token, HTTP Digest (`qop=auth`) or Basic | via `USE_TLS` + `http.https` | | MJPEG | 8880 (`/stream.mjpeg`, `/mjpeg`) | `multipart/x-mixed-replace` | MJPEG (piggyback or dedicated `jpeg.*` channel) | n/a | same as HTTP | same as HTTP | | Snapshot | 8880 (`/snapshot.jpg`) | single JPEG response | MJPEG | n/a | same as HTTP | same as HTTP | | SRT | 9000 | MPEG-TS over SRT, listener (default) or caller (`srt.mode`) | H.264/H.265 (PID `0x100`) | AAC only (PID `0x101`; video-only fallback for G.711) | `srt.streamid` (access) + `srt.passphrase` (AES) | SRT's own encryption | ## RTSP (`src/rtsp/rtsp.c` + `src/rtsp/rtp.c`) - **Port**: `rtsp.port` (default **554**); RTSPS on `rtsp.tls_port` (default **322**, RFC 7826 Annex C's well-known port) when built with `USE_TLS` and `rtsp.tls=1` — sharing the **same certificate/key** as HTTPS (`http.tls_cert`/`http.tls_key`). - **Methods**: `OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN, GET_PARAMETER`. Anything else mid-session (e.g. `PAUSE`/`SET_PARAMETER`) gets a clean `405` with an `Allow:` header rather than being silently dropped. - **Path convention**: `videoN.rtsp_path` (default `/ch0` for the main stream, `/ch1` for the sub stream) is matched as a path prefix followed by `/`, `?`, or end-of-string; an unmatched path falls back to the first boot-enabled stream. This is the one config key that is honestly **live** — see [Configuration Reference](Configuration-Reference.md). - **Transport**: both **TCP-interleaved** (`Transport: RTP/AVP/TCP; interleaved=N-N+1`, with self-assigned sequential channel numbers per SETUP so video/audio/backchannel tracks never collide) and **UDP unicast** (`RTP/AVP;unicast;client_port=...;server_port=...`, server ports picked randomly from 6000–14190, even, with retry-on-collision). A `SETUP` offering neither gets `461 Unsupported Transport`. - **Session timeout**: 60s (`RTSP_SESSION_TIMEOUT_S`), advertised via the SETUP response's `Session: ...;timeout=60` (clients like ffmpeg/live555/VLC send keepalives at half that). UDP-transport sessions are additionally reaped after 2× timeout of control-channel/RTCP silence; TCP-interleaved sessions rely on the socket's own send timeout instead. **Since v1.9.28** the control connection also has TCP keepalive (20 s idle, 4 probes 5 s apart), so a UDP client whose host vanished without a FIN (power cut, WiFi drop) is dropped after ~40 s instead of 120 s. - **RTP payload types**: fixed `96` (video), `97` (audio). - **Performance**: UDP video RTP packets are batched via `sendmmsg()` (16 packets per call) instead of one `sendto()` per packet, with a per-packet fallback if the kernel lacks `sendmmsg`. TCP-interleaved video is batched the same way into one `sendmsg()`. RTSPS has no scatter/gather write, so it used one TLS record per RTP packet; **since v1.9.28** its video is copied into a 16 KB stage and written as one `ms_tls_write()` when that fills or the access unit ends. ### SDP (DESCRIBE) `gen_sdp()` builds a session description with several deliberate RFC-compliance choices, all backed by code comments citing real client breakage they fix: - `o=` uses a real session id (not a hardcoded `"0 0"`) per RFC 4566 §5.2. - `a=range:npt=now-` marks the stream as live/unbounded (RFC 2326 Annex A.3) so players don't assume a seekable VOD range. - `a=control:*` plus per-track `a=control:trackID=0` (video) / `trackID=1` (audio) / `trackID=2` (backchannel, if applicable). - The video m-line always includes an `a=fmtp:` line (`sprop-parameter-sets`/`profile-level-id`) — the code comment is blunt about why: *"never ship an SDP without the a=fmtp line... hardware-decoder NVRs and some mobile SDKs init their decoder strictly from the SDP and stay black"* without it. When no parameter sets are cached yet, `DESCRIBE` briefly subscribes to the encoder (up to 2s) to force an IDR/SPS-PPS capture before answering, falling back to `503` + `Retry-After: 1` rather than sending a degraded SDP. **Since v1.9.28** a `DESCRIBE` against a warm stream answers from the cache and forces no keyframe (it is unauthenticated on an open camera, and `PLAY` asks for its own), and an SDP that would not fit its buffer gets a `500` instead of being sent truncated. The keyframe a cold `DESCRIBE` asks for does not count against the 500 ms start-IDR limit below, so the `PLAY` that follows gets its own at once. - The audio m-line is `mpeg4-generic//` (AAC, RFC 3640 `fmtp`: `streamtype=5;mode=AAC-hbr;config=`) or G.711 PCMU (payload type 0) / PCMA (payload type 8) at 8kHz. - `Content-Base:` is explicitly set to the request URL so a strict parser's relative `a=control:trackID=N` resolves correctly. ### RTP packetization (`rtp.c`) - **H.264** (RFC 6184) / **H.265** (RFC 7798): single-NAL-per-packet when it fits the MTU (`rtsp.mtu`, default 1200), otherwise FU-A (H.264) or the equivalent H.265 fragmentation unit, with the marker bit set correctly on the last NAL of an access unit via one-NAL lookahead (an earlier fixed-size 64-NAL list silently dropped NALs beyond that cap). - **AAC** (RFC 3640 `mpeg4-generic`): a 4-byte AU-header (16-bit headers-length + a 13-bit size/3-bit index-delta pair) is prepended, and — a specific RFC-conformance fix — the header reports the **complete** AU size on **every** fragment of a split AU, not just the first. - **G.711**: sample-exact fragmentation; the marker bit is set only on the very first packet of the session (RFC 3551 §4.1 talkspurt semantics — there's no silence suppression here, so it never re-fires). - **Timestamping**: each track's RTP timestamp is anchored to its own first PTS rather than absolute uptime, avoiding divergent wrap periods between the 90kHz video clock and 16/48kHz audio clock (an earlier design made some clients drop and reconnect every few minutes). Audio timestamps are sample-count-driven with a gap-resync mechanism so a real discontinuity (mute, capture stall, queue overflow) doesn't leave a permanently growing A/V offset. - **RTCP**: a compound **SR+SDES** packet (never a bare SR) at most once/second, per RFC 3550 §6.1 — a bare 28-byte SR was tolerated by ffmpeg/VLC/gstreamer but flagged by stricter stacks (ONVIF conformance suites, Genetec-class VMS). `TEARDOWN` sends SR-or-RR + SDES + BYE (§6.3.7). SSRC and initial sequence/timestamp are seeded from `/dev/urandom`, not `rand()`, so off-path RTP injection/guessing isn't feasible. ### Authentication Disabled while `rtsp.user` is empty. Otherwise every method except `OPTIONS` requires auth: **RTSP Digest** or **HTTP Basic** in the `Authorization:` header. A `401` challenges with *both* `WWW-Authenticate: Digest` and `...Basic`, with a fresh nonce minted per connection (RTSP has no `qop` here, unlike the HTTP side). The digest check additionally verifies the client's `uri=` against the actual request-line target (anti cross-URI replay) and that the nonce is one this server actually issued this session (anti offline replay — a sniffed Authorization header is otherwise fully reproducible and replayable forever against any connection). **Since v1.9.28** a connection is closed after **5 rejected logins** (credentials presented and refused), so one TCP connection no longer allows unlimited password guesses, and a connection that has not reached `PLAY` within **60 s** of connecting is closed too: `OPTIONS` needs no auth, so a client sending one every <30 s could otherwise hold one of the 8 client slots forever. Since v1.10.0 a connection must have authenticated (on an open camera: sent a request other than `OPTIONS`) within **15 s** of connecting: a player that prompts the user for the password has 15 s from connect before the socket is closed (it then has to reconnect). With every slot taken, an unauthenticated connection older than 1 s is closed to make room for the next client. ### ONVIF audio backchannel (trackID=2) Advertised only if the client's `Require:` header includes `www.onvif.org/ver20/backchannel` and the feature was actually configured at boot (`USE_BACKCHANNEL`, `audio.backchannel=1`) — any other unsupported `Require:` feature tag gets a clean `551 Option not supported` (RFC 2326 §12.32) rather than being silently ignored. See [Audio](Audio.md) for the full backchannel pipeline. ## HTTP fMP4 preview (`src/mp4/httpd.c` + `src/mp4/fmp4.c`) - **Port**: `http.port` (default **8880**). With `USE_TLS` and `http.https=1` that one port serves **both** `http://` and `https://`, chosen per connection by peeking at the client's first byte (`0x16` = TLS handshake record, otherwise an HTTP method letter) — so the preview works whichever scheme the embedding WebUI page was served over. `http.https=2` refuses plaintext (`426`) instead. - **Routes**: `/` and `/?embed` (an HTML page with an embedded MediaSource-Extensions `