Skip to content

Repository files navigation

librtmp2

A modern, open-source Rust library for Legacy RTMP and Enhanced RTMP v1/v2.

crates.io Crates.io Downloads (recent) docs.rs License GitHub Release Language


Overview

librtmp2 is a Rust RTMP protocol library: handshake, chunking, AMF commands, live publish/play relay, and an extern "C" FFI layer. It is meant to be embedded in custom servers, clients, and relay tools.

What it is good at today:

  • Live ingest and relay for OBS/FFmpeg-style workflows (H.264/AAC and Enhanced-RTMP passthrough for HEVC/AV1)
  • Minimal RTMP server and client (connectcreateStreampublish / play)
  • RTMPS (RTMP over TLS) via the optional tls Cargo feature (OpenSSL), enabled by default
  • Parser/serializer modules for E-RTMP v1/v2 structures (usable from embedders; not all are wired into the session layer yet)

What it is not:

  • Not a complete Adobe RTMP 1.0 implementation (no VOD commands, encrypted handshake, etc.; AMF3 shared object envelopes are parsed/delivered, but multi-client attribute sync/persistence is left to the host)
  • Not a full E-RTMP v2 session stack (capsEx negotiation, multitrack, ModEx, and the reconnect-request handshake are wired into the session; the actual reconnect-redirect transport logic is left to the host application)
  • Not an HTTP server, media policy layer, or FFmpeg wrapper

See Implementation status for the code-accurate breakdown.


Architecture

OBS / FFmpeg / App
        │
        ▼
      librtmp2          ← this library
      ├── Handshake / Chunking / Control
      ├── AMF0 commands (connect, publish, play, …)
      ├── Session relay (publisher → players)
      ├── ertmp/ + flv/ parsers (library modules)
      └── RTMPS transport (optional)

The live path is implemented in session/conn.rs and server/mod.rs. Parser modules under ertmp/ and flv/ can be used directly by embedders even when they are not yet called from the default session flow.


State Machine

TCP_ACCEPTED
  → HANDSHAKE
  → CONNECTED
  → [CAPS_NEGOTIATED]      ← entered when connect advertises E-RTMP v2 caps
  → APP_CONNECTED
  → STREAM_CREATED
  → PUBLISHING | PLAYING
  → CLOSING
  → CLOSED

CAPS_NEGOTIATED is entered when the client's connect command advertises fourCcList / capsEx / videoFourCcInfoMap / reconnect; negotiate_caps() computes the response and the negotiated caps are echoed back in the connect _result (session/conn.rs). Clients that don't advertise v2 caps skip straight to APP_CONNECTED as before.


Build

# Debug build
cargo build

# Release build
cargo build --release

# Run tests
cargo test

# Build without TLS (no OpenSSL dependency)
cargo build --no-default-features

The crate uses crate-type = ["cdylib", "staticlib", "lib"] and produces:

  • librtmp2.so / librtmp2.dll — cdylib for FFI consumers
  • librtmp2.a / librtmp2.lib — staticlib for FFI consumers
  • Rust lib — for direct Cargo dependency

TLS / RTMPS

RTMPS (RTMP over TLS) is supported via OpenSSL and is enabled by default via the tls Cargo feature. To produce a plaintext-only build without the optional TLS/OpenSSL dependency:

cargo build --no-default-features

Call lrtmp2_tls_supported() at runtime to check whether the library was built with TLS.


Using as a Rust Crate

Add to your Cargo.toml:

[dependencies]
librtmp2 = { path = "../librtmp2" }

Without TLS:

[dependencies]
librtmp2 = { path = "../librtmp2", default-features = false }

External media export / inject (Rust)

Integrators can observe or feed the same local relay path used by publishers and players — without sockets or cluster concepts:

use librtmp2::server::{
    is_external_publisher_id, Server, StreamInitSnapshot, EXTERNAL_RELAY_PUBLISHER_ID,
};
use librtmp2::{FrameType, RelayFrame, ServerConfig};

// Bounded export of publisher frames (disabled by default = no clones).
server.enable_relay_export(/*max_frames*/ 256, /*max_bytes*/ 4 * 1024 * 1024);
let frames: Vec<RelayFrame> = server.drain_exported_relay_frames();

// Socket-less inject into cache + player fan-out for (app, stream).
// Claims the route until `release_injected_route` (call when the feed ends).
// Required for continually changing routes — otherwise new claims stop at
// `MAX_EXTERNAL_PUBLISH_ROUTES` (soft cap; no mid-feed eviction).
server.inject_relay_frame("live", "cam1", FrameType::Video, ts, &payload)?;
server.release_injected_route("live", "cam1");
// Or on shutdown / remesh: server.release_all_injected_routes();

// Late-joiner / remote-subscriber init snapshot from StreamCache.
if let Some(snap) = server.stream_init_snapshot("live", "cam1") {
    let _: &StreamInitSnapshot = &snap;
    let _ = snap.avc_header;
    let _ = EXTERNAL_RELAY_PUBLISHER_ID;
    let _ = is_external_publisher_id(EXTERNAL_RELAY_PUBLISHER_ID);
}

On an existing local publisher Conn, Conn::inject_relay_frame queues into pending_relay the same way as socket-received media.

HA / cluster mirroring notes

  • enable_relay_export + drain_exported_relay_frames mirror live relay frames only. Init state (AVC/AAC sequence headers, metadata, last keyframe) is not included in the export buffer.
  • Integrators mirroring to remote nodes must also call stream_init_snapshot (or equivalent init-cache handoff) so late joiners can decode the stream.
  • External inject routes (Server::inject_relay_frame) must call release_injected_route when a feed ends. Stale claims are reaped automatically after ~120s without frames, but explicit release is still recommended.

Conn::inject_relay_frame skips media auth callbacks.


Using via the extern "C" FFI

The crate also builds as a cdylib/staticlib and exposes a stable extern "C" API for use from C, Go, Python, PHP, and others. See src/lib.rs for the full FFI surface.

Server

lrtmp2_server_t *lrtmp2_server_create(const lrtmp2_server_config_t *config);
void             lrtmp2_server_destroy(lrtmp2_server_t *server);
int              lrtmp2_server_listen(lrtmp2_server_t *server, const char *bind_addr);
int              lrtmp2_server_poll(lrtmp2_server_t *server, int timeout_ms);
void             lrtmp2_server_stop(lrtmp2_server_t *server);

Client

lrtmp2_client_t *lrtmp2_client_create(const lrtmp2_server_config_t *config);
void             lrtmp2_client_destroy(lrtmp2_client_t *client);
int              lrtmp2_client_connect(lrtmp2_client_t *client, const char *url);
int              lrtmp2_client_publish(lrtmp2_client_t *client);
int              lrtmp2_client_play(lrtmp2_client_t *client);
int              lrtmp2_client_send_frame(lrtmp2_client_t *client, const lrtmp2_frame_t *frame);
int              lrtmp2_client_poll(lrtmp2_client_t *client, int timeout_ms);

Utilities

int          lrtmp2_tls_supported(void);
const char  *lrtmp2_version_string(void);
int          lrtmp2_version_major(void);
int          lrtmp2_version_minor(void);
int          lrtmp2_version_patch(void);
const char  *lrtmp2_error_string(int code);

Repository Structure

librtmp2/
├── src/
│   ├── lib.rs              Rust API + extern "C" FFI layer
│   ├── alloc.rs            Custom allocator hook
│   ├── amf/                AMF0 + AMF3 encoding/decoding
│   ├── buffer.rs           Growable byte buffers
│   ├── bytes.rs            Big-endian byte helpers
│   ├── chunk/              Chunk reader/writer/state (per-csid)
│   ├── client/             Outbound client: connect → publish/play
│   ├── ertmp/              E-RTMP v1/v2 parsers (see Implementation status)
│   ├── flv/                FLV tag parsers (library; not used in live relay path)
│   ├── handshake.rs        C0/C1/C2 ↔ S0/S1/S2
│   ├── media/              Init-frame cache classification + ModEx helpers shared by session/server
│   ├── message/            Message reassembly, control, commands
│   ├── net.rs              Host:port parsing shared by server bind / client connect
│   ├── server/             Listening socket, accept loop, relay
│   ├── session/            Connection state, publish/play handling
│   ├── transport.rs        TLS/plaintext transport
│   └── types.rs            Shared types
├── tests/
│   ├── interop/
│   └── server_client_loopback.rs
├── build.rs
├── Cargo.toml
└── docs/

Implementation status

Status reflects what is wired into the live session path (conn.rs, server/mod.rs, client/mod.rs), not merely what exists as parser code elsewhere in the repo.

Legacy RTMP — live path

Area Status
Standard handshake (C0–C2) Done
Encrypted / Adobe-digest handshake Not implemented
Chunking, control messages, ping Done
Commands connect, createStream, publish, play Done
Commands pause, seek, receiveAudio, receiveVideo, closeStream Done — pause/receiveAudio/receiveVideo gate per-frame relay; closeStream mirrors deleteStream cleanup
FCPublish / releaseStream FCPublish ignored (no-op); releaseStream force-releases a stale publish-route claim so a reconnecting encoder can immediately republish
FCUnpublish / deleteStream Done — clears publish route, play state (is_playing/paused), and sends StreamEOF
Audio / video ingest and relay Done
Aggregate messages Done (unpack → relay)
Publisher onMetaData parsing (stats) Done — relayed live to players and cached for late joiners
AMF3 shared objects Done — envelope (name/version/flags + event list) parsed and delivered via on_shared_object_cb; Conn::send_shared_object writes them. Event payloads are opaque bytes — interpreting Change/SendMessage contents as AMF is left to the host, per this crate's "deliver the event, not the policy" design
User Control StreamBegin / StreamEOF / SetBufferLength Done — StreamBegin/SetBufferLength sent on play, StreamEOF sent on deleteStream/closeStream, inbound SetBufferLength read and stored
One stream per connection (current_stream) By design today
Init-frame cache for late joiners Legacy H.264 (0x17) + AAC, plus enhanced (ex-header) and multitrack sequence starts per track

E-RTMP v1

Area Status
Enhanced A/V passthrough (HEVC/AV1/Opus from FFmpeg/OBS) Done (opaque byte relay)
exvideo_parse / exaudio_parse in session hot path Done — called for codec detection and init-cache classification
fourCcList in connect Done — parsed on read; echoed back in the _result when v2 caps negotiation triggers
HDR / colorInfo (metadata.rs) Done — an enhanced video Metadata packet (ERTMP_PACKET_TYPE_METADATA) is parsed and exposed via Conn::detected_hdr_info (Rust-only; kept off Frame to preserve its ABI-stable #[repr(C)] layout, see docs/abi-policy.md)
Enhanced sequence-start cache for players Done (see init-frame cache above)
exvideo_write / exaudio_write helpers Done — mirror exvideo_parse / exaudio_parse in reverse (src/ertmp/exvideo.rs, src/ertmp/exaudio.rs); exvideo_write covers enhanced (ex-header) headers only — VideoHeader carries no legacy codec ID, so legacy output is rejected (returns 0) rather than emitting an invalid tag byte

E-RTMP v2

Area Status
capsEx, videoFourCcInfoMap, reconnect, multitrack, modex parse/write Library code + unit tests, also wired into the session (see below)
v2 capability negotiation in session Done — negotiate_caps() runs on connect when the client advertises v2 caps; state transitions to CAPS_NEGOTIATED and the response echoes negotiated caps
Multitrack / ModEx in session Done — ModEx normalized on every ingested frame; multitrack containers demuxed per track for codec authorization and per-track init-cache headers
Reconnect in session Done — Conn::send_reconnect_request / Server::request_reconnect send NetConnection.Connect.ReconnectRequest; the client detects it mid-session and fires on_reconnect_request_cb with the (optional) tcUrl. The client also advertises the capsEx reconnect bit + a reconnect value on connect whenever on_reconnect_request_cb is set, so spec-compliant servers know to expect it. The library only delivers the event — establishing the new connection is left to the host application

Client, TLS, tests

Area Status
Minimal publish client Done
Minimal play client (A/V receive callback) Done — also delivers metadata (AMF0/AMF3 data) and handles Aggregate messages
RTMPS Done
Loopback + FFmpeg interop tests Present (tests/, tests/interop/)

Parser-level details and spec mappings: docs/protocol-mapping-legacy.md, docs/protocol-mapping-ertmp-v1.md, docs/protocol-mapping-ertmp-v2.md.


License

MIT — see LICENSE

About

Modern Rust library for Legacy RTMP and Enhanced RTMP v1/v2 with RTMPS, AMF, handshake, chunking, server/client APIs and C FFI.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages