AudioBridge
janus.plugin.audiobridge is an audio conference bridge: every participant sends one Opus stream to the server, the server mixes the streams, and each participant receives the mix of everyone else. You use it for voice conferences that need one stream down per listener instead of one per speaker, and to forward a mixed room to another program (a recorder, a broadcaster) over RTP.
The mixer does not send audio back yet
In this port a participant can join, appear in listparticipants, mute and leave. The mix does not come back to it: the browser's inbound-rtp stays at 0 packets (see the join transcript). Room management and RTP forwarders work. Read Known gaps before you build on it.
Enable and configure
Build with the plugin-audiobridge feature. The plugin reads janus.plugin.audiobridge.jcfg; each room-<id> block is a room that exists at startup:
general: {
}
room-1234: {
description = "Demo Room"
secret = "adminpwd"
sampling_rate = 16000
record = false
use_limiter = false
}sampling_rate is mandatory in a static room: a room without it is skipped with the log line Can't add the AudioBridge room, missing mandatory information.... Valid values are 8000, 12000, 16000, 24000 and 48000 (create answers 499 Unsupported sampling rate 12345 for others). The other keys, including pin, spatial_audio, allow_rtp_participants, mjrs and the rtp_forward_* keys that start a forwarder with the room, are in the AudioBridge config reference.
Try it
Manage rooms (answered at once)
list, exists, create, edit, allowed, listparticipants, rtp_forward, listforwarders, mute_room, kick, play_file, destroy and the other management requests answer in the same HTTP reply. The transcript also shows the requests the port does not implement yet (stop_rtp_forward, enable_recording) and a permanent room.
// AudioBridge requests that are answered directly (synchronous): no PeerConnection needed.
// Room 1234 comes from janus.plugin.audiobridge.jcfg (secret adminpwd, sampling_rate 16000).
// → POST /janus
{
"janus": "create",
"transaction": "t-create"
}
// ← 200 OK
{
"janus": "success",
"transaction": "t-create",
"data": {
"id": 4100001234567908
}
}
// → POST /janus/4100001234567908
{
"janus": "attach",
"plugin": "janus.plugin.audiobridge",
"transaction": "t-attach"
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-attach",
"data": {
"id": 4100002469135799
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-1",
"body": {
"request": "list"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-1",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "success",
"list": [
{
"room": 1234,
"description": "Demo Room",
"sampling_rate": 16000,
"spatial_audio": false,
"use_limiter": false,
"pin_required": false,
"record": false,
"muted": false,
"num_participants": 0
}
]
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-2",
"body": {
"request": "exists",
"room": 1234
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-2",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "success",
"room": 1234,
"exists": true
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-3",
"body": {
"request": "listparticipants",
"room": 1234
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-3",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "participants",
"room": 1234,
"participants": []
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-4",
"body": {
"request": "create",
"room": 5678,
"description": "Docs bridge",
"secret": "s3cret",
"pin": "1111",
"sampling_rate": 16000,
"is_private": false
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-4",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "created",
"room": 5678,
"permanent": false
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-5",
"body": {
"request": "create",
"room": 5678
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-5",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "event",
"error_code": 486,
"error": "Room 5678 already exists"
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-6",
"body": {
"request": "list"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-6",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "success",
"list": [
{
"room": 1234,
"description": "Demo Room",
"sampling_rate": 16000,
"spatial_audio": false,
"use_limiter": false,
"pin_required": false,
"record": false,
"muted": false,
"num_participants": 0
},
{
"room": 5678,
"description": "Docs bridge",
"sampling_rate": 16000,
"spatial_audio": false,
"use_limiter": false,
"pin_required": true,
"record": false,
"muted": false,
"num_participants": 0
}
]
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-7",
"body": {
"request": "edit",
"room": 5678,
"secret": "s3cret",
"new_description": "Renamed bridge"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-7",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "edited",
"room": 5678,
"permanent": false
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-8",
"body": {
"request": "edit",
"room": 5678,
"secret": "wrong",
"new_description": "x"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-8",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "event",
"error_code": 489,
"error": "Unauthorized (wrong secret)"
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-9",
"body": {
"request": "allowed",
"room": 5678,
"secret": "s3cret",
"action": "add",
"allowed": [
"token-a"
]
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-9",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "success",
"room": 5678,
"allowed": [
"token-a"
]
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-10",
"body": {
"request": "rtp_forward",
"room": 5678,
"secret": "s3cret",
"host": "127.0.0.1",
"port": 5002,
"codec": "opus",
"ptype": 100,
"ssrc": 1234
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-10",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "success",
"room": 5678,
"stream_id": 2007654332,
"host": "127.0.0.1",
"port": 5002
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-11",
"body": {
"request": "listforwarders",
"room": 5678,
"secret": "s3cret"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-11",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "forwarders",
"room": 5678,
"rtp_forwarders": [
{
"stream_id": 2007654332,
"ip": "127.0.0.1",
"port": 5002,
"ssrc": 1234,
"codec": "opus",
"ptype": 100,
"always_on": false
}
]
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-12",
"body": {
"request": "stop_rtp_forward",
"room": 5678,
"secret": "s3cret",
"stream_id": 1
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-12",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "event",
"error_code": 499,
"error": "Request 'stop_rtp_forward' is not implemented by this port"
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-13",
"body": {
"request": "mute_room",
"room": 5678,
"secret": "s3cret"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-13",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "success",
"room": 5678
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-14",
"body": {
"request": "unmute_room",
"room": 5678,
"secret": "s3cret"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-14",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "success",
"room": 5678
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-15",
"body": {
"request": "kick",
"room": 5678,
"secret": "s3cret",
"id": 99
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-15",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "event",
"error_code": 492,
"error": "No such user 99 in room 5678"
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-16",
"body": {
"request": "listannouncements",
"room": 5678,
"secret": "s3cret"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-16",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "announcements",
"room": 5678,
"announcements": []
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-17",
"body": {
"request": "play_file",
"room": 5678,
"secret": "s3cret",
"filename": "/nonexistent.opus",
"file_id": "intro"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-17",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "success",
"room": 5678,
"file_id": "intro"
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-18",
"body": {
"request": "enable_recording",
"room": 5678,
"secret": "s3cret",
"record": true
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-18",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "event",
"error_code": 499,
"error": "Request 'enable_recording' is not implemented by this port"
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-19",
"body": {
"request": "resetdecoder"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-19",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "event",
"error_code": 487,
"error": "Can't reset (not in a room)"
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-20",
"body": {
"request": "destroy",
"room": 5678,
"secret": "s3cret"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-20",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "destroyed",
"room": 5678,
"permanent": false
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-21",
"body": {
"request": "destroy",
"room": 5678,
"secret": "s3cret"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-21",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "event",
"error_code": 485,
"error": "No such room (5678)"
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-22",
"body": {
"request": "bogus"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-22",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "event",
"error_code": 482,
"error": "Unknown request 'bogus'"
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-23",
"body": {
"request": "create",
"room": 5679,
"permanent": true
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-23",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "event",
"error_code": 499,
"error": "No configuration file, can't create permanent room"
}
}
}Join with a browser
join, configure, changeroom and leave are asynchronous: you get an ack, then an event with the same transaction. The browser's SDP offer goes in the jsep of join (or of a later configure), and the plugin answers in the joined event. The transcript was recorded with two real PeerConnections in headless Chrome. Look at the end: the last note is the browser's own getStats() after the join.
// Two browsers join room 1234 with an audio-only offer. SDP is shortened.
// join carries the offer. The plugin answers with `joined` and the JSEP answer in one event.
// → POST /janus
{
"janus": "create",
"transaction": "t-create"
}
// ← 200 OK
{
"janus": "success",
"transaction": "t-create",
"data": {
"id": 4100001234567908
}
}
// → POST /janus/4100001234567908
{
"janus": "attach",
"plugin": "janus.plugin.audiobridge",
"transaction": "t-attach"
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-attach",
"data": {
"id": 4100002469135799
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-1",
"body": {
"request": "join",
"room": 1234,
"display": "Alice",
"muted": false
},
"jsep": {
"type": "offer",
"sdp": "v=0\r\no=- 6100000000001000000 2 IN IP4 127.0.0.1\r\ns=-\r\n…"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4100001234567908,
"transaction": "t-1"
}
// ⇠ event, long poll GET /janus/<session>
{
"janus": "event",
"session_id": 4100001234567908,
"transaction": "t-1",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "joined",
"room": 1234,
"id": 4100003703703690,
"participants": []
}
},
"jsep": {
"type": "answer",
"sdp": "v=0\r\no=- 4100004938271581 1 IN IP4 192.0.2.10\r\ns=AudioBridge 1234\r\n…"
}
}
// ⇠ event, long poll GET /janus/<session>
{
"janus": "webrtcup",
"session_id": 4100001234567908,
"sender": 4100002469135799
}
// → POST /janus/4100001234567908
{
"janus": "attach",
"plugin": "janus.plugin.audiobridge",
"transaction": "t-attach"
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-attach",
"data": {
"id": 4100006172839472
}
}
// → POST /janus/4100001234567908/4100006172839472
{
"janus": "message",
"transaction": "t-1",
"body": {
"request": "join",
"room": 1234,
"display": "Bob"
},
"jsep": {
"type": "offer",
"sdp": "v=0\r\no=- 6100000000002000000 2 IN IP4 127.0.0.1\r\ns=-\r\n…"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4100001234567908,
"transaction": "t-1"
}
// ⇠ event, long poll GET /janus/<session>
{
"janus": "event",
"session_id": 4100001234567908,
"transaction": "t-1",
"sender": 4100006172839472,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "joined",
"room": 1234,
"id": 4100007407407363,
"participants": [
{
"id": 4100003703703690,
"display": "Alice",
"setup": true,
"muted": false,
"talking": false
}
]
}
},
"jsep": {
"type": "answer",
"sdp": "v=0\r\no=- 4100008641975254 1 IN IP4 192.0.2.10\r\ns=AudioBridge 1234\r\n…"
}
}
// ⇠ event, long poll GET /janus/<session>
{
"janus": "event",
"session_id": 4100001234567908,
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "joined",
"room": 1234,
"participants": [
{
"id": 4100007407407363,
"display": "Bob",
"setup": false,
"muted": false
}
]
}
}
}
// → POST /janus/4100001234567908/4100006172839472
{
"janus": "message",
"transaction": "t-2",
"body": {
"request": "listparticipants",
"room": 1234
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-2",
"sender": 4100006172839472,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "participants",
"room": 1234,
"participants": [
{
"id": 4100007407407363,
"display": "Bob",
"setup": false,
"muted": false,
"talking": false
},
{
"id": 4100003703703690,
"display": "Alice",
"setup": true,
"muted": false,
"talking": false
}
]
}
}
}
// → POST /janus/4100001234567908/4100006172839472
{
"janus": "message",
"transaction": "t-3",
"body": {
"request": "configure",
"muted": true
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4100001234567908,
"transaction": "t-3"
}
// ⇠ event, long poll GET /janus/<session>
{
"janus": "event",
"session_id": 4100001234567908,
"transaction": "t-3",
"sender": 4100006172839472,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "event",
"result": "ok"
}
}
}
// ⇠ event, long poll GET /janus/<session>
{
"janus": "event",
"session_id": 4100001234567908,
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "event",
"room": 1234,
"participants": [
{
"id": 4100007407407363,
"display": "Bob",
"setup": false,
"muted": true
}
]
}
}
}
// → POST /janus/4100001234567908/4100006172839472
{
"janus": "message",
"transaction": "t-4",
"body": {
"request": "leave"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4100001234567908,
"transaction": "t-4"
}
// ⇠ event, long poll GET /janus/<session>
{
"janus": "event",
"session_id": 4100001234567908,
"transaction": "t-4",
"sender": 4100006172839472,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "left",
"room": 1234,
"id": 4100007407407363
}
}
}
// ⇠ event, long poll GET /janus/<session>
{
"janus": "event",
"session_id": 4100001234567908,
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "event",
"room": 1234,
"leaving": 4100007407407363
}
}
}
// Browser stats for alice a few seconds after the join (RTCPeerConnection.getStats):
// outbound-rtp packetsSent = 165, inbound-rtp packetsReceived = 0
// A working mixer sends the mix back, so packetsReceived would grow. Here it stays 0 (known gap).
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-2",
"body": {
"request": "leave"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4100001234567908,
"transaction": "t-2"
}
// ⇠ event, long poll GET /janus/<session>
{
"janus": "event",
"session_id": 4100001234567908,
"transaction": "t-2",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "left",
"room": 1234,
"id": 4100003703703690
}
}
}
// Join errors: wrong PIN, no such room, and a plain RTP join (no PeerConnection) in a room that allows it and in one that does not.
// → POST /janus/4100001234567908
{
"janus": "attach",
"plugin": "janus.plugin.audiobridge",
"transaction": "t-attach"
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-attach",
"data": {
"id": 4100009876543145
}
}
// → POST /janus/4100001234567908/4100009876543145
{
"janus": "message",
"transaction": "t-1",
"body": {
"request": "create",
"room": 6000,
"description": "Pinned bridge",
"pin": "1111",
"secret": "s3cret"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-1",
"sender": 4100009876543145,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "created",
"room": 6000,
"permanent": false
}
}
}
// → POST /janus/4100001234567908/4100009876543145
{
"janus": "message",
"transaction": "t-2",
"body": {
"request": "create",
"room": 6001,
"description": "Plain RTP bridge",
"allow_rtp_participants": true
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-2",
"sender": 4100009876543145,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "created",
"room": 6001,
"permanent": false
}
}
}
// → POST /janus/4100001234567908
{
"janus": "attach",
"plugin": "janus.plugin.audiobridge",
"transaction": "t-attach"
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-attach",
"data": {
"id": 4100011111111036
}
}
// → POST /janus/4100001234567908/4100011111111036
{
"janus": "message",
"transaction": "t-1",
"body": {
"request": "join",
"room": 6000,
"display": "Carol",
"pin": "wrong"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4100001234567908,
"transaction": "t-1"
}
// ⇠ event, long poll GET /janus/<session>
{
"janus": "event",
"session_id": 4100001234567908,
"transaction": "t-1",
"sender": 4100011111111036,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "event",
"error_code": 489,
"error": "Unauthorized (wrong pin)"
}
}
}
// → POST /janus/4100001234567908/4100011111111036
{
"janus": "message",
"transaction": "t-2",
"body": {
"request": "join",
"room": 99
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4100001234567908,
"transaction": "t-2"
}
// ⇠ event, long poll GET /janus/<session>
{
"janus": "event",
"session_id": 4100001234567908,
"transaction": "t-2",
"sender": 4100011111111036,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "event",
"error_code": 485,
"error": "No such room (99)"
}
}
}
// → POST /janus/4100001234567908/4100011111111036
{
"janus": "message",
"transaction": "t-3",
"body": {
"request": "join",
"room": 1234,
"display": "Carol",
"rtp": {
"ip": "127.0.0.1",
"port": 6000,
"payload_type": 100
}
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4100001234567908,
"transaction": "t-3"
}
// ⇠ event, long poll GET /janus/<session>
{
"janus": "event",
"session_id": 4100001234567908,
"transaction": "t-3",
"sender": 4100011111111036,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "event",
"error_code": 489,
"error": "Plain RTP participants not allowed in this room"
}
}
}
// → POST /janus/4100001234567908/4100011111111036
{
"janus": "message",
"transaction": "t-4",
"body": {
"request": "join",
"room": 6001,
"display": "Carol",
"rtp": {
"ip": "127.0.0.1",
"port": 6000,
"payload_type": 100
}
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4100001234567908,
"transaction": "t-4"
}
// ⇠ event, long poll GET /janus/<session>
{
"janus": "event",
"session_id": 4100001234567908,
"transaction": "t-4",
"sender": 4100011111111036,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "joined",
"room": 6001,
"id": 4100012345678927,
"participants": []
}
}
}The steps:
- Create the PeerConnection, add an audio track, create the offer (wait for ICE gathering or send trickle candidates).
- Send
{"request": "join", "room": 1234, "display": "Alice"}withjsepset to the offer. - Apply the
jsepanswer in thejoinedevent. The event also carries your participantidand the list of participants already in the room. - After
webrtcup, sendconfigure(without ajsep) to mute and unmute, andleaveto leave. The other participants getjoined,event(withparticipantsfor a mute, orleavingfor a leave) notifications.
To make the plugin send the offer instead, put "generate_offer": true in join, apply its offer, and send your answer in a configure. This is verified: the joined event then carries a JSEP offer.
Forward the mix over RTP
rtp_forward sends the room's mix to any UDP address as RTP, which is how you feed ffmpeg or GStreamer. This transcript starts a UDP listener on this machine, adds a forwarder with always_on, and counts what arrives.
// rtp_forward sends the mix of a room to a UDP address. A UDP socket on 127.0.0.1:5002 (a stand-in for ffmpeg or GStreamer) listens.
// always_on makes the forwarder send silence while the room is empty.
// → POST /janus
{
"janus": "create",
"transaction": "t-create"
}
// ← 200 OK
{
"janus": "success",
"transaction": "t-create",
"data": {
"id": 4100001234567908
}
}
// → POST /janus/4100001234567908
{
"janus": "attach",
"plugin": "janus.plugin.audiobridge",
"transaction": "t-attach"
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-attach",
"data": {
"id": 4100002469135799
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-1",
"body": {
"request": "rtp_forward",
"room": 1234,
"secret": "adminpwd",
"host": "127.0.0.1",
"port": 5002,
"codec": "opus",
"ptype": 100,
"ssrc": 1234,
"always_on": true
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-1",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "success",
"room": 1234,
"stream_id": 2007654332,
"host": "127.0.0.1",
"port": 5002
}
}
}
// The listener received 100 packets in 2 seconds (an empty room, so silence).
// First packet: 20 bytes, RTP version 2, payload type 100, sequence 1, SSRC 1234.
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-2",
"body": {
"request": "listforwarders",
"room": 1234,
"secret": "adminpwd"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-2",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "forwarders",
"room": 1234,
"rtp_forwarders": [
{
"stream_id": 2007654332,
"ip": "127.0.0.1",
"port": 5002,
"ssrc": 1234,
"codec": "opus",
"ptype": 100,
"always_on": true
}
]
}
}
}Reference
All requests have "request": "<name>". The reply key is audiobridge. Parameters not marked required are optional. Rows that no transcript above shows are taken from the C documentation and the Rust request table; they were not all run.
Synchronous requests
| Request | Parameters | Reply |
|---|---|---|
create | room (number, random if missing), permanent, description, secret, pin, is_private, allowed (tokens), sampling_rate (default 16000), spatial_audio, use_limiter, audiolevel_ext, audiolevel_event, audio_active_packets, audio_level_average, default_expectedloss, default_bitrate, denoise, record, record_file, record_dir, mjrs, mjrs_dir, allow_rtp_participants, groups, admin_key | {"audiobridge": "created", "room", "permanent"} |
edit | room, secret, new_description, new_secret, new_pin, new_is_private, new_record_dir, new_mjrs_dir, permanent | {"audiobridge": "edited", "room", "permanent"} |
destroy | room, secret, permanent | {"audiobridge": "destroyed", "room", "permanent"}; participants get a destroyed event |
exists | room | {"audiobridge": "success", "room", "exists"} |
list | none | {"audiobridge": "success", "list": [{room, description, sampling_rate, spatial_audio, use_limiter, pin_required, record, muted, num_participants}]} |
listparticipants | room | {"audiobridge": "participants", "room", "participants": [{id, display, setup, muted, talking}]} |
allowed | room, secret, action (enable, disable, add, remove), allowed (tokens) | {"audiobridge": "success", "room", "allowed": […]} |
kick, kick_all | room, secret, id (for kick) | {"audiobridge": "success"} |
mute, unmute | room, secret, id | {"audiobridge": "success"} |
mute_room, unmute_room | room, secret | {"audiobridge": "success", "room"} |
suspend, resume | room, secret, id, plus pause_events, stop_record (suspend) or record, filename (resume) | {"audiobridge": "success"} |
resetdecoder | none (on a joined handle) | {"audiobridge": "success"}; error 487 if you are not in a room |
rtp_forward | room, secret (or admin_key if configured), host, port, codec (opus, pcma, pcmu), ptype, ssrc, group, host_family, srtp_suite, srtp_crypto, always_on | {"audiobridge": "success", "room", "stream_id", "host", "port"} |
listforwarders | room, secret | {"audiobridge": "forwarders", "room", "rtp_forwarders": [{stream_id, ip, port, ssrc, codec, ptype, always_on}]} |
stop_rtp_forward | room, secret, stream_id | not implemented, see below |
play_file, is_playing, stop_file, stop_all_files, listannouncements | room, secret, plus file_id, filename, loop, group | success, announcements; see below |
enable_recording, enable_mjrs, denoise_enable, denoise_disable | see the C docs | enable_recording, denoise_enable and denoise_disable are not implemented |
listannouncements and play_file need the room secret when the room has one: without it the answer is 483 Missing mandatory element (secret).
Asynchronous requests
| Request | Parameters | Events |
|---|---|---|
join | room (required), id (unique participant id; random if missing), display, pin, token, muted, suspended, pause_events, codec (opus, pcma, pcmu), bitrate, quality (0 to 10), expected_loss (0 to 20), volume (percent), spatial_position (0 to 100), denoise, secret (makes you an admin: not muted by mute_room), group, record, filename, generate_offer, rtp (plain RTP participant, see below), audio_level_average, audio_active_packets | to you: {"audiobridge": "joined", "room", "id", "participants": […]} with the JSEP answer; to the others: joined with the new participant in participants |
configure | muted, display, bitrate, quality, expected_loss, volume, spatial_position, denoise, record, filename, group; with a jsep for a renegotiation or ICE restart | to you: {"audiobridge": "event", "result": "ok"}; to the others, if muted changed: event with participants |
changeroom | like join, no jsep and no generate_offer | to you: roomchanged; to both rooms: leave and join notifications |
leave | none | to you: {"audiobridge": "left", "room", "id"}; to the others: {"audiobridge": "event", "room", "leaving": id} |
The joined event has no display of its own: you chose it. Other participants appear in participants with id, display, setup (has a PeerConnection), muted and, in listparticipants, talking.
Error codes
| Code | Meaning |
|---|---|
| 480 to 484 | No message, invalid JSON, unknown request, missing element, invalid element |
| 485, 486 | No such room, room exists |
| 487, 488 | Not in a room, libopus error |
| 489 | Unauthorized (wrong secret, PIN or token; also a plain RTP join into a room that does not allow it) |
| 490, 491, 492 | Participant id exists, already in the room, no such participant |
| 493, 494 | Invalid SDP, no such group |
| 499 | Other error, including "not implemented by this port" |
Behaviour you may not expect
listanswers with the keylist. The C doxygen text saysrooms; the C server and this port both sendlist.- In the recorded
listparticipantsreplies each entry hasid,display,setup,mutedandtalking.listincludesmuted, the room-wide mute frommute_room. rtp_forwardsends from the moment the forwarder exists: 50 packets per second, payload type 100, 20 bytes each for silence. It does not need a participant ifalways_onistrue.play_filewith a path that does not exist still answerssuccesswith thefile_id: the reply does not check the file.- A room created with
"permanent": trueanswers 499No configuration file, can't create permanent room, because writing the room back tojanus.plugin.audiobridge.jcfgis not ported.
Known gaps
- fails The mixer sends 0 packets to participants. Verified: after a
join,getStats()in Chrome showsoutbound-rtpgrowing and noinbound-rtp(transcript above). The mixer, decoder and encoder exist in the crate (plugins/audiobridge/src/frame.rs,opus.rs), but the mix is not sent on the participant's PeerConnection. Janus Benchaudiobridge.mixfails for this reason. - gap Plain RTP participants. A
joinwith anrtpobject into a room created withallow_rtp_participants: trueanswersjoined, but the event has nortpobject (the address and port to send to): the plain RTP sockets are not ported, so nothing can be sent or received. - gap Not implemented requests answer error 499
Request '<name>' is not implemented by this port:stop_rtp_forward,enable_recording,denoise_enable,denoise_disable. A forwarder cannot be stopped except by destroying the room. - gap Permanent rooms (above), and RNNoise denoising.
- Opus FEC decoding and some encoder details are open in
parity/status.json. - Static
rtp_forward_*keys are read; room recording to WAV and per-participant MJR files are ported in the crate but not exercised here.
Test it in Janus Bench
Open the AudioBridge page, or run audiobridge.mix from Run all checks. It joins room 1234, lists participants, mutes and leaves, and waits for mixed audio. Result today: fails at the step "mixed audio arrives" (0 packets received).