Real-time media stack and lightweight libwebrtc alternative, built in C++20
Real-time media stack and lightweight libwebrtc alternative, built in C++20
The C++ Media Stack
WebRTC, FFmpeg, and async networking in one toolkit. No Google monolith. No dependency hell. No fighting three build systems to get a frame on screen.
// HEVC + AAC, captured through encoder to network in one zero-copy pipeline
av::EncoderOptions opts;
opts.oformat = {"mpegts", "mpegts",
{"H265", "hevc_videotoolbox", 1920, 1080, 60},
{"AAC", "aac", 2, 48000}};
PacketStream stream;
stream.attachSource(&capture);
stream.attach(new av::MultiplexPacketEncoder(opts), 5);
stream.attach(&socket, 10);
stream.start();
icey is the connective tissue: a modular C++20 toolkit that pulls FFmpeg, libuv, OpenSSL, llhttp, libdatachannel, Symple, STUN, and TURN into one runtime model. Capture, encode, transport, signalling, and relay. Core third-party code is pulled in by CMake; system TLS and media dependencies are auto-detected. Builds in minutes.
Documentation | Changelog | Contributing | LGPL-2.1+
Rust bindings are split into two crates:
icey is the safe Rust wrapper most users
should depend on.icey-sys is the raw bindgen layer
over icey's graft C ABI.Both crates use https://0state.com/icey as their Cargo homepage and
https://0state.com/icey/docs as their Cargo documentation URL so crates.io,
docs.rs, lib.rs, Libraries.io, and other registry crawlers resolve the Rust
package listings back to the product and docs surfaces.
If you want the shortest path from zero to browser video, use the published icey-server image from the separate icey-cli repo.
One command. One URL. One click.
docker run --rm --network host 0state/icey-server:latest
Then open http://localhost:4500 and click Watch on the icey peer.
This express path ships from nilstate/icey-cli and starts stream mode with the bundled demo source. If you want the repo-backed app path for local edits or richer runtime control, use the separate icey-cli app surface on top of the core icey modules.
libdatachannel gives you the WebRTC transport pipe. icey gives you the pipe, the water, and the faucet.
Everything flows through PacketStream. Plug in a source, chain processors, attach a sink. Borrowed packets stay zero-copy until the first queue or retained adapter; that boundary is explicit in the graph. The pipeline handles backpressure, frame dropping, and teardown so you don't. Nothing runs that you didn't ask for. Decoded branches can feed vision and speech processors without changing the transport path.
…
Camera to browser in 150 lines. Browser to file in 130. The pipeline handles the plumbing.
150 lines of C++. Camera capture, H.264 encoding, WebRTC transport, Symple signalling. Open a browser, see video. No plugins, no Google, no pain.
// Accept call, wire up the pipeline, stream
session.IncomingCall += [&](const std::string& peerId) {
session.accept();
};
session.StateChanged += [&](wrtc::PeerSession::State state) {
if (state == wrtc::PeerSession::State::Active) {
stream.attachSource(capture.get());
stream.attach(&session->media().videoSender(), 5);
stream.start();
}
};
See src/webrtc/samples/webcam-streamer/ or read WebRTC in 150 Lines of C++.
Browser sends WebRTC, your C++ server decodes with FFmpeg, writes to any format. Video depositions, telehealth recording, proctoring - server-side recording without cloud vendor lock-in.
See src/webrtc/samples/media-recorder/.
Feed an MP4 in, get a real-time WebRTC stream out. Includes a simple data channel for control messages alongside the media stream.
See src/webrtc/samples/file-streamer/.
Production-grade RFC 5766 TURN server with channel binding and TCP support. Stop paying for hosted TURN. ~30% of real-world WebRTC connections need relay through symmetric NATs; this handles them.
See src/turn/samples/turnserver/.
72,000 req/s with keep-alive on a single-core micro VM. Built on the same libuv + llhttp that powers Node.js, minus the runtime, GC, and language bridge.
Server Req/sec Latency Raw libuv+llhttp 96,088 1.04ms icey 72,209 1.43ms Go 1.25 net/http 53,878 2.31ms Node.js v20 45,514 3.56msicey delivers 75% of raw libuv throughput while providing a complete HTTP stack (connection management, header construction, WebSocket upgrade, streaming responses). It outperforms Go's net/http by 34% and Node.js by 59%. All three share the same foundation (libuv for async IO, llhttp for HTTP parsing); the difference is pure runtime overhead.
See src/http/perf/ for the cross-stack methodology, and src/http/bench/ for the reportable HTTP microbenchmarks.
If you just want the right page:
CMake 3.21+ and pkg-config (Linux/macOS) required. icey fetches its core bundled third-party code automatically:
Dependency Version libuv 1.50 llhttp 9.2.1 nlohmann/json 3.11.3 zlib 1.3.1System dependencies such as OpenSSL 3.x, FFmpeg 5+/6+/7+, and OpenCV 3.0+ are auto-detected when present. libdatachannel is fetched automatically when building the webrtc module.
git clone https://github.com/nilstate/icey.git
cd icey
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON
cmake --build build --parallel $(nproc)
ctest --test-dir build --output-on-failure
include(FetchContent)
FetchContent_Declare(icey
GIT_REPOSITORY https://github.com/nilstate/icey.git
GIT_TAG 2.5.0
)
FetchContent_MakeAvailable(icey)
target_link_libraries(myapp PRIVATE icey::base icey::net icey::http)
After installing (cmake --install build):
find_package(icey REQUIRED)
target_link_libraries(myapp PRIVATE icey::base icey::net icey::http)
Public registry submission is not live yet, but the repo now carries local package-manager support:
…
The Conan recipe lives at packaging/conan/conanfile.py, the vcpkg overlay port lives at packaging/vcpkg/icey/, the Arch packaging files live at packaging/arch/, the Homebrew tap formulae live at packaging/homebrew/Formula/, and the Debian / PPA seed lives at packaging/debian/debian/.
For the release/tag/archive-pin flow behind those package managers, use docs/releasing.md.
Camera to encoder to network:
PacketStream stream;
stream.attachSource(videoCapture);
stream.attach(new av::MultiplexPacketEncoder(opts), 5);
stream.attach(socket, 10);
stream.start();
http::Server srv{ "127.0.0.1", 1337 };
srv.Connection += [](http::ServerConnection::Ptr conn) {
conn->Payload += [](http::ServerConnection& conn, const MutableBuffer& buffer) {
conn.send(bufferCast<const char*>(buffer), buffer.size());
conn.close();
};
};
srv.start();
wrtc::PeerSession::Config config;
config.rtcConfig.iceServers.emplace_back("stun:stun.l.google.com:19302");
config.media.videoCodec = av::VideoCodec("H264", "libx264", 1280, 720, 30);
wrtc::SympleSignaller signaller(client);
wrtc::PeerSession session(signaller, config);
session.IncomingCall += [&](const std::string& peerId) {
session.accept();
};
session.StateChanged += [&](wrtc::PeerSession::State state) {
if (state == wrtc::PeerSession::State::Active)
startStreaming(session);
};
16 modules. Include only what you need; dependencies resolve automatically.
Module What it does base Event loop (libuv), signals, streams, logging, filesystem, timers crypto Hashing, HMAC, RSA, X509 (OpenSSL 3.x) net TCP, SSL/TLS, UDP sockets, DNS http HTTP server/client, WebSocket, cookies, streaming, keep-alive json JSON serialisation (nlohmann/json) av FFmpeg capture, encode, decode, record, stream (FFmpeg 5/6/7) speech Audio intelligence primitives for decoded media streams vision Video intelligence primitives for sampled decoded frames symple Real-time messaging, presence, rooms, WebRTC call signalling stun RFC 5389 STUN for NAT traversal turn RFC 5766 TURN relay server webrtc WebRTC via libdatachannel: media bridge, peer sessions, codec negotiation archo ZIP/archive handling graft Native plugin ABI and shared-library loading pacm Package manager for plugin distribution sched Task scheduler for deferred/periodic jobsNo open issues yet, or sync has not completed.