# librtmp2
A modern, open-source **Rust library** for Legacy RTMP and Enhanced RTMP v1/v2.
[](https://crates.io/crates/librtmp2)

[](https://docs.rs/librtmp2)
[](LICENSE)


---
## 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 (`connect` → `createStream` → `publish` / `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, shared objects, encrypted handshake, etc.)
- Not a full E-RTMP v2 session stack (`capsEx` negotiation, reconnect, multitrack, ModEx are library code only today)
- Not an HTTP server, media policy layer, or FFmpeg wrapper
See [Implementation status](#implementation-status) for the code-accurate breakdown.
---
## Architecture
```text
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
```text
TCP_ACCEPTED
→ HANDSHAKE
→ CONNECTED
→ APP_CONNECTED ← connect today skips CAPS_NEGOTIATED
→ STREAM_CREATED
→ CLOSED
```
`CAPS_NEGOTIATED` exists in `ConnState` for a future E-RTMP v2 capability exchange but is not entered by the current session code.
---
## Build
```bash
# 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:
```bash
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`:
```toml
[dependencies]
librtmp2 = { path = "../librtmp2" }
```
Without TLS:
```toml
[dependencies]
librtmp2 = { path = "../librtmp2", default-features = false }
```
---
## 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
```c
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
```c
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
```c
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
```text
librtmp2/
├── src/
│ ├── lib.rs Rust API + extern "C" FFI layer
│ ├── alloc.rs Custom allocator hook
│ ├── amf.rs AMF0 + AMF3 encoding/decoding
│ ├── buffer.rs Growable byte buffers
│ ├── bytes.rs Big-endian byte helpers
│ ├── chunk.rs 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
│ ├── message/ Message reassembly, control, commands
│ ├── 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
| 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` | Not implemented |
| `FCPublish` / `releaseStream` | Ignored (no-op) |
| `FCUnpublish` / `deleteStream` | Partial (publish route cleanup only) |
| Audio / video ingest and relay | Done |
| Aggregate messages | Done (unpack → relay) |
| Publisher `onMetaData` parsing (stats) | Done — not relayed to players |
| AMF3 shared objects | Not implemented |
| User Control `StreamBegin` / `StreamEOF` / `SetBufferLength` | Encode/decode helpers only — not sent or handled in session |
| One stream per connection (`current_stream`) | By design today |
| Init-frame cache for late joiners | Legacy H.264 (`0x17`) + AAC only |
### E-RTMP v1
| Enhanced A/V passthrough (HEVC/AV1/Opus from FFmpeg/OBS) | Done (opaque byte relay) |
| `exvideo_parse` / `exaudio_parse` in session hot path | Not wired — codec detection uses lightweight heuristics |
| `fourCcList` in `connect` | Skipped on read; not sent on connect |
| HDR / `colorInfo` (`metadata.rs`) | Parser only |
| Enhanced sequence-start cache for players | Not implemented (see init-frame cache above) |
| `exvideo_write` / `exaudio_write` helpers | Not present — send raw enhanced payloads via `send_frame` |
### E-RTMP v2
| `capsEx`, `videoFourCcInfoMap`, `reconnect`, `multitrack`, `modex` parse/write | Library code + unit tests |
| v2 capability negotiation in session | Not implemented |
| Reconnect / multitrack / ModEx in session | Not implemented |
### Client, TLS, tests
| Minimal publish client | Done |
| Minimal play client (A/V receive callback) | Done — no metadata/aggregate on play |
| RTMPS | Done |
| Loopback + FFmpeg interop tests | Present (`tests/`, `tests/interop/`) |
Parser-level details and spec mappings: [`docs/protocol-mapping-legacy.md`](docs/protocol-mapping-legacy.md), [`docs/protocol-mapping-ertmp-v1.md`](docs/protocol-mapping-ertmp-v1.md), [`docs/protocol-mapping-ertmp-v2.md`](docs/protocol-mapping-ertmp-v2.md).
---
## License
MIT — see [LICENSE](LICENSE)