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.
// The core of a WebRTC media server
PacketStream stream;
stream.attachSource(capture.get());
stream.attach(&session->media().videoSender(), 5);
stream.start();Icey is the connective tissue: a modular C++20 toolkit that unifies FFmpeg, libuv, OpenSSL, and libdatachannel into a single composable pipeline. Capture, encode, transport, signalling, and relay. All dependencies managed via CMake FetchContent. Builds in minutes.
Documentation | Changelog | Contributing | LGPL-2.1+
| libWebRTC (Google) | libdatachannel | GStreamer | Icey | |
|---|---|---|---|---|
| Build system | GN/Ninja | CMake | Meson | CMake |
| Build time | Hours | Minutes | 30+ min | Minutes |
| Binary size | 50MB+ | Small | Large | Small |
| SSL | BoringSSL (conflicts) | OpenSSL | OpenSSL | OpenSSL |
| Media codecs | Bundled | None | GObject plugins | FFmpeg (any codec) |
| Capture/encode | Included | No | Plugin pipeline | PacketStream pipeline |
| Signalling | No | No | No | Symple (built-in) |
| TURN server | No | No | No | RFC 5766 (built-in) |
| Language | C++ | C++17 | C/GObject | C++20 |
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. The pipeline handles backpressure, frame dropping, and teardown so you don't. Nothing runs that you didn't ask for.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PacketStream β
β β
β ββββββββββββ ββββββββββββββββ βββββββββββββββββββββββββ β
β β Source βββββΆβ Processor βββββΆβ Sink β β
β β β β β β β β
β β Camera β β FFmpeg H.264 β β WebRTC Track Sender β β
β β File β β Opus encode β β Network socket β β
β β Network β β OpenCV β β File recorder β β
β β Device β β Custom β β HTTP response β β
β ββββββββββββ ββββββββββββββββ βββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
WebRTC send path:
MediaCapture β VideoEncoder β WebRtcTrackSender β [libdatachannel]
β
Browser βββ RTP/SRTP βββ DTLS βββ ICE (libjuice) βββββ
β
Icey TURN server
(relay for symmetric NATs)
WebRTC receive path:
[libdatachannel] β WebRtcTrackReceiver β FFmpeg decode β file/display
β
ββββ ICE β DTLS β SRTP decrypt β RTP depacketise β raw frames
Signalling (Symple v4):
C++ server/client βββββ WebSocket βββββΆ Browser (symple-client-player)
Auth, presence, rooms, call protocol (init/accept/offer/answer/candidate)
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. Data channel for seek commands. Build your own streaming service.
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.56ms |
Icey 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/samples/httpbenchmark/ for methodology.
| Platform | Compiler |
|---|---|
| Linux | GCC 12+ or Clang 15+ |
| macOS | AppleClang 15+ (Xcode 15+) |
| Windows | MSVC 2022 (Visual Studio 17+) |
CMake 3.21+ and pkg-config (Linux/macOS) required. Everything else is fetched automatically:
| Dependency | Version |
|---|---|
| libuv | 1.50 |
| llhttp | 9.2.1 |
| OpenSSL | 3.x |
| nlohmann/json | 3.11.3 |
| zlib | 1.3.1 |
Optional: FFmpeg 5+/6+/7+ (-DWITH_FFMPEG=ON), OpenCV 3.0+ (-DWITH_OPENCV=ON), libdatachannel (-DWITH_LIBDATACHANNEL=ON).
git clone https://github.com/sourcey/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-failureinclude(FetchContent)
FetchContent_Declare(icey
GIT_REPOSITORY https://github.com/sourcey/icey.git
GIT_TAG v2.1.0
)
FetchContent_MakeAvailable(icey)
target_link_libraries(myapp PRIVATE icy_base icy_net icy_http)After installing (cmake --install build):
find_package(Icey REQUIRED)
target_link_libraries(myapp PRIVATE icy_base icy_net icy_http)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.mediaOpts.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);
};14 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) |
| 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 |
| pluga | Plugin system (shared library loading) |
| pacm | Package manager for plugin distribution |
| sched | Task scheduler for deferred/periodic jobs |
- Kam Low (@auscaster) - Creator and primary developer
- Sergey Parfenyuk (@sparfenyuk) - macOS compile fixes, type corrections, buffer handling
- Yury Shubin (@yuryshubin) - iOS build toolchain and platform fixes
- Norm Ovenseri (@normano) - Apple/FFmpeg builds, AVFoundation support, verbose logging
- Igor Lutsyk (@lutsykigor) - WebRTC/WebSocket fixes, OpenCV+WebRTC sample app, Firefox compatibility
- Kryton (@Malesio) - Segfault fixes and Valgrind cleanup
- Vinci Xu (@VinciShark) - Windows documentation, testing and updates
- Michael Fig (@michael-fig) - Compiler flags for building without FFmpeg
- Stanislav Kapulkin (@kapulkin) - WebRTC modernisation and macOS compile definitions
- Thomas Reichhart (@blackforest-tom) - FFmpeg constant updates and ARM build fixes
- Artem Suprunov (@artemiuzzz) - WebRTC null pointer fix and library path resolution
- Hyunuk Kim (@surinkim) - Windows
std::codecvtunicode conversion fix - Cameron Smith (@cksmith) - Git line ending normalisation
- Damian Zelim (@ZelimDamian) - OS X compiler flag fixes
- Alexey (@deilos) - Cross-platform FFmpeg build script fixes
PRs welcome. See the contributing guide for code style, tests, and workflow.