chilly-soccer-432
12/28/2025, 6:14 PMtall-airline-16756
12/28/2025, 6:14 PMtall-airline-16756
12/28/2025, 6:14 PMchilly-soccer-432
12/28/2025, 6:15 PMtall-airline-16756
12/28/2025, 6:16 PMcreateLocalVideoTrack from frames, or server-side ingestion?)
• What pixel format are your FFmpeg frames in when sent to the browser (e.g., YUV420p, NV12, RGB24)? If you’re piping raw frames, what pixel format did you specify to FFmpeg?
• Are you converting or drawing frames into a Canvas before sending to LiveKit, or sending raw byte frames directly?
• Do you see any warnings or errors in the browser console or LiveKit server logs when the stream starts?
• Can you confirm the browser and OS where this happens and whether other browsers show the same issue?
Quick likely cause: a pixel-format / YUV↔️RGB mismatch (green tint/solid green usually means chroma channels are misinterpreted). If you prefer, share the FFmpeg command you use to produce the frames and the code snippet that creates/sends the track — I’ll point out exactly where to convert formats or adjust rendering.chilly-soccer-432
12/28/2025, 6:23 PMtall-airline-16756
12/28/2025, 6:24 PMimport cv2
rgb = cv2.cvtColor(bgr_frame, cv2.COLOR_BGR2RGB)
buf = rgb.tobytes()
video_frame = rtc.VideoFrame(
type=rtc.VideoFrameType.RGB24,
width=rgb.shape[1],
height=rgb.shape[0],
data=buf,
)
video_source.capture_frame(video_frame)
2) If you want YUV (I420/NV12)
• You must convert properly to planar YUV and pack planes in the expected order/size. Passing RGB/BGR bytes as I420/NV12 will corrupt chroma and produce green artifacts.
• Example conversion (I420) using OpenCV:
yuv = cv2.cvtColor(bgr_frame, cv2.COLOR_BGR2YUV_I420)
# yuv is planar: Y then U then V — pack as bytes and use VideoFrameType.I420 with correct sizes
• Confirm byte lengths: RGB24 = W_H_3; I420 = W_H_1.5; NV12 = W_H_1.5 with appropriate plane order.
3) Quick checks to validate
• Save and inspect the exact bytes you send for a single frame (compare channel values at a pixel).
• Publish a simple known-pattern frame (solid red/green/blue) and verify what the browser shows — this quickly shows channel swaps.
• Inspect <chrome://webrtc-internals> to confirm codec/frame info.
If converting BGR→RGB and publishing RGB24 doesn't fix it, please paste a minimal reproducible snippet (server code that builds the VideoFrame and the exact VideoFrameType used). I’ll review the bytes/packing and point out the exact mismatch.
Sources: client-sdk-unity-web | livekit/livekit_composite | DeepWiki | livekit/livekit_composite | DeepWikichilly-soccer-432
12/28/2025, 6:25 PMtall-airline-16756
12/28/2025, 6:25 PMdef aligned_stride(width, bpp=3, align=16):
return (width * bpp + align - 1) & ~(align - 1)
stride = aligned_stride(width, 3, 16)
padded = np.zeros((height, stride // 3, 3), dtype=np.uint8) # or shape (height, stride, 3) if using bytes
padded[:, :width, :] = rgb # rgb already converted BGR->RGB
data = padded.tobytes()
# use VideoFrame with data
• If the Python API exposes a `stride`/`row_stride` field, set it to the aligned value. If it does NOT, ensure the buffer you pass already has the aligned row layout.
2) Verify plane packing for YUV formats (if using I420/NV12)
• For I420, pack as Y plane (W*H) then U (W/2 * H/2) then V (W/2 * H/2). For NV12, pack Y then interleaved UV.
• Confirm total byte length equals expected (I420 = W_H_1.5).
• Use OpenCV conversion helpers (e.g., cv2.COLOR_BGR2YUV_I420) but then verify the exact plane sizes/order you pass to VideoFrame.
3) Quick sanity tests to isolate:
• Publish a single solid-color test frame (pure red, green, blue) after conversion/stride changes. If colors map correctly, it's fixed; if red/blue swap or green persists, stride/packing still wrong.
• Force encoder to VP8 or H264 when publishing to rule out codec-specific decode issues.
4) Debug dump
• Save the exact bytes you pass (e.g., frame.bin). Play with ffplay to confirm the bytes render as expected:
ffplay -f rawvideo -pix_fmt rgb24 -s WxH frame.bin
If you want, share a minimal standalone script (the small snippet that builds the VideoFrame and calls capture_frame). With that I can point out the exact byte/stride packing issue or confirm if a Python SDK stride field is needed.
Sources: Rust SDKs | livekit/livekit_composite | DeepWiki | Yeah, but can it run DOOM?chilly-soccer-432
12/28/2025, 6:30 PMtall-airline-16756
12/28/2025, 6:31 PMchilly-soccer-432
12/28/2025, 6:32 PMtall-airline-16756
12/28/2025, 6:32 PMstride field for VideoFrame, while libwebrtc expects per‑plane stride/pitch and sometimes aligned rows. That mismatch is a known limitation pattern (not a public / documented bug), which is why padding/stride or using an alternate ingestion path is the practical path forward.
Which do you want to try next — force VP8/H264 and report results, or paste your minimal repro script so I can inspect the exact bytes?
Sources: Rust SDKs | livekit/livekit_composite | DeepWiki