obs-studio icon indicating copy to clipboard operation
obs-studio copied to clipboard

WHIP output fails to connect when server only provides TURN relay via Link header

Open leporel opened this issue 3 months ago • 8 comments

Operating System Info

Windows 11

Other OS

No response

OBS Studio Version

32.0.1

OBS Studio Version (Other)

No response

OBS Studio Log URL

https://obsproject.com/logs/oga2cXY8OfzoIWh6

OBS Studio Crash Log URL

No response

Expected Behavior

OBS should use the TURN server provided in the Link header and establish a connection through the relay when direct UDP connection is not available.

Current Behavior

OBS appears to attempt direct ICE UDP connection despite TURN credentials being provided in the Link header. The connection enters "Connecting" state but never successfully transmits data, eventually failing after ~2 minutes.

Steps to Reproduce

  1. Use only TURN server, provided like described in rfc9725 from header
  2. webrtc server accepts only connection to turn server (in case OME they have ebbeded turn server) (so firewall opens onlly this port of turn server, other ports are deny, except 443 for https for whip)

Anything else we should know?

When attempting to stream using WebRTC WHIP to a server that only provides TURN relay (OvenMediaEngine), the stream is created on the server side but disconnects after approximately 2 minutes without receiving data. The OBS peer appears to disconnect due to a timeout.

Server Response Headers:

Location: /room/[stream-name]?direction=whip
ETag: [etag-value]
Link: <turn:server.example.com:3378?transport=tcp>; rel="ice-server"; username="user"; credential="pass"; credential-type="password"
Access-Control-Expose-Headers: Location, Link, ETag
Vary: Origin
content-type: application/sdp
server: OvenMediaEngine

OBS Logs:

15:05:43.768: [obs-webrtc] [whip_output: 'simple_stream'] PeerConnection state is now: Connecting
15:06:46.767: [obs-webrtc] [whip_output: 'simple_stream'] PeerConnection state is now: Failed
15:06:46.767: Output 'simple_stream': stopping
15:06:46.767: [obs-webrtc] [whip_output: 'simple_stream'] PeerConnection state is now: Closed

Additional Context:

I can see that OBS has ParseLinkHeader in the code, and the server correctly returns TURN credentials in the Link header per WHIP specification. However, OBS still attempts to use direct ICE candidates which are not accessible from the external internet.

In my use case, I need TURN to be used exclusively (relay-only policy) so users stream over TCP. I encountered the same issue when implementing WHIP in a browser, but was able to resolve it by forcing use configured iceServers before creating RTCPeerConnection:

const config = {
  iceServers: [{
    urls: 'turn:server.example.com:3378?transport=tcp',
    username: 'user',
    credential: 'pass'
  }],
  iceTransportPolicy: "relay"
};
new RTCPeerConnection(config);

leporel avatar Nov 03 '25 12:11 leporel

Hi @leporel!

A couple blocking things/behaviors here

  • libjuice doesn't support TURN via TCP at all Only UDP is supported as transport protocol and other protocols are ignored.

However I added ICE-TCP support! https://github.com/paullouisageneau/libjuice/commit/db9579a7169abb0a0db3574ab257ec253f3d1330

@paullouisageneau I know the ice-tcp had some bugs (sorry), but how do you feel about new release for libjuice and making the TCP option available via libdatachannel? I don't want to create churn on your side.

I think we have ~3 weeks until OBS code freeze?

Sean-Der avatar Nov 12 '25 15:11 Sean-Der

@Sean-Der That seems doable, I'll look into it this weekend.

paullouisageneau avatar Nov 13 '25 23:11 paullouisageneau

@Sean-Der I've released libjuice v1.7.0 and libdatachannel v0.24.0 with ICE-TCP support.

paullouisageneau avatar Nov 17 '25 01:11 paullouisageneau

Cloudflare would be willing to support WHIP adoption in OBS by providing free TURN services like we did here: https://huggingface.co/blog/fastrtc-cloudflare

renandincer avatar Nov 17 '25 17:11 renandincer

Hi @leporel

Would you mind testing with https://github.com/obsproject/obs-studio/pull/12848?

Excited to see this fixed :)

Sean-Der avatar Nov 22 '25 12:11 Sean-Der

Hi @leporel

Would you mind testing with #12848?

Excited to see this fixed :)

I apologize, I tried and downloaded 32.0.2-16-g4eab3b818 (from artifacts), but it seems the service I'm using (self-hosted OvenMediaEngine) doesn't have an ICE-TCP implementation, only turn-tcp. So I wasn't able to test it.

leporel avatar Nov 22 '25 15:11 leporel

Would you mind opening an issue with OME about ice-tcp support?

The latency and CPU usage overhead of TURN I’m hoping to avoid :(

Sean-Der avatar Nov 22 '25 18:11 Sean-Der

Would you mind opening an issue with OME about ice-tcp support?

Good idea, but I'm shy :). Anyway, I thought about it and decided—why not use vibe-coding and add ICE-TCP myself for testing? So I did: https://github.com/leporel/OvenMediaEngine/tree/feat/ice-tcp .

Good news: it seems to work, and I can establish a connection. Bad news: when connecting from OBS, I'm seeing Failed to unprotect SRTP packet and other errors, and the stream eventually drops. This issue doesn't occur when using ICE-UDP.

When streaming from a browser using ICE-TCP doesn't produce these errors. The test webpage I used: webrtc_tcp_test.html (see below attachments)

OBS settings (stream url: http://192.168.5.84:3333/app/stream?direction=whip)

17:32:59.236: [x264 encoder: 'simple_video_stream'] preset: veryfast
17:32:59.236: [x264 encoder: 'simple_video_stream'] tune: zerolatency
17:32:59.236: [x264 encoder: 'simple_video_stream'] settings:
17:32:59.236: 	rate_control: CBR
17:32:59.236: 	bitrate:      5000
17:32:59.236: 	buffer size:  5000
17:32:59.236: 	crf:          23
17:32:59.236: 	fps_num:      30
17:32:59.236: 	fps_den:      1
17:32:59.236: 	width:        1920
17:32:59.236: 	height:       1080
17:32:59.236: 	keyint:       60
17:32:59.236: 
17:32:59.236: [x264 encoder: 'simple_video_stream'] custom settings: 
17:32:59.236: 	tune = zerolatency
17:32:59.236: 	keyint = 60
17:32:59.236: 	bframes = 0
17:32:59.237: ---------------------------------
17:32:59.237: [FFmpeg libopus encoder: 'simple_opus'] bitrate: 128, channels: 2, channel_layout: stereo, track: 1
17:32:59.237: 
17:32:59.245: [obs-webrtc] [whip_output: 'simple_stream'] PeerConnection state is now: Connecting
17:32:59.252: ==== Streaming Start ===============================================
17:32:59.252: [obs-webrtc] [whip_output: 'simple_stream'] PeerConnection state is now: Connected
17:32:59.252: [obs-webrtc] [whip_output: 'simple_stream'] Connect time: 6ms

If you'd like to try it, just pull the branch (feat/ice-tcp) and build the Docker image:

docker build -t ovenmediaengine:ice_tcp -f Dockerfile.local .

Then run the container:

docker run --name ome -d \
  -e OME_HOST_IP=192.168.5.84 \
  -p 1935:1935 \
  -p 9999:9999/udp \
  -p 9000:9000 \
  -p 3333:3333 \
  -p 3478:3478 \
  -p 10000-10005:10000-10005/tcp \
  -p 10000-10005:10000-10005/udp \
  -v G:\temp\123\ome_test\origin_conf:/opt/ovenmediaengine/bin/origin_conf \
  -v G:\temp\123\ome_test\edge_conf:/opt/ovenmediaengine/bin/edge_conf \
  ovenmediaengine:ice_tcp

Place the Server.xml file in G:\temp\123\ome_test\origin_conf.

Server.xml

webrtc_tcp_test.html

ome_server_errors.txt


PS: I’m certainly skeptical about my implementation - I don’t have experience with C++ or a deep understanding of the WebRTC RFCs. I wouldn’t have written this comment at all if the browser-based version also produced errors, but it seems to work fine through the browser, so I decided to share this information.

leporel avatar Nov 23 '25 14:11 leporel