Skip to content

H264-specific issue when subscribing to a WHIP ingress video track from Python. #592

Description

@utezduyar

A Python agent connects to a room, subscribes to an ingress participant’s video track, and reads it through rtc.VideoStream via RoomIO. When the WHIP ingress publishes VP8, frames arrive normally. When it publishes H264 passthrough, subscription succeeds, but no video frames are ever yielded.

The ingress configuration

{
  "input_type": 1,
  "name": "gst-whip-test",
  "room_name": "test-room",
  "participant_identity": "camera-1",
  "participant_name": "Camera 1",
  "enable_transcoding": false
}

Monitoring task:

async def monitor_video_frames(video_input, participant_identity: str) -> None:
    frame_count = 0
    started_at = time.monotonic()
    last_report_at = started_at

    logger.info(
        "starting video frame monitor",
        extra={"participant": participant_identity},
    )

    async for _frame in video_input:
        frame_count += 1
        now = time.monotonic()

        if frame_count == 1:
            logger.info(
                "first video frame received",
                extra={"participant": participant_identity},
            )

        if now - last_report_at >= 5.0:
            elapsed = now - started_at
            fps = frame_count / elapsed if elapsed > 0 else 0.0
            logger.info(
                "video frames received",
                extra={
                    "participant": participant_identity,
                    "frames": frame_count,
                    "elapsed_s": round(elapsed, 2),
                    "avg_fps": round(fps, 2),
                },
            )
            last_report_at = now

Task:

        frame_monitor_task = asyncio.create_task(
            monitor_video_frames(session._room_io.video_input, ingress.identity)
        )

Observed behavior

With VP8 from the same GStreamer/WHIP path:
video monitor starts
first frame is received
frame counts continue normally

With H264 from the same path:
video monitor starts
no first frame is ever received
no frame counts are ever logged

I am following

https://docs.livekit.io/recipes/gemini_live_vision/
uv add "livekit-agents[silero,google,images]" python-dotenv

You can test this with gstreamer test video

gst-launch-1.0 \
      whipclientsink name=whip \
        signaller::whip-endpoint="https://.....whip.livekit.cloud/w/....." \
        video-caps="video/x-h264" \
      videotestsrc is-live=true pattern=ball ! \
      video/x-raw,width=1280,height=720,framerate=30/1 ! \
      videoconvert ! \
      x264enc tune=zerolatency speed-preset=veryfast bitrate=2000 key-int-max=30 ! \
      h264parse ! \
      queue ! \
      whip.

Activity

  1. utezduyar commented on Apr 8, 2026

    @utezduyar
    Author

    I believe this is not WHIP specific. I have a golang application (lksdk github.com/livekit/server-sdk-go/v2) that publishes H264 media and the same problem.

    lkTrack, err := lksdk.NewLocalSampleTrack(codec.RTPCodecCapability,
    if err := lkTrack.WriteRTP(rtpPacket, nil); err != nil {
    
  2. m13v commented on Apr 30, 2026

    @m13v

    this looks like the classic H264 SPS/PPS gating problem. with VP8 every keyframe is self-contained, so the decoder hands you frames immediately. with H264 passthrough the decoder won't emit anything until it sees in-band SPS/PPS, and a lot of WHIP publishers only send those once at the start or out-of-band in the SDP. if the subscriber path strips or never receives them, the track stays subscribed but the decoder just buffers forever. worth checking whether the ingress is actually inserting SPS/PPS before each IDR (gst's h264parse with config-interval=-1 fixes this on the publish side) and whether the python pipeline is reading them from sprop-parameter-sets in the SDP.

  3. cloudwebrtc commented on May 25, 2026

    @cloudwebrtc
    Contributor

    Yeah, if SPS/PPS transmission is fine, then the problem might be that the key frame wasn't being requested correctly. In libwebrtc, when the receiving side doesn't receive the first frame, it sends an FIR or PLI via RTCP to the encoding side. At this point, the sender should regenerate a key frame and send it as PPS/SPS so the receiving end can quickly receive the first frame. The problem seems to be that the key frame request isn't being transmitted correctly somewhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions