Repository navigation
librtmp2 0.12.0
Added
- Native Windows support (x86_64 and ARM64, MSVC) for RTMP and RTMPS, client
and server. All socket calls now go throughsrc/net/, with a Unix backend
(recv/send/sendmsg/poll, unchanged) and a Winsock backend
(recv/send/WSASend/WSAPoll), selected at compile time. See
docs/windows.md. net::RawSocket/net::INVALID_SOCKET(the platform socket handle type and
its "no socket" value) andTransport::from_tcp_stream().- Cross-platform loopback integration tests (
tests/cross_platform_transport.rs)
and native Windows x64/ARM64 CI jobs.
Changed
- Socket-handle fields and methods (
client_fd,server_fd,
listener_fds(),Transport::fd(),Transport::new_plain(),
TlsCtx::accept()) usenet::RawSocket, which isi32on Unix (no change)
andu64on Windows. Server::listen_reuseport()/listen_tls_reuseport()return
ErrorCode::Unsupportedon Windows, which has noSO_REUSEPORT.- On Unix the transport now puts its socket into
O_NONBLOCKmode when it
takes it over (as the Windows backend does withFIONBIO). macOS ignores
MSG_DONTWAITonsend/sendmsg, so a plaintext client could block once
the socket buffer was full. On Windows a socket that cannot be made
non-blocking is closed instead of stalling the poll loop. examples/minimal_serverbinds127.0.0.1:1935by default (pass an
address as the first argument to expose it), because it accepts every
publish and play request.- CI builds, tests and
cargo publishuse the committedCargo.lock
(--locked). - A release tag is built and tested on macOS and Windows (x86_64 and ARM64)
before anything is published; previously only Linux gated the release. - CI benchmark history is kept in
bench/onmain(bench/latest.json,
bench/releases/<tag>.json) instead of a separatebench-databranch;
bench/is excluded from the published crate.
Fixed
- Async publish/play authorization: a decision that arrives after its stream
was torn down or replaced (FCUnpublish/deleteStream,closeStream, a
newcreateStream) is a silent no-op instead of authorizing whatever
request came next. The withdrawn request keeps its slot until the stale
completion or the timeout clears it, sohas_pending_authorization()stays
trueuntil then. - A pending authorization reaped by the 15 s timeout now also closes the
connection: the deny response is flushed, then the server tears the
connection down in the same tick, so the client has to reconnect to try
again. The reaped slot stays sealed, so a completion that arrives later can
never authorize a newer request. - A republish on an already-publishing connection (for example a stream
rename) no longer keeps relaying media under the old authorization while
the new decision is pending, and a deferred-relay publisher's media counts
toward the squat deadline. poll(2)interrupted by a signal (EINTR) during a blocking send or a TLS
accept is retried instead of failing the connection withIo.- The client's receive loop retries the read after a TLS
WANT_WRITEonce
the socket is writable, instead of giving up on a writable socket. - Chunk reader: a fmt=3 chunk that starts a new message right after a fmt=0
message repeats the fmt=0 timestamp as its delta (RTMP spec 5.3.1.2.4),
instead of reusing the previous message's timestamp. - The client handles
AbortMessagein all of its dispatch paths and discards
the partial message on the aborted CSID. - Legacy FLV
SoundFormat14 is MP3 8 kHz, not Opus (Opus is only signaled
through the E-RTMP ExHeader); reserved legacy sound format nibbles are
rejected, and unregistered enhanced FourCCs are no longer labeled H.264. AMF3_DATAmessages have their one-byte AMF0 marker stripped even when it
is the whole body, and re-parsingconnector theconnect_resultinto
a reusedConnectInfo/NegotiatedCapsno longer keeps stale optional
fields.- The init cache ignores unpeeled ModEx wrappers instead of parsing them as
an ex-header, and a route whose only cached video is the combined
avc_headeris classified as multitrack so congested players can resync
on audio. - Client: a fatal
recverror endspoll()withIoinstead of returning
Okforever (which busy-spun embedders on a reset socket); a failed
connect()resets the previous session so a laterpublish()cannot
stream to the old server; a full send buffer while publishing is treated as
"not writable yet" instead of aborting the session; and a receive that ends
on a partial chunk no longer spins. - The transport's 10 s send poll timeout is an absolute deadline instead of
restarting on every iteration. examples/minimal_serverregisters its publish/play callbacks so it
actually accepts streams,examples/minimal_clientsends the frame type it
declares, andbench_handshake/bench_relayreject--count 0and stop
on a dead viewer.scripts/bench_report.pyreads and writes its reports as UTF-8 and retries
the baseline fetch, keeping the measurements when it fails.- Broken links in the API documentation (
AuthDecision,split_host_port,
Conn); CI now builds the docs with warnings as errors.
Benchmarks
Measured automatically for this release on a CI runner; the machine is described below. Raw data: bench-results-v0.12.0.json.
- Measured: 2026-10-10 17:07:41 UTC ·
ba44578· crate version 0.12.0 · workflow run - CPU: AMD EPYC 7763 64-Core Processor · 4 vCPUs
- RAM: 15.6 GiB
- System: Linux 6.17.0-1022-azure x86_64 · rustc 1.99.0 · GitHub-hosted ubuntu24 (image 20261004.327.1)
- Noise: shared CI VM — CPU model, neighbouring load and the runner image change between runs, so treat absolute numbers as indicative.
No baseline yet (no release or previous main run has been recorded).
protocol
| Benchmark | Time | Throughput |
|---|---|---|
amf0_build_connect |
219 ns | — |
chunk/write_read_roundtrip |
5.89 µs | ~663 MiB/s |
flv/audio_tag_aac |
1.25 ns | — |
flv/video_tag_h264 |
1.25 ns | — |
fourcc_to_video_codec_avc1 |
2.49 ns | — |
server_read_c1 |
1.07 µs | — |
relay
| Benchmark | Time | Throughput |
|---|---|---|
relay/publish_to_player/100 |
97.0 ms | ~1031 elem/s |
relay/publish_to_player/500 |
100.3 ms | ~4984 elem/s |
🟢 faster · 🔴 slower · ⚪ within the noise band (5–25 % depending on how comparable the two environments are, plus both runs' Criterion confidence intervals). Lower time is better.