Fork me on GitHub
AudioBridge plugin documentation

This is a plugin implementing an audio conference bridge for Janus, specifically mixing Opus streams. This means that it replies by providing in the SDP only support for Opus, and disabling video. Opus encoding and decoding is implemented using libopus (http://opus.codec.org). The plugin provides an API to allow peers to join and leave conference rooms. Peers can then mute/unmute themselves by sending specific messages to the plugin: any way a peer mutes/unmutes, an event is triggered to the other participants, so that it can be rendered in the UI accordingly.

Rooms to make available are listed in the plugin configuration file. A pre-filled configuration file is provided in conf/janus.plugin.audiobridge.jcfg and includes a demo room for testing.

To add more rooms or modify the existing one, you can use the following syntax:

room-<unique room ID>: {
        description = This is my awesome room
        is_private = true|false (private rooms don't appear when you do a 'list' request)
        secret = <optional password needed for manipulating (e.g. destroying) the room>
        pin = <optional password needed for joining the room>
        sampling_rate = <sampling rate> (e.g., 16000 for wideband mixing)
        audiolevel_ext = true|false (whether the ssrc-audio-level RTP extension must be
                negotiated/used or not for new joins, default=true)
        audiolevel_event = true|false (whether to emit event to other users or not, default=false)
        audio_active_packets = 100 (number of packets with audio level, default=100, 2 seconds)
        audio_level_average = 25 (average value of audio level, 127=muted, 0='too loud', default=25)
        default_prebuffering = number of packets to buffer before decoding each participant (default=DEFAULT_PREBUFFERING)
        record = true|false (whether this room should be recorded, default=false)
        record_file =   /path/to/recording.wav (where to save the recording)

                [The following lines are only needed if you want the mixed audio
                to be automatically forwarded via plain RTP to an external component
                (e.g., an ffmpeg script, or a gstreamer pipeline) for processing.
                By default plain RTP is used, SRTP must be configured if needed]
        rtp_forward_id = numeric RTP forwarder ID for referencing it via API (optional: random ID used if missing)
        rtp_forward_host = host address to forward RTP packets of mixed audio to
        rtp_forward_host_family = ipv4|ipv6; by default, first family returned by DNS request
        rtp_forward_port = port to forward RTP packets of mixed audio to
        rtp_forward_ssrc = SSRC to use to use when streaming (optional: stream_id used if missing)
        rtp_forward_codec = opus (default), pcma (A-Law) or pcmu (mu-Law)
        rtp_forward_ptype = payload type to use when streaming (optional: only read for Opus, 100 used if missing)
        rtp_forward_srtp_suite = length of authentication tag, if SRTP is needed (32 or 80)
        rtp_forward_srtp_crypto = key to use as crypto, if SRTP is needed (base64 encoded key as in SDES)
        rtp_forward_always_on = true|false, whether silence should be forwarded when the room is empty (optional: false used if missing)
}

Audio Bridge API

The Audio Bridge API supports several requests, some of which are synchronous and some asynchronous. There are some situations, though, (invalid JSON, invalid request) which will always result in a synchronous error response even for asynchronous requests.

create , edit , destroy , exists, allowed, kick, list, mute , unmute , mute_room , unmute_room , listparticipants , resetdecoder , rtp_forward, stop_rtp_forward , list_forwarders , play_file , is_playing and stop_file are synchronous requests, which means you'll get a response directly within the context of the transaction. create allows you to create a new audio conference bridge dynamically, as an alternative to using the configuration file; edit allows you to dynamically edit some room properties (e.g., the PIN); destroy removes an audio conference bridge and destroys it, kicking all the users out as part of the process; exists allows you to check whether a specific audio conference exists; allowed allows you to edit who's allowed to join a room via ad-hoc tokens; list lists all the available rooms, while listparticipants lists all the participants of a specific room and their details; resetdecoder marks the Opus decoder for the participant as invalid, and forces it to be recreated (which might be needed if the audio for generated by the participant becomes garbled); rtp_forward allows you to forward the mix of an AudioBridge room via RTP to a separate component (e.g., for broadcasting it to a wider audience, or for processing/recording), whereas stop_rtp_forward can remove an existing forwarder; a list of configured forwarders for a room can be retrieved using the list_forwarders request; finally, play_file allows you to reproduce an audio .opus file in a mix (e.g., to play an announcement or some background music), is_playing checks if a specific file is still playing, while stop_file will stop such a playback instead.

The join , configure , changeroom and leave requests instead are all asynchronous, which means you'll get a notification about their success or failure in an event. join allows you to join a specific audio conference bridge; configure can be used to modify some of the participation settings (e.g., mute/unmute); changeroom can be used to leave the current room and move to a different one without having to tear down the PeerConnection and recreate it again (useful for sidebars and "waiting rooms"); finally, leave allows you to leave an audio conference bridge for good.

The AudioBridge plugin also allows you to forward the mix to an external listener, e.g., a gstreamer/ffmpeg pipeline waiting to process the mixer audio stream. You can add new RTP forwarders with the rtp_forward request; a stop_rtp_forward request removes an existing RTP forwarder; listforwarders lists all the current RTP forwarders on a specific AudioBridge room instance. As an alternative, you can configure a single static RTP forwarder in the plugin configuration file.

create can be used to create a new audio room, and has to be formatted as follows:

{
        "request" : "create",
        "room" : <unique numeric ID, optional, chosen by plugin if missing>,
        "permanent" : <true|false, whether the room should be saved in the config file, default=false>,
        "description" : "<pretty name of the room, optional>",
        "secret" : "<password required to edit/destroy the room, optional>",
        "pin" : "<password required to join the room, optional>",
        "is_private" : <true|false, whether the room should appear in a list request>,
        "allowed" : [ array of string tokens users can use to join this room, optional],
        "sampling_rate" : <sampling rate of the room, optional, 16000 by default>,
        "audiolevel_ext" : <true|false, whether the ssrc-audio-level RTP extension must be negotiated for new joins, default=true>,
        "audiolevel_event" : <true|false (whether to emit event to other users or not)>,
        "audio_active_packets" : <number of packets with audio level (default=100, 2 seconds)>,
        "audio_level_average" : <average value of audio level (127=muted, 0='too loud', default=25)>,
        "default_prebuffering" : <number of packets to buffer before decoding each participant (default=DEFAULT_PREBUFFERING)>,
        "record" : <true|false, whether to record the room or not, default=false>,
        "record_file" : "</path/to/the/recording.wav, optional>",
}

A successful creation procedure will result in a created response:

{
        "audiobridge" : "created",
        "room" : <unique numeric ID>,
        "permanent" : <true if saved to config file, false if not>
}

If you requested a permanent room but a false value is returned instead, good chances are that there are permission problems.

An error instead (and the same applies to all other requests, so this won't be repeated) would provide both an error code and a more verbose description of the cause of the issue:

{
        "audiobridge" : "event",
        "error_code" : <numeric ID, check Macros below>,
        "error" : "<error description as a string>"
}

Notice that, in general, all users can create rooms. If you want to limit this functionality, you can configure an admin admin_key in the plugin settings. When configured, only "create" requests that include the correct admin_key value in an "admin_key" property will succeed, and will be rejected otherwise. Notice that you can optionally extend this functionality to RTP forwarding as well, in order to only allow trusted clients to use that feature.

Once a room has been created, you can still edit some (but not all) of its properties using the edit request. This allows you to modify the room description, secret, pin and whether it's private or not: you won't be able to modify other more static properties, like the room ID, the sampling rate, the extensions-related stuff and so on. If you're interested in changing the ACL, instead, check the allowed message. An edit request has to be formatted as follows:

{
        "request" : "edit",
        "room" : <unique numeric ID of the room to edit>,
        "secret" : "<room secret, mandatory if configured>",
        "new_description" : "<new pretty name of the room, optional>",
        "new_secret" : "<new password required to edit/destroy the room, optional>",
        "new_pin" : "<new password required to join the room, optional>",
        "new_is_private" : <true|false, whether the room should appear in a list request>,
        "permanent" : <true|false, whether the room should be also removed from the config file, default=false>
}

A successful edit procedure will result in an edited response:

{
        "audiobridge" : "edited",
        "room" : <unique numeric ID>
}

On the other hand, destroy can be used to destroy an existing audio room, whether created dynamically or statically, and has to be formatted as follows:

{
        "request" : "destroy",
        "room" : <unique numeric ID of the room to destroy>,
        "secret" : "<room secret, mandatory if configured>",
        "permanent" : <true|false, whether the room should be also removed from the config file, default=false>
}

A successful destruction procedure will result in a destroyed response:

{
        "audiobridge" : "destroyed",
        "room" : <unique numeric ID>
}

This will also result in a destroyed event being sent to all the participants in the audio room, which will look like this:

{
        "audiobridge" : "destroyed",
        "room" : <unique numeric ID of the destroyed room>
}

You can check whether a room exists using the exists request, which has to be formatted as follows:

{
        "request" : "exists",
        "room" : <unique numeric ID of the room to check>
}

A successful request will result in a success response:

{
        "audiobridge" : "success",
        "room" : <unique numeric ID>,
        "exists" : <true|false>
}

You can configure whether to check tokens or add/remove people who can join a room using the allowed request, which has to be formatted as follows:

{
        "request" : "allowed",
        "secret" : "<room secret, mandatory if configured>",
        "action" : "enable|disable|add|remove",
        "room" : <unique numeric ID of the room to update>,
        "allowed" : [
                // Array of strings (tokens users might pass in "join", only for add|remove)
        ]
}

A successful request will result in a success response:

{
        "audiobridge" : "success",
        "room" : <unique numeric ID>,
        "allowed" : [
                // Updated, complete, list of allowed tokens (only for enable|add|remove)
        ]
}

If you're the administrator of a room (that is, you created it and have access to the secret) you can kick participants using the kick request. Notice that this only kicks the user out of the room, but does not prevent them from re-joining: to ban them, you need to first remove them from the list of authorized users (see allowed request) and then kick them. The kick request has to be formatted as follows:

{
        "request" : "kick",
        "secret" : "<room secret, mandatory if configured>",
        "room" : <unique numeric ID of the room>,
        "id" : <unique numeric ID of the participant to kick>
}

A successful request will result in a success response:

{
        "audiobridge" : "success",
}

To get a list of the available rooms (excluded those configured or created as private rooms) you can make use of the list request, which has to be formatted as follows:

{
        "request" : "list"
}

A successful request will produce a list of rooms in a success response:

{
        "audiobridge" : "success",
        "rooms" : [             // Array of room objects
                {       // Room #1
                        "room" : <unique numeric ID>,
                        "description" : "<Name of the room>",
                        "pin_required" : <true|false, whether a PIN is required to join this room>,
                        "sampling_rate" : <sampling rate of the mixer>,
                        "record" : <true|false, whether the room is being recorded>,
                        "num_participants" : <count of the participants>
                },
                // Other rooms
        ]
}

To get a list of the participants in a specific room, instead, you can make use of the listparticipants request, which has to be formatted as follows:

{
        "request" : "listparticipants",
        "room" : <unique numeric ID of the room>
}

A successful request will produce a list of participants in a participants response:

{
        "audiobridge" : "participants",
        "room" : <unique numeric ID of the room>,
        "participants" : [              // Array of participant objects
                {       // Participant #1
                        "id" : <unique numeric ID of the participant>,
                        "display" : "<display name of the participant, if any; optional>",
                        "setup" : <true|false, whether user successfully negotiate a WebRTC PeerConnection or not>,
                        "muted" : <true|false, whether user is muted or not>,
                        "talking" : <true|false, whether user is talking or not (only if audio levels are used)>,
                },
                // Other participants
        ]
}

To mark the Opus decoder context for the current participant as invalid and force it to be recreated, use the resetdecoder request:

{
        "request" : "resetdecoder"
}

A successful request will produce a success response:

{
        "audiobridge" : "success"
}

You can add a new RTP forwarder for an existing room using the rtp_forward request, which has to be formatted as follows:

{
        "request" : "rtp_forward",
        "room" : <unique numeric ID of the room to add the forwarder to>,
        "ssrc" : <SSRC to use to use when streaming (optional: stream_id used if missing)>,
        "codec" : "<opus (default), pcma (A-Law) or pcmu (mu-Law)>",
        "ptype" : <payload type to use when streaming (optional: 100 used if missing)>,
        "host" : "<host address to forward the RTP packets to>",
        "host_family" : "<ipv4|ipv6, if we need to resolve the host address to an IP; by default, whatever we get>",
        "port" : <port to forward the RTP packets to>,
        "srtp_suite" : <length of authentication tag (32 or 80); optional>,
        "srtp_crypto" : "<key to use as crypto (base64 encoded key as in SDES); optional>",
        "always_on" : <true|false, whether silence should be forwarded when the room is empty>
}

Notice that, as explained above, in case you configured an admin_key property and extended it to RTP forwarding as well, you'll need to provide it in the request as well or it will be rejected as unauthorized. By default no limitation is posed on rtp_forward .

A successful request will result in a success response:

{
        "audiobridge" : "success",
        "room" : <unique numeric ID, same as request>,
        "stream_id" : <unique numeric ID assigned to the new RTP forwarder>,
        "host" : "<host this forwarder is streaming to, same as request if not resolved>",
        "port" : <audio port this forwarder is streaming to, same as request>
}

To stop a previously created RTP forwarder and stop it, you can use the stop_rtp_forward request, which has to be formatted as follows:

{
        "request" : "stop_rtp_forward",
        "room" : <unique numeric ID of the room to remove the forwarder from>,
        "stream_id" : <unique numeric ID of the RTP forwarder>
}

A successful request will result in a success response:

{
        "audiobridge" : "success",
        "room" : <unique numeric ID, same as request>,
        "stream_id" : <unique numeric ID, same as request>
}

To get a list of the forwarders in a specific room, instead, you can make use of the listforwarders request, which has to be formatted as follows:

{
        "request" : "listforwarders",
        "room" : <unique numeric ID of the room>
}

A successful request will produce a list of RTP forwarders in a forwarders response:

{
        "audiobridge" : "forwarders",
        "room" : <unique numeric ID of the room>,
        "rtp_forwarders" : [            // Array of RTP forwarder objects
                {       // RTP forwarder #1
                        "stream_id" : <unique numeric ID of the forwarder>,
                        "ip" : "<IP this forwarder is streaming to>",
                        "port" : <port this forwarder is streaming to>,
                        "ssrc" : <SSRC this forwarder is using, if any>,
                        "codec" : <codec this forwarder is using, if any>,
                        "ptype" : <payload type this forwarder is using, if any>,
                        "srtp" : <true|false, whether the RTP stream is encrypted>,
                        "always_on" : <true|false, whether this forwarder works even when no participant is in or not>
                },
                // Other forwarders
        ]
}

As anticipated, while the AudioBridge is mainly meant to allow real users to interact with each other by mixing their contributions, you can also start the playback of one or more pre-recorded audio files in a mix: this is especially useful whenever you have, for instance, to play an announcement of some sort, or when maybe you want to play some background music (e.g., some music on hold when the room is empty). You can start the playback of an .opus file in an existing room using the play_file request, which has to be formatted as follows:

{
        "request" : "play_file",
        "room" : <unique numeric ID of the room to play the file in>,
        "secret" : "<room password, if configured>",
        "file_id": "<unique string ID of the announcement; random if not provided>",
        "filename": "<path to the Opus file to play>",
        "loop": <true|false, depending on whether or not the file should be played in a loop forever>
}

Notice that, as explained above, in case you configured an admin_key property and extended it to RTP forwarding as well, you'll need to provide it in the request as well or it will be rejected as unauthorized. By default play_file only requires the room secret, meaning only people authorized to edit the room can start an audio playback.

Also notice that the only supported files are .opus files: no other audio format will be accepted. Besides, the file must be reachable and available on the file system: network addresses (e.g., HTTP URL) are NOT supported.

A successful request will result in a success response:

{
        "audiobridge" : "success",
        "room" : <unique numeric ID, same as request>,
        "file_id" : "<unique string ID of the announcement, same as request if provided or randomly generated otherwise>"
}

As soon as the playback actually starts (usually immediately after the request has been sent), an event is sent to all participants so that they're aware something is being played back in the room besides themselves:

{
        "audiobridge" : "announcement-started",
        "room" : <unique numeric ID, same as request>,
        "file_id" : "<unique string ID of the announcement>"
}

A similar event is also sent whenever the playback stops, whether it's because the file ended and loop was FALSE (which will automatically clear the resources) or because a stop_file request asked for the playback to be interrupted:

{
        "audiobridge" : "announcement-stopped",
        "room" : <unique numeric ID, same as request>,
        "file_id" : "<unique string ID of the announcement>"
}

You can check whether a specific playback is still going on in a room, you can use the is_playing request, which has to be formatted as follows:

{
        "request" : "is_playing",
        "room" : <unique numeric ID of the room where the playback is taking place>,
        "secret" : "<room password, if configured>",
        "file_id" : "<unique string ID of the announcement>"
}

A successful request will result in a success response:

{
        "audiobridge" : "success",
        "room" : <unique numeric ID>,
        "file_id" : "<unique string ID of the announcement>",
        "playing" : <true|false>
}

As anticipated, when not looping a playback will automatically stop and self-destruct when it reaches the end of the audio file. In case you want to stop a playback sooner than that, or want to stop a looped playback, you can use the stop_file request:

{
        "request" : "stop_file",
        "room" : <unique numeric ID of the room where the playback is taking place>,
        "secret" : "<room password, if configured>",
        "file_id": "<unique string ID of the announcement>"
}

A successful request will result in a success response:

{
        "audiobridge" : "success",
        "room" : <unique numeric ID, same as request>,
        "file_id" : "<unique string ID of the now interrupted announcement>"
}

That completes the list of synchronous requests you can send to the AudioBridge plugin. As anticipated, though, there are also several asynchronous requests you can send, specifically those related to joining and updating one's presence as a participant in an audio room.

The way you'd interact with the plugin is usually as follows:

  1. you use a join request to join an audio room, and wait for the joined event; this event will also include a list of the other participants, if any;
  2. you send a configure request attached to an audio-only JSEP offer to start configuring your participation in the room (e.g., join unmuted or muted), and wait for a configured event, which will be attached to a JSEP answer by the plugin to complete the setup of the WebRTC PeerConnection;
  3. you send other configure requests (without any JSEP-related attachment) to mute/unmute yourself during the audio conference;
  4. you intercept events originated by the plugin (joined , leaving ) to notify you about users joining/leaving/muting/unmuting;
  5. you eventually send a leave request to leave a room; if you leave the PeerConnection instance intact, you can subsequently join a different room without requiring a new negotiation (and so just use a join + JSEP-less configure to join).

Notice that there's also a changeroom request available: you can use this request to immediately leave the room you're in and join a different one, without requiring you to do a leave + join + configure round. Of course remember not to pass any JSEP-related payload when doing a changeroom as the same pre-existing PeerConnection will be re-used for the purpose.

About the syntax of all the above mentioned requests, join has to be formatted as follows:

{
        "request" : "join",
        "room" : <numeric ID of the room to join>,
        "id" : <unique ID to assign to the participant; optional, assigned by the plugin if missing>,
        "pin" : "<password required to join the room, if any; optional>",
        "display" : "<display name to have in the room; optional>",
        "token" : "<invitation token, in case the room has an ACL; optional>",
        "muted" : <true|false, whether to start unmuted or muted>,
        "codec" : "<codec to use, among opus (default), pcma (A-Law) or pcmu (mu-Law)>",
        "prebuffer" : <number of packets to buffer before decoding this participant (default=room value, or DEFAULT_PREBUFFERING)>,
        "quality" : <0-10, Opus-related complexity to use, the higher the value, the better the quality (but more CPU); optional, default is 4>,
        "volume" : <percent value, <100 reduces volume, >100 increases volume; optional, default is 100 (no volume change)>,
        "secret" : "<room management password; optional, if provided the user is an admin and can't be globally muted with mute_room>",
        "audio_level_average" : "<if provided, overrides the room audio_level_average for this user; optional>",
        "audio_active_packets" : "<if provided, overrides the room audio_active_packets for this user; optional>"
}

A successful request will produce a joined event:

{
        "audiobridge" : "joined",
        "room" : <numeric ID of the room>,
        "id" : <unique ID assigned to the participant>,
        "display" : "<display name of the new participant>",
        "participants" : [
                // Array of existing participants in the room
        ]
}

The other participants in the room will be notified about the new participant by means of a different joined event, which will only include the room and the new participant as the only object in a participants array.

At this point, the media-related settings of the participant can be modified by means of a configure request. The configure request has to be formatted as follows (notice that all parameters except request are optional, depending on what you want to change):

{
        "request" : "configure",
        "muted" : <true|false, whether to unmute or mute>,
        "display" : "<new display name to have in the room>",
        "prebuffer" : <new number of packets to buffer before decoding this participant (see "join" for more info)>,
        "quality" : <new Opus-related complexity to use (see "join" for more info)>,
        "volume" : <new volume percent value (see "join" for more info)>,
        "record": <true|false, whether to record this user's contribution to a .mjr file (mixer not involved),
        "filename": "<basename of the file to record to, -audio.mjr will be added by the plugin>"
}

muted instructs the plugin to mute or unmute the participant; quality changes the complexity of the Opus encoder for the participant; record can be used to record this participant's contribution to a Janus .mjr file, and filename to provide a basename for the path to save the file to (notice that this is different from the recording of a whole room: this feature only records the packets this user is sending, and is not related to the mixer stuff). A successful request will result in a ok event:

{
        "audiobridge" : "event",
        "room" : <numeric ID of the room>,
        "result" : "ok"
}

In case the muted property was modified, the other participants in the room will be notified about this by means of a event notification, which will only include the room and the updated participant as the only object in a participants array.

If you're the administrator of a room (that is, you created it and have access to the secret) you can mute or unmute individual participants using the mute or unmute request

{
        "request" : "<mute|unmute, whether to mute or unmute >",
        "secret" : "<room secret, mandatory if configured>",
        "room" : <unique numeric ID of the room>,
        "id" : <unique numeric ID of the participant to mute|unmute>
}

A successful request will result in a success response:

{
        "audiobridge" : "success",
}

To mute/unmute the whole room, use mute_room and unmute_room instead.

{
        "request" : "<mute|unmute, whether to mute or unmute >",
        "secret" : "<room secret, mandatory if configured>",
        "room" : <unique numeric ID of the room>,
        "id" : <unique numeric ID of the participant to mute|unmute>
}

A successful request will result in a success response:

{
        "audiobridge" : "success",
}

As anticipated, you can leave an audio room using the leave request, which has to be formatted as follows:

{
        "request" : "leave"
}

All the participants will receive an event notification with the ID of the participant who just left:

{
        "audiobridge" : "event",
        "room" : <numeric ID of the room>,
        "leaving" : <numeric ID of the participant who left>
}

For what concerns the changeroom request, instead, it's pretty much the same as a join request and as such has to be formatted as follows:

{
        "request" : "changeroom",
        "room" : <numeric ID of the room to move to>,
        "id" : <unique ID to assign to the participant; optional, assigned by the plugin if missing>,
        "display" : "<display name to have in the room; optional>",
        "token" : "<invitation token, in case the new room has an ACL; optional>",
        "muted" : <true|false, whether to start unmuted or muted>,
        "quality" : <0-10, Opus-related complexity to use, lower is higher quality; optional, default is 4>
}

Such a request will trigger all the above-described leaving/joined events to the other participants, as it is indeed wrapping a leave followed by a join and as such the other participants in both rooms need to be updated accordingly. The participant who switched room instead will be sent a roomchanged event which is pretty similar to what joined looks like:

A successful request will produce a joined event:

{
        "audiobridge" : "roomchanged",
        "room" : <numeric ID of the new room>,
        "id" : <unique ID assigned to the participant in the new room>,
        "display" : "<display name of the new participant>",
        "participants" : [
                // Array of existing participants in the new room
        ]
}

As a last note, notice that the AudioBridge plugin does support renegotiations, mostly for the purpose of facilitating ICE restarts: in fact, there isn't much need for renegotiations outside of that context, as PeerConnections here will typically always contain a single m-line for audio, and so adding/removing streams makes no sense; besides, muting and unmuting is available via APIs, meaning that updating the media direction via SDP renegotiations would be overkill.

To force a renegotiation, all you need to do is send the new JSEP offer together with a configure request: this request doesn't need to contain any directive at all, and can be empty. A JSEP answer will be sent back along the result of the request, if successful.