oxideav-rtmp
Pure-Rust RTMP for the
oxideav framework — accept an
incoming publisher (server / source) or push your own stream to a
remote RTMP server (client / sink). Zero external dependencies,
blocking-thread-per-connection.
Server (accept a publisher)
use ;
let server = bind?;
let req = server.accept?; // blocks until one publisher connects
if req.stream_name != "my-secret-key"
let mut session = req.accept?; // sends NetStream.Publish.Start
while let Some = session.next_packet?
Multi-client variant — one thread per connection:
server.serve?;
Client (push to a remote RTMP server)
use RtmpClient;
let mut client = connect?;
client.send_video_sequence_header?; // AVCDecoderConfigurationRecord
client.send_audio_sequence_header?; // 2-byte AudioSpecificConfig
loop
client.close?;
Scope
- RTMP (
rtmp://, plain TCP port 1935). No RTMPS yet — wrap ourRead + Writewith rustls if you need it, or request anrtmpsfeature. - Publish direction only. The server accepts incoming publishers; the client pushes to remote servers. RTMP play (subscribe / pull) is a follow-up.
- AMF0 command flow is what every commodity ingest endpoint
(OBS / Wirecast / nginx-rtmp / libavformat) negotiates. The
[
amf3] module ships a complete AMF3 wire-format encoder + decoder — all thirteen markers plus the three reference tables — and AMF3 data / command messages are now routed end-to-end: the server decodesonMetaDatacarried as a type-15 AMF3 data message (per AMF 3 spec §4.1 / AMF 0 spec §3.1, an AMF0 frame switching to AMF3 via theavmplus-object-marker0x11), bridges the AMF3 value graph ontoAmf0Value, and surfaces it through the sameStreamPacket::Metadatapath as AMF0; type-17 AMF3 commands feed the same stream-teardown detection.RtmpClient::send_metadata_amf3emits the AMF3-encoded form for peers on an AMF3 channel. Shared objects, RTMFP, and the Adobe digest-verified handshake remain unimplemented. - H.264 + AAC are the canonical legacy payloads, plus
Enhanced RTMP v1 (Veovera 2023) FourCC video codecs —
hvc1(HEVC / H.265),av01(AV1),vp09(VP9) — and the Enhanced RTMP v2 (Veovera 2026) additions:vp08(VP8),avc1(FourCC-mode AVC / H.264),vvc1(VVC / H.266). Sequence-start (HEVC / VVC / AVCDecoderConfigurationRecord,AV1CodecConfigurationRecord,VPCodecConfigurationRecordfor VP8 + VP9),CodedFrames,CodedFramesX(CTS=0 omitted),SequenceEnd, and the HDRPacketTypeMetadata(colorInfo) frames all round-trip viaflv::parse_video/flv::build_video. SI24compositionTimeOffsetis emitted on the wire for the three NALU-based FourCCs (hvc1/avc1/vvc1) withCodedFramesand stripped forCodedFramesXper the v2 spec. The crate passes through FLV tag bytes verbatim, so additional codecs (MP3, H.263, Speex, …) keep working too. - Enhanced RTMP v2 (Veovera 2026) FourCC audio codecs:
Opus,fLaC(FLAC),ac-3(AC-3),ec-3(E-AC-3),.mp3,mp4a(AAC, added FourCC signalling).SequenceStart,CodedFrames, andSequenceEndAudioPacketTypes round-trip viaflv::parse_audio/flv::build_audio; bodies are the per-codec shapes called out inenhanced-rtmp-v2.pdf§"ExAudioTagBody" (OpusHeadID-header per RFC 7845 §5.1 for Opus SequenceStart,fLaC + STREAMINFOper Xiph FLAC §7 for FLAC SequenceStart, ATSC sync frames for AC-3 / E-AC-3 CodedFrames, MPEG Layer III frames for MP3, ISO/IEC 14496-3AudioSpecificConfigfor FourCC-AAC SequenceStart).MultitrackandMultichannelConfigAudioPacketTypes parse to opaque bodies pending follow-up rounds. - Enhanced RTMP v2
ModExprelude (Veovera 2026). TheModExpacket-type signal (enhanced-rtmp-v2.pdf§"ExVideoTagHeader" / §"ExAudioTagHeader") is decoded for both audio and video: a chain of size-prefixedmodExDataentries (UI8 + 1length, escaping to0xFF+UI16 + 1for 256..=65536-byte payloads) preceding the FourCC, each terminated by amodExType | next-PacketTypenibble byte. The chain round-trips throughflv::parse_*/flv::build_*via the newVideoTag::mod_ex/AudioTag::mod_ex(Vec<flv::ModEx>) fields, and the real PacketType is recovered from the chain so the packet adapters route a ModEx-prefixed tag transparently. The only subtype defined today,TimestampOffsetNano(abytesToUI24sub-millisecond presentation offset, 0..=999_999 ns), is exposed via{Video,Audio}Tag::timestamp_offset_nano; folding that nanosecond offset into the millisecondPackettimeline is a follow-up.
Pipeline integration (SourceRegistry)
Wire rtmp:// URIs into the workspace's
[oxideav_core::SourceRegistry] so the pipeline executor reads
RTMP streams via the same dispatch as file:// and http(s)://:
use SourceRegistry;
let mut reg = new;
register;
// `rtmp://host:port/app/stream-name` opens a one-shot listener
// that accepts a single publisher and surfaces it as a
// `PacketSource` (audio = stream 0, video = stream 1, both with
// time_base 1/1000). Codec ids are auto-detected from the
// publisher's first audio + video tags (h264, hevc, av1, vp9 in
// Enhanced-RTMP-v1 FourCC mode; vp8, h264 (avc1), vvc added in
// Enhanced-RTMP-v2 FourCC mode; h264, h263, vp6f / vp6a, flashsv /
// flashsv2 for legacy single-byte codec ids; opus, flac, ac3,
// eac3, mp3, aac in Enhanced-RTMP-v2 audio FourCC mode; aac, mp3,
// pcm_*, speex, nellymoser for legacy single-byte audio
// sound-format).
let _src = reg.open?;
The opener is listen-style: each open() binds the URL's
host:port, accepts one publisher, validates the announced
app + stream_name against the URL path (rejects on
mismatch), then hands packets to the registry. For multi-client
service, keep using [RtmpServer::serve] directly.
Reusable building blocks
The lower-level modules are public so callers can compose something non-standard:
amf::{encode, decode, encode_command, Amf0Value}amf3::{encode, decode, decode_all, decode_data_message, encode_all, Amf3Value, Decoder}plusAmf3Value::to_amf0()andamf3::AVMPLUS_OBJECT_MARKERchunk::{ChunkReader, ChunkWriter, Message}handshake::{client_handshake, server_handshake}flv::{parse_video, build_video, parse_audio, build_audio, ModEx}message::build_*— builders for every protocol-control / command message we emit