|
| 1 | +# encoded-video-ingest |
| 2 | + |
| 3 | +End-to-end demo of encoded video ingest in the LiveKit C++ SDK. The |
| 4 | +producer uses `livekit::EncodedTcpIngest`, the high-level SDK helper that owns |
| 5 | +the TCP reconnect loop, H.26x access-unit splitting, keyframe feedback, stats, |
| 6 | +and track publishing. |
| 7 | + |
| 8 | +## Prerequisites |
| 9 | + |
| 10 | +- A LiveKit server, such as `livekit-server --dev`. |
| 11 | +- GStreamer 1.22+ with `good`, `bad`, `ugly`, and `libav` plugins. |
| 12 | +- Two LiveKit tokens for the same room: |
| 13 | + |
| 14 | +```bash |
| 15 | +export LIVEKIT_URL=ws://localhost:7880 |
| 16 | +export PRODUCER_TOKEN="$(lk token create -r encoded-video-demo -i encoded-sender --join --publish)" |
| 17 | +export CONSUMER_TOKEN="$(lk token create -r encoded-video-demo -i encoded-receiver --join --subscribe)" |
| 18 | +``` |
| 19 | + |
| 20 | +# Setup Validation |
| 21 | + |
| 22 | +**Before bringing LiveKit into the picture**, confirm your camera encode path |
| 23 | +and a basic H.264 decode preview work in pure GStreamer. The **send** and |
| 24 | +**receive** commands below use the **same UDP port (5005)** on purpose: |
| 25 | +`udpsink` sends RTP to `127.0.0.1:5005` and `udpsrc` binds `port=5005` for a |
| 26 | +quick local check. |
| 27 | + |
| 28 | +That is only for this camera-validation hop. In the full LiveKit demo below, |
| 29 | +**port 5005** is reserved for **TCP** from the camera pipeline into the |
| 30 | +producer, and **port 5006** is where the consumer serves **decoded I420** to a |
| 31 | +separate GStreamer visualizer. |
| 32 | + |
| 33 | +### Send - camera to RTP/UDP 5005 |
| 34 | + |
| 35 | +macOS (`avfvideosrc`). Linux: replace the source with |
| 36 | +`v4l2src device=/dev/video0`. Windows: `mfvideosrc device-index=0`. If the |
| 37 | +camera cannot produce 640x480 natively, add `videoscale ! videorate !` before |
| 38 | +`x264enc` and relax the first caps filter as needed. |
| 39 | + |
| 40 | +```bash |
| 41 | +gst-launch-1.0 -v \ |
| 42 | + avfvideosrc ! \ |
| 43 | + video/x-raw,width=640,height=480,framerate=30/1 ! \ |
| 44 | + videoconvert ! \ |
| 45 | + x264enc tune=zerolatency bitrate=1000 speed-preset=ultrafast key-int-max=30 ! \ |
| 46 | + video/x-h264,profile=baseline ! \ |
| 47 | + rtph264pay pt=96 config-interval=1 ! \ |
| 48 | + udpsink host=127.0.0.1 port=5005 |
| 49 | +``` |
| 50 | + |
| 51 | +### Receive - RTP/UDP 5005 to display |
| 52 | + |
| 53 | +```bash |
| 54 | +gst-launch-1.0 -v \ |
| 55 | + udpsrc port=5005 caps="application/x-rtp,media=video,encoding-name=H264,payload=96" ! \ |
| 56 | + rtph264depay ! \ |
| 57 | + avdec_h264 ! \ |
| 58 | + videoconvert ! \ |
| 59 | + autovideosink |
| 60 | +``` |
| 61 | + |
| 62 | +On macOS, if `autovideosink` hangs at `PREROLLING`, replace it with |
| 63 | +`osxvideosink`. |
| 64 | + |
| 65 | +This path validates camera, encoder, and decoder. It is **not** the same wire |
| 66 | +format as the ingest path: the demo ingest uses **TCP** and **Annex-B** with |
| 67 | +**AUD-delimited** access units. For that path use `x265enc` with AUD enabled, |
| 68 | +`h265parse`, and `tcpserversink` as shown below. |
| 69 | + |
| 70 | +### Debugging a blank / green receive window |
| 71 | + |
| 72 | +Before blaming the network, collapse encode to decode into a single local |
| 73 | +pipeline. A green square here means the encoder is being fed buffers it cannot |
| 74 | +consume, such as the wrong pixel format, GL memory, or no frames at all: |
| 75 | + |
| 76 | +```bash |
| 77 | +gst-launch-1.0 -v \ |
| 78 | + avfvideosrc device-index=0 ! \ |
| 79 | + video/x-raw,width=640,height=480,format=NV12,framerate=30/1 ! \ |
| 80 | + videoconvert ! \ |
| 81 | + x264enc tune=zerolatency speed-preset=ultrafast bitrate=1000 key-int-max=60 aud=true ! \ |
| 82 | + h264parse config-interval=1 ! avdec_h264 ! videoconvert ! autovideosink sync=false |
| 83 | +``` |
| 84 | + |
| 85 | +Common causes of a green or all-black preview: |
| 86 | + |
| 87 | +- **macOS camera permission.** Grant your terminal app Camera access in |
| 88 | + *System Settings -> Privacy & Security -> Camera* and relaunch it. |
| 89 | +- **`memory:GLMemory` on the source pad.** Pinning `format=NV12` on the first |
| 90 | + caps filter forces a CPU buffer. |
| 91 | +- **Caps pinned to a mode the camera cannot produce.** Run |
| 92 | + `gst-device-monitor-1.0 Video/Source` and pick a listed `video/x-raw` mode. |
| 93 | + |
| 94 | +## 1. Encode a webcam to TCP port 5005 using H.265 |
| 95 | + |
| 96 | +`tcpserversink` listens on **TCP** port **5005**. Stop any other TCP listener |
| 97 | +on that port before starting this command. |
| 98 | + |
| 99 | +```bash |
| 100 | +gst-launch-1.0 -v \ |
| 101 | + avfvideosrc device-index=0 ! \ |
| 102 | + video/x-raw,width=640,height=480,format=NV12,framerate=30/1 ! \ |
| 103 | + videoconvert ! \ |
| 104 | + x265enc tune=zerolatency speed-preset=ultrafast bitrate=1000 key-int-max=60 \ |
| 105 | + option-string="aud=1:repeat-headers=1" ! \ |
| 106 | + h265parse config-interval=1 ! \ |
| 107 | + video/x-h265,stream-format=byte-stream,alignment=au ! \ |
| 108 | + tcpserversink host=0.0.0.0 port=5005 |
| 109 | +``` |
| 110 | + |
| 111 | +Linux: replace `avfvideosrc device-index=0` with |
| 112 | +`v4l2src device=/dev/video0`. Windows: use `mfvideosrc device-index=0`. |
| 113 | + |
| 114 | +## 2. Run the producer |
| 115 | + |
| 116 | +```bash |
| 117 | +LIVEKIT_URL=ws://localhost:7880 LIVEKIT_TOKEN="$PRODUCER_TOKEN" \ |
| 118 | + ./build/encoded-video-ingest/EncodedVideoIngestProducer \ |
| 119 | + --tcp-host 127.0.0.1 --tcp-port 5005 \ |
| 120 | + --width 640 --height 480 \ |
| 121 | + --codec h265 |
| 122 | +``` |
| 123 | + |
| 124 | +The producer starts `EncodedTcpIngest`, publishes a camera track named |
| 125 | +`encoded-video`, and logs stats every two seconds. |
| 126 | + |
| 127 | +## 3. Run the consumer |
| 128 | + |
| 129 | +```bash |
| 130 | +LIVEKIT_URL=ws://localhost:7880 LIVEKIT_TOKEN="$CONSUMER_TOKEN" \ |
| 131 | + ./build/encoded-video-ingest/EncodedVideoIngestConsumer \ |
| 132 | + --tcp-port 5006 \ |
| 133 | + --from encoded-sender |
| 134 | +``` |
| 135 | + |
| 136 | +The consumer subscribes to `encoded-sender` and serves decoded I420 frames on |
| 137 | +TCP port 5006. |
| 138 | + |
| 139 | +## 4. View the consumer output |
| 140 | + |
| 141 | +```bash |
| 142 | +gst-launch-1.0 -v \ |
| 143 | + tcpclientsrc host=127.0.0.1 port=5006 ! \ |
| 144 | + rawvideoparse width=640 height=480 format=i420 framerate=30/1 ! \ |
| 145 | + videoconvert ! autovideosink sync=false |
| 146 | +``` |
| 147 | + |
| 148 | +`rawvideoparse` needs the exact width and height that the producer declared. |
| 149 | +The consumer output is raw I420, not H.265, so do not pipe port 5006 through |
| 150 | +`h265parse`. |
0 commit comments