OPUS Codec Failing on Janus: ICE Error While PCMU Works Fine!

Hello everyone!

First of all, we are new to the concepts of WebRTC and are trying our best to understand it. We are learning step by step as we use it in our project.
So feel free to explain any concepts we missed or to point us toward some documentation.

We started a project one week ago:

  • Client side → .NET WPF (C#) application using the SIPSorcery package for WebRTC communication.
  • Server side → Janus (of course!) Version 1.3.0.

We successfully tested our application with the echotest and audiobridge plugins using the PCMU codec.

However, when we try to use the OPUS codec, the WebRTC connection fails.
Every test we do ends up with the following errors :

[Sun Mar  2 11:41:10 2025] [WARN] [365109979763687] ICE failed for component 1 in stream 1, but let's give it some time... (trickle pending, answer received, alert not set)
[Sun Mar  2 11:41:15 2025] [WARN] [365109979763687] ICE failed for component 1 in stream 1, but we're still waiting for some info so we don't care... (trickle pending, answer received, alert not set)
[Sun Mar  2 11:41:25 2025] [ERR] [ice.c:janus_ice_check_failed:2084] [365109979763687] ICE failed for component 1 in stream 1...
[Sun Mar  2 11:41:25 2025] [365109979763687] Hanging up PeerConnection because of a ICE failed

We enabled the admin API to try to understand what is going on, but we are overwhelmed by the amount of information.

Here are some chunk of our configuration in Janus :

  • janus.plugin.audiobridge.jcfg
general: {
        events = true
        string_ids = true
}

room-1234: {
        description = "Example room"
        sampling_rate = 48000
        codecs = "opus"
        opus_ptime = 20
}
  • janus.jcfg
media: {
        rtp_port_range = "40000-50000"
        opus_fec = true 
        opus_dtx = true
        opus_bitrate = 48000
}

nat: {
        ice_lite = false
        ice_tcp = false

        stun_server = "stun.l.google.com"
        stun_port = 19302

        nice_debug = true
        trickle_ice = true
        full_trickle = false
}

Here if needed an example of what is going one when exanging SDP and candidate

  • Local SDP offer
v=0
o=- 74318 0 IN IP4 127.0.0.1
s=sipsorcery
t=0 0
a=group:BUNDLE 0
m=audio 9 UDP/TLS/RTP/SAVPF 111 101
c=IN IP4 0.0.0.0
a=ice-ufrag:TNQK
a=ice-pwd:XXXXXXXXXXXXXXX
a=fingerprint:sha-256 9B:4C:F2:F7:C0:8E:79:2C:8E:74:6C:65:B3:F1:B3:25:5A:48:4F:D9:67:5B:92:13:CF:B8:41:9E:7F:34:F0:C1
a=setup:actpass
a=candidate:19814941 1 udp 2113937663 XXXXXXX 61474 typ host generation 0
a=candidate:4002404704 1 udp 2113940223 XXXXXXX 61474 typ host generation 0
a=candidate:3741958187 1 udp 2113940223 XXXXXXX 61474 typ host generation 0
a=candidate:4185247567 1 udp 2113940223 XXXXXXX 61474 typ host generation 0
a=mid:0
a=rtpmap:111 opus/48000/2
a=fmtp:111 useinbandfec=1
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-16
a=ice-options:ice2,trickle
a=rtcp-mux
a=rtcp:9 IN IP4 0.0.0.0
a=sendrecv
a=ssrc:2064832985 cname:c10ba225-06c8-4e23-a00f-bbd82137e85e
  • Remote SDP offer
v=0
o=- 1740915663471936 1 IN IP4 XXX.XX.X.XXX
s=AudioBridge 1234
t=0 0
a=group:BUNDLE 0
a=ice-options:trickle
a=fingerprint:sha-256 F2:0D:1B:27:B9:20:5F:AF:A9:2A:7F:A3:0C:ED:39:82:3F:9C:68:CE:A7:45:0D:1C:9F:DA:72:F5:1F:F4:E9:19
a=extmap-allow-mixed
a=msid-semantic: WMS *
m=audio 9 UDP/TLS/RTP/SAVPF 111
c=IN IP4 XXX.XX.X.XXX
a=sendrecv
a=mid:0
a=rtcp-mux
a=ice-ufrag:Lhv1
a=ice-pwd:q4CsvWKOhT/dIis1bBAVq7
a=ice-options:trickle
a=setup:active
a=rtpmap:111 opus/48000/2
a=fmtp:111 maxplaybackrate=48000; stereo=0; sprop-stereo=0; useinbandfec=1
a=msid:janus janus0
a=ssrc:4070505196 cname:janus
a=candidate:1 1 udp 2015363327 XXX.XX.X.XXX 48561 typ host
a=end-of-candidates
  • Local candidate send
{"janus":"trickle","session_id":7654351391612618,"handle_id":365109979763687,"transaction":"5","candidate":{"candidate":"19814941 1 udp 2113937663 XXXXXXX 61474 typ host generation 0","sdpMid":"audio","sdpMLineIndex":0}}
{"janus":"trickle","session_id":7654351391612618,"handle_id":365109979763687,"transaction":"6","candidate":{"candidate":"4002404704 1 udp 2113940223 XXXXXXX 61474 typ host generation 0","sdpMid":"audio","sdpMLineIndex":0}}
{"janus":"trickle","session_id":7654351391612618,"handle_id":365109979763687,"transaction":"7","candidate":{"candidate":"3741958187 1 udp 2113940223 XXXXXXX 61474 typ host generation 0","sdpMid":"audio","sdpMLineIndex":0}}
{"janus":"trickle","session_id":7654351391612618,"handle_id":365109979763687,"transaction":"8","candidate":{"candidate":"4185247567 1 udp 2113940223 XXXXXXX 61474 typ host generation 0","sdpMid":"audio","sdpMLineIndex":0}}

We would really appreciate any guidance on how to debug this issue—whether it’s analyzing candidate exchanges, SDP negotiation, or checking Janus’ configuration.

Any help would be appreciated!

Thanks in advance!

This is the cause. Check the Admin API for info on why. It may be missing candidates on either side, or incompatible addresses.

Hello Lorenzo, thanks for the quick response.

We indeed check the admin api to debug ice candidate and we found something.

Working PCMU (one more prflx candidate):

        "ice": {
            "stream_id": 1,
            "component_id": 1,
            "state": "ready",
            "gathered": 28790198627,
            "connected": 28790304775,
            "local-candidates": [
                "1 1 udp 2015363327 XXXXXXX 44079 typ host"
            ],
            "remote-candidates": [
                "19814941 1 udp 2113937663 XXXXXXX 57906 typ host generation 0",
                "4002404704 1 udp 2113940223 XXXXXXX 57906 typ host generation 0",
                "3741958187 1 udp 2113940223 XXXXXXX 57906 typ host generation 0",
                "4185247567 1 udp 2113940223 XXXXXXX 57906 typ host generation 0",
                "remote1 1 udp -14024578 XXXXXXX 57906 typ prflx raddr XXXXXXX rport 57906\r\n"
            ],
            "selected-pair": "XXXXXXX:44079 [host,udp] <-> XXXXXXX:57906 [prflx,udp]",
            "ready": 1
        },

And when using OPUS, it didn’t apear:

        "ice": {
            "stream_id": 1,
            "component_id": 1,
            "state": "connecting",
            "gathered": 28462033068,
            "local-candidates": [
                "1 1 udp 2015363327 XXXXXXX 44599 typ host"
            ],
            "remote-candidates": [
                "19814941 1 udp 2113937663 XXXXXXX 58054 typ host generation 0",
                "4002404704 1 udp 2113940223 XXXXXXX 58054 typ host generation 0",
                "3741958187 1 udp 2113940223 XXXXXXX 58054 typ host generation 0",
                "4185247567 1 udp 2113940223 XXXXXXX 58054 typ host generation 0"
            ],
            "ready": 1
        },

It is possible that the OPUS codec has more difficulty finding a path than the PCMU codec ?

No. Janus ignores codecs, so I think this is an issue in Sipsorcery.

Thanks a lot. We will investigate more on the sipsorcery side.

We will post here again if we find something.

Hello,

It was indeed a sipsorcery implementation problem.

At the moment, OPUS is not fully compatible with it, whe had to implement our own SIPSorceryMedia.Abstractions.IAudioEncoder to make it work.

Thanks again for your quick response !
Louis

1 Like