# rtp2httpd: a 500KB C program that puts a whole IPTV headend behind one HTTP port

> The multicast problem, the FCC trick that makes channel changes feel native, and the stack behind a binary small enough to live on a router.

**stackia/rtp2httpd** — Multicast RTP/RTSP to Unicast HTTP stream converter, with built-in web player, status dashboard, fast channel change support, and more!

- Repository: https://github.com/stackia/rtp2httpd
- Website: https://rtp2httpd.com
- Stars: 2,241 · Forks: 358
- Language: C
- License: GPL-2.0
- Published: 2026-10-08 · Updated: 2026-10-08 · Language: en
- Canonical page: https://hysenlabs.com/projects/stackia-rtp2httpd

## The problem is that multicast will not cross a network on its own

Chinese IPTV providers hand subscribers multicast RTP over UDP. That is efficient for the operator, who sends one copy of a channel to a whole neighborhood, and it is a poor fit for a home network, because multicast does not route and most consumer gear will not forward it. Every device on the LAN therefore wants its own unicast stream, which means something has to sit between the operator's multicast and the devices that want HTTP.

That something is what this project is. The repository description is plain about the job: a multicast RTP and RTSP to unicast HTTP converter, with a built in web player, a status dashboard and fast channel change support. The README frames it as an operator tuned build for mainland China networks, and the repository topics say the same thing in shorthand: `iptv`, `multicast`, `rtp-proxy`, `udpxy`, `china-iptv`, `msd-lite`, `fast-channel-change`.

The tool positions itself as a replacement for two predecessors. `udpxy` is the older, simpler answer, and the project advertises full compatibility with its URL format, which matters more than it sounds because a huge amount of existing IPTV configuration is written against that shape. `msd_lite` is the more capable rival, and the benchmark page linked from the README compares all three plus `tvgate` head to head.

## FCC is the feature that separates it from a packet forwarder

Fast Channel Change is an operator side protocol. The mechanism is that the operator's FCC server holds several frames ahead of the live edge, so when a client asks to switch channels it can be handed an IDR frame immediately instead of waiting for the encoder to produce one. A decoder needs that IDR to start, and waiting for a natural keyframe is the multi second delay people associate with IPTV switching.

rtp2httpd implements the operator side of that handshake, and the README credits the industry people who chose to publish the protocol details. The result is a channel change in milliseconds rather than seconds, which is the difference between feeling like a set top box and feeling like a web player.

There is a catch the project states plainly. FCC only shows up if the client asks for it, so you need a player built for the purpose. The README names `mytv-android`, TiviMate and Cloud Stream, and then says directly that common desktop players such as PotPlayer and IINA do not optimise for startup, so the effect is not visible there. That is worth internalising before blaming the server: a fast channel change feature measured through a slow player looks like a broken server. The README also notes the browser player can hit decoding limits, showing as no audio or a black screen on some channels, which is a codec support question rather than a proxying one.

## Where the packet handling work actually goes

A plain RTP to HTTP proxy is not hard. The parts that make this one worth reading are the ones that handle a bad network without visibly degrading playback.

Out of order arrival is handled first, because multicast over a home network with several switches will reorder packets, and reordering is what produces the frozen and mosaic artefacts people report as broken streaming. Forward error correction support covers the other half, restoring dropped packets with Reed-Solomon redundancy when the multicast upstream offers it. The README is careful to qualify that, FEC only helps if the source provides it.

Then there is the multicast sharing model. The README describes sharing one multicast subscription, the RTP processing and the batched buffers across every client watching the same channel within a worker process. This is the difference between N subscriptions and one, and it is the single most important design decision in the project. Buffer pools are preallocated so the hot path does not allocate, and slow clients are handled without letting them consume the resources of everyone else.

For the numbers the README is willing to commit to, 25 concurrent 1080p multicast streams at 8 Mbps each run at 25% of a single core on an i3-N305 with 4MB of memory. The concurrency model is epoll plus multiple worker processes, so cores scale out rather than one process trying to do everything.

## RTSP in, and the reasons catch-up needs it

Multicast is one way in, but the project also speaks RTSP, with the full RTSP and RTP stack and STUN for UDP NAT traversal. The reason shows up in the README's claim that RTSP sources make time shift replay of IPTV possible, which multicast cannot do on its own. Replay needs a server you can request an arbitrary segment from, and that is a unicast protocol.

The catch-up side is visible in the release notes rather than the feature list. The web player splits replay requests on EPG programme boundaries instead of cutting at fixed intervals, so a single request does not straddle two programmes, which the project attributes to compatibility with particular operators. The same notes record a fix for RTSP losing the final interleaved media segment when the server closed the connection.

HLS is handled too, but as a reverse proxy rather than a converter. If an operator publishes HLS on an internal network, rtp2httpd will proxy it outward so devices outside can reach it. The project notes it rewrites `rtsp://`, `rtp://` and `udp://` prefixes in a 302 Location header so the player keeps coming back through the proxy instead of failing on an address it cannot resolve. Channel snapshots are also available over HTTP, which lets a client show a thumbnail without pulling the full stream.

## One binary, and a build pipeline with three toolchains in it

The README describes rtp2httpd as pure C, zero dependencies and 509KB on x86_64, with the web player assets embedded so the whole thing fits on a router, an optical modem or a NAS. The release build is a CMake project with aggressive optimisation on.

```bash
RELEASE_VERSION="${RELEASE_VERSION}" cmake -B build \
    -DCMAKE_BUILD_TYPE=Release \
    -DENABLE_AGGRESSIVE_OPT=ON \
    -DCMAKE_INSTALL_PREFIX=/usr/local \
    -DCMAKE_INSTALL_SYSCONFDIR=/usr/local/etc
```

That claim about the runtime is fair and the claim about the repository is not, and it is worth being precise about which is which. The repository tree lists `web-ui/`, `docs/`, `e2e/`, `tools/` and `scripts/` beside `src/`. The web player is a Vite frontend built by a separate step and then embedded into a generated C header, so the source checkout is not the binary. There is a Python side too, and it demands a recent interpreter.

```toml
[project]
name = "rtp2httpd"
version = "0.1.0"
requires-python = ">=3.14"
dependencies = [
    "scapy>=2.7.0",
    "pytest>=9.1.1",
    "pytest-xdist>=3.8.0",
]
```

That is scapy and pytest behind a Python 3.14 floor, which is where the e2e suite lives. The pytest markers name the four things it exercises: `multicast` for multicast on loopback, `rtsp` for a mock RTSP server, `fcc` for a mock FCC server and `http_proxy` for a mock HTTP upstream. Testing FCC and multicast end to end means standing up fakes of the operator side, and scapy is there so the tests can craft packets rather than only replay real ones.

So a source build wants Node, Python and CMake, while a released binary wants none of them. Both statements are true and they describe different things, which is the practical point: take the prebuilt package if your only goal is running the service.

## Packaging, playlists and the operator dashboard

Deployment targets are broad, and the tree shows the shape of it: `openwrt-support/`, `ikuai-support/` and `wireshark-support/` are directories in the repository, so the packaging work is versioned rather than scattered in wiki pages. The docs cover OpenWrt as the quick start path and add Homebrew and systemd unit instructions, and the release notes mention a new `powerpc_464fp` package for devices on apm821xx silicon, which is the kind of target that tells you how much embedded hardware this is expected to land on.

The container route is a two stage Alpine build, building on the target platform so cross builds go through QEMU, and the runtime stage carries only the binary and the config file.

```dockerfile
# Runtime stage
FROM alpine:3.24

# Copy the built binary and config
COPY --from=builder /workdir/build/rtp2httpd /usr/local/bin/
COPY --from=builder /workdir/rtp2httpd.conf /usr/local/etc/

# Expose the default port
EXPOSE 5140
```

Port 5140 is the default and the whole service listens there, which is the point of the design: one port for streams, one for the player at `/player`, one for the playlist at `/playlist.m3u`, and one for the dashboard at `/status`.

The playlist handling is more than a passthrough. It accepts an external M3U or M3U8 URL, detects RTP and RTSP entries and rewrites them into the proxy form, and processes catch-up source addresses so replay entries survive. A fix in v3.17.0 made it honour several stream addresses under a single `#EXTINF` line, so entries that list a fallback no longer lose it.

The dashboard is the operational half. It lists each client with its IP, state, bandwidth and bytes transferred, tails the server log and lets the log level be changed at runtime, and it can force-disconnect a client. That force-disconnect briefly blocks the client IP for about five seconds, added because otherwise the player reconnects immediately and the action does nothing.

Licensing is GPL-2.0, and the README spells out the consequences rather than leaving them implied. Deploying it inside a company is fine, charging for a forwarding service built on it is fine, and selling hardware with it bundled is fine. Modifying it obliges you to publish the modified source, distributing binaries obliges you to ship the source, and selling it closed is not permitted. The project also credits `oskar456/rtp2httpd` as the original, so the lineage runs through an earlier project of the same name.

On maintenance, the last push was 2026-09-21 and releases v3.17.0 and v3.17.1 landed on 2026-09-09 and 2026-09-11. The 3.17.0 notes quantify the shared subscription work: with several clients on one channel, CPU down 60 to 80 percent and memory down 80 percent, and the benchmark is stated to put it below msd_lite in every scenario tested. Reading that as a controlled comparison is fair since the benchmark lives in the project's own docs, and reading it as a reason to try the tool is also fair.

## Conclusion

rtp2httpd is a good study in what matters when a program has one job and does not have a runtime. The whole server is C with epoll, the binary is 509KB on x86_64, and the memory cost of watching 25 concurrent 1080p multicast streams is quoted at 4MB. Everything else in the repository exists to serve that binary: a Vite frontend that gets embedded into a header, a Python test harness with scapy, a CMake build that a two stage Dockerfile runs on Alpine. The FCC implementation is the part worth studying, because it is the one place where the project does something the older tools it replaces do not. The rough edge is that a from source build needs Node and Python installed before you get the small binary, which sits awkwardly beside the README's zero dependency claim. If you run IPTV and want channel changes that feel like a set top box, this is the project to try, on a host you control.

## FAQ

### Is RTP the same as UDP?

No. UDP is a transport protocol and says nothing about the payload. RTP runs on top of UDP and adds the sequence numbers, timestamps and payload type that make real time media reconstructible, which is why a proxy has to understand RTP itself rather than just forward bytes. rtp2httpd parses those fields, which is what lets it reorder packets and apply forward error correction instead of blindly relaying datagrams.

### Are multicast and UDP the same?

They are related but they answer different questions. UDP is how a packet gets from one host to one other host, while multicast is a delivery mode that uses UDP to reach a group that has joined a multicast group address. A multicast datagram is still carried over UDP, which is exactly why it needs a proxy to reach devices one at a time.

### Can RTSP be used for multicast?

RTSP is a session control protocol and it is normally used over unicast, with the media carried in RTP over UDP or TCP. Multicast delivery of the media is a separate mechanism, and the usual arrangement for IPTV is multicast RTP straight to the client with no RTSP session at all. rtp2httpd handles both paths, and the RTSP side is what enables time shift replay, because replay needs a server you can request segments from rather than a one way broadcast.

## Sources

- [License: GPL-2.0](https://github.com/stackia/rtp2httpd/blob/main/LICENSE)
- [Project website](https://rtp2httpd.com)
- [README](https://github.com/stackia/rtp2httpd/blob/main/README.md)
- [Releases](https://github.com/stackia/rtp2httpd/releases)
- [stackia/rtp2httpd on GitHub](https://github.com/stackia/rtp2httpd)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/stackia-rtp2httpd
