Summary
lk room join --publish h265://host:port (single-stream, no WxH suffix) successfully connects, publishes a track, and the SFU completes SDP negotiation with subscribers — but the SFU never starts forwarding RTP. The subscriber's video track stays muted: true indefinitely and 0 bytes ever arrive at the receiver. The same exact pipeline with h264://host:port works perfectly. Confirmed against livekit-server v1.11.0 and livekit-cli v2.16.2, with Chrome 147 on Mac as subscriber, ARM64 Ubuntu 20.04 as publisher host.
Reproduction
# Bridge an H.265 RTSP source to a TCP socket as raw HEVC bytestream:
ffmpeg -fflags +genpts -rtsp_transport udp -i rtsp://camera/video1 \
-an -c:v copy -f hevc 'tcp://127.0.0.1:9001?listen=1' &
# Publish the bytestream as a single-stream H.265 track:
LIVEKIT_URL=ws://localhost:7880 \
LIVEKIT_API_KEY=key LIVEKIT_API_SECRET=secret \
lk room join --identity rtsp-pub --room test \
--publish 'h265://127.0.0.1:9001' --fps 30
Subscribe via Chrome (>= 136 for native H.265 WebRTC support) using livekit-client. The track will be subscribed (TrackSubscribed fires with codec: video/H265), but no RTP arrives.
What works
lk ... --publish 'h264://127.0.0.1:9001' (after re-encoding source to H.264) → renders perfectly, 30fps, 0 packet loss, 1.1ms decode time on Mac.
- The exact same SFU forwarding path is used for both codecs (verified in
livekit/pkg/sfu/{receiver.go,downtrack.go,forwarder.go} — Forwarder.DetermineCodec handles H264 and H265 the same).
Root cause
The publisher-side classification logic in server-sdk-go at v2/publication.go:482-516 only treats a published codec as "primary" if p.MimeType() ends with the codec name (e.g. h265). Otherwise it falls through into the backup-codec branch, which finds no matching backup track for an H.265-only publish and exits silently — never unblocking RTP forwarding to the subscriber.
MimeType() returns the empty string when TrackInfo.MimeType and TrackInfo.Codecs[].MimeType are not set in the publisher's AddTrackRequest. TrackInfo.MimeType is only populated when SimulcastCodecs was supplied to AddTrackRequest.
In livekit-cli's publishReader (cmd/lk/join.go:393):
pub, err = room.LocalParticipant.PublishTrack(track, &lksdk.TrackPublicationOptions{})
TrackPublicationOptions{} is empty — no WithSimulcast(...), no codec metadata. For H.264 this works because the SDK has H.264 inference paths; for H.265 the SDK requires explicit metadata or the MimeType() check fails.
In createSimulcastVideoTrack (cmd/lk/join.go:480) the multi-stream path correctly sets:
opts = append(opts, lksdk.ReaderTrackWithSampleOptions(lksdk.WithSimulcast("simulcast", &livekit.VideoLayer{
Quality: quality, Width: urlParts.width, Height: urlParts.height,
})))
…which is why publishing 2-3 H.265 simulcast streams works around this issue.
Diagnostic evidence
Server log on publish (note videoLayerMode: MODE_UNUSED, single layer at HIGH):
mediaTrack published mime=video/H265 trackInfo={..., codecs: [{mimeType: video/H265,
layers: [{quality: HIGH, ssrc: ...}], videoLayerMode: MODE_UNUSED}],
backupCodecPolicy: PREFER_REGRESSION}
Publisher SDK log on subscriber join (note mime: ""):
INFO handling subscribed quality update trackID=... mime=""
subscribedCodecs=[{codec: h265, qualities: [LOW,MED,HIGH]}]
WARN subscriber requested backup codec but no track found trackID=... codec=h265
The same mime: "" log + WARN appears for H.264 single-stream too, but for H.264 the SFU forwards anyway. So the WARN itself is noise; the underlying bug only blocks H.265.
Proposed fix
In publishReader (or wherever single-stream H.265 publishes flow), call WithSimulcast(...) with a single layer marked Quality: HIGH (W/H can be 0/0 if not known) so the publisher SDK populates SimulcastCodecs metadata in AddTrackRequest. Alternatively, fix at the SDK level so H.265 single-stream PublishTrack infers the codec MimeType the same way H.264 does.
Happy to send a PR if you'd like — wanted to surface the diagnosis first since it's a multi-layer issue (lk-cli + server-sdk-go publication classification).
Environment
Summary
lk room join --publish h265://host:port(single-stream, noWxHsuffix) successfully connects, publishes a track, and the SFU completes SDP negotiation with subscribers — but the SFU never starts forwarding RTP. The subscriber's video track staysmuted: trueindefinitely and 0 bytes ever arrive at the receiver. The same exact pipeline withh264://host:portworks perfectly. Confirmed againstlivekit-serverv1.11.0 andlivekit-cliv2.16.2, with Chrome 147 on Mac as subscriber, ARM64 Ubuntu 20.04 as publisher host.Reproduction
Subscribe via Chrome (>= 136 for native H.265 WebRTC support) using
livekit-client. The track will be subscribed (TrackSubscribedfires withcodec: video/H265), but no RTP arrives.What works
lk ... --publish 'h264://127.0.0.1:9001'(after re-encoding source to H.264) → renders perfectly, 30fps, 0 packet loss, 1.1ms decode time on Mac.livekit/pkg/sfu/{receiver.go,downtrack.go,forwarder.go}—Forwarder.DetermineCodechandles H264 and H265 the same).Root cause
The publisher-side classification logic in
server-sdk-goatv2/publication.go:482-516only treats a published codec as "primary" ifp.MimeType()ends with the codec name (e.g.h265). Otherwise it falls through into the backup-codec branch, which finds no matching backup track for an H.265-only publish and exits silently — never unblocking RTP forwarding to the subscriber.MimeType()returns the empty string whenTrackInfo.MimeTypeandTrackInfo.Codecs[].MimeTypeare not set in the publisher'sAddTrackRequest.TrackInfo.MimeTypeis only populated whenSimulcastCodecswas supplied toAddTrackRequest.In
livekit-cli'spublishReader(cmd/lk/join.go:393):TrackPublicationOptions{}is empty — noWithSimulcast(...), no codec metadata. For H.264 this works because the SDK has H.264 inference paths; for H.265 the SDK requires explicit metadata or theMimeType()check fails.In
createSimulcastVideoTrack(cmd/lk/join.go:480) the multi-stream path correctly sets:…which is why publishing 2-3 H.265 simulcast streams works around this issue.
Diagnostic evidence
Server log on publish (note
videoLayerMode: MODE_UNUSED, single layer at HIGH):Publisher SDK log on subscriber join (note
mime: ""):The same
mime: ""log + WARN appears for H.264 single-stream too, but for H.264 the SFU forwards anyway. So the WARN itself is noise; the underlying bug only blocks H.265.Proposed fix
In
publishReader(or wherever single-stream H.265 publishes flow), callWithSimulcast(...)with a single layer markedQuality: HIGH(W/H can be 0/0 if not known) so the publisher SDK populatesSimulcastCodecsmetadata inAddTrackRequest. Alternatively, fix at the SDK level so H.265 single-streamPublishTrackinfers the codec MimeType the same way H.264 does.Happy to send a PR if you'd like — wanted to surface the diagnosis first since it's a multi-layer issue (lk-cli + server-sdk-go publication classification).
Environment
livekit-serverv1.11.0livekit-cliv2.16.2 (with PR Add ability to publish h265 bytestream via tcp or unix socket #722's H.265 socket-publish feature)livekit-client(browser SDK) v2.5.10