Minimal room-management + RTP forwarder/transcoder.
| Piece | Language | Role |
|---|---|---|
proto/room.proto |
Protobuf | Room control API |
go/ |
Go + gRPC | Room service (control plane) |
cpp/ |
C++17, no third-party libs | RTP stack, G.711 transcode, UDP forwarder |
The C++ process never links protobuf, gRPC, Boost, FFmpeg, or libnice. Sockets are raw POSIX. RTP is RFC 3550 from scratch. “Transcode” is G.711 μ-law (PT 0) ↔ A-law (PT 8), which is the one codec pair you can implement correctly without a media library.
participant A C++ rtp_forwarder participant B
RTP/UDP --------------------> ingest :portA
parse RTP
optional G.711 convert
rewrite PT
---------------------- RTP/UDP -> B egress
Go is the source of truth for rooms. On CreateRoom / JoinRoom / LeaveRoom it pushes line-oriented commands to the forwarder:
CREATE_ROOM <room_id>
ADD_PARTICIPANT <room> <pid> <ingest_port> <egress_ip> <egress_port> <ssrc> <send_pt> <recv_pt>
REMOVE_PARTICIPANT <room> <pid>
DELETE_ROOM <room_id>
Dependencies: g++, cmake, go ≥ 1.22, protoc, protoc-gen-go, protoc-gen-go-grpc.
# plugins (once)
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.2
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.4.0
export PATH="$(go env GOPATH)/bin:$PATH"
make
make testBinaries:
cpp/build/rtp_forwardercpp/build/rtp_testscpp/build/rtp_send/rtp_recv— demo sender/receiverbin/roomservice/bin/roomclient
Terminal 1 — media plane:
./cpp/build/rtp_forwarder --control-port 9000Terminal 2 — control plane:
./bin/roomservice --listen :50051 --forwarder 127.0.0.1:9000Terminal 3 — create a room, two participants. A sends μ-law, wants μ-law. B sends A-law, wants A-law. The forwarder converts in both directions.
RID=$(./bin/roomclient create demo | awk '{print $2}')
./bin/roomclient join "$RID" alice 127.0.0.1 20001 pcmu pcmu
./bin/roomclient join "$RID" bob 127.0.0.1 20002 pcma pcma
./bin/roomclient get "$RID"Note the ingest ports printed for alice and bob.
Terminal 4 — Bob listens for the forwarded stream:
./cpp/build/rtp_recv --port 20002 --seconds 8Terminal 5 — Alice sends μ-law into her ingest port (replace 10000 with alice’s ingest):
./cpp/build/rtp_send --dest 127.0.0.1:10000 --pt 0 --ssrc 1 --seconds 5rtp_recv should print packets with pt=8 (transcoded to A-law for Bob).
cpp/include/rtp/rtp_packet.h RFC 3550 parse/serialize (V/P/X/CC/M/PT/seq/ts/ssrc/CSRC/ext/pad)
cpp/include/rtp/g711.h μ-law ↔ linear ↔ A-law
cpp/include/rtp/udp_socket.h POSIX UDP
cpp/include/rtp/forwarder.h per-room SFU-style fan-out
cpp/include/rtp/control_server.h
Packet headers are always written big-endian. Padding is stripped on parse and re-applied on serialize. Unknown payload-type pairs are forwarded unchanged.
proto/room.proto
go/internal/room/ in-memory store, port pool, gRPC service
go/internal/forwarder/ TCP client for the C++ control port
go/cmd/roomservice
go/cmd/roomclient