Fork me on GitHub
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
Data Structures | Macros | Typedefs | Functions
janus_audiobridge.c File Reference

Janus AudioBridge plugin. More...

#include "plugin.h"
#include <jansson.h>
#include <opus/opus.h>
#include <sys/time.h>
#include "../debug.h"
#include "../apierror.h"
#include "../config.h"
#include "../mutex.h"
#include "../rtp.h"
#include "../rtcp.h"
#include "../utils.h"
Include dependency graph for janus_audiobridge.c:

Data Structures

struct  janus_audiobridge_message
struct  janus_audiobridge_room
struct  janus_audiobridge_session
struct  janus_audiobridge_rtp_context
struct  janus_audiobridge_participant
struct  janus_audiobridge_rtp_relay_packet
struct  wav_header

Macros

#define JANUS_AUDIOBRIDGE_VERSION   8
#define JANUS_AUDIOBRIDGE_VERSION_STRING   "0.0.8"
#define JANUS_AUDIOBRIDGE_DESCRIPTION   "This is a plugin implementing an audio conference bridge for Janus, mixing Opus streams."
#define JANUS_AUDIOBRIDGE_NAME   "JANUS AudioBridge plugin"
#define JANUS_AUDIOBRIDGE_AUTHOR   "Meetecho s.r.l."
#define JANUS_AUDIOBRIDGE_PACKAGE   "janus.plugin.audiobridge"
#define sdp_template
#define BUFFER_SAMPLES   8000
#define OPUS_SAMPLES   160
#define USE_FEC   0
#define DEFAULT_COMPLEXITY   4
#define JANUS_AUDIOBRIDGE_ERROR_UNKNOWN_ERROR   499
#define JANUS_AUDIOBRIDGE_ERROR_NO_MESSAGE   480
#define JANUS_AUDIOBRIDGE_ERROR_INVALID_JSON   481
#define JANUS_AUDIOBRIDGE_ERROR_INVALID_REQUEST   482
#define JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT   483
#define JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT   484
#define JANUS_AUDIOBRIDGE_ERROR_NO_SUCH_ROOM   485
#define JANUS_AUDIOBRIDGE_ERROR_ROOM_EXISTS   486
#define JANUS_AUDIOBRIDGE_ERROR_NOT_JOINED   487
#define JANUS_AUDIOBRIDGE_ERROR_LIBOPUS_ERROR   488
#define JANUS_AUDIOBRIDGE_ERROR_UNAUTHORIZED   489
#define JANUS_AUDIOBRIDGE_ERROR_ID_EXISTS   490
#define JANUS_AUDIOBRIDGE_ERROR_ALREADY_JOINED   491

Typedefs

typedef struct
janus_audiobridge_message 
janus_audiobridge_message
typedef struct
janus_audiobridge_room 
janus_audiobridge_room
typedef struct
janus_audiobridge_session 
janus_audiobridge_session
typedef struct
janus_audiobridge_rtp_context 
janus_audiobridge_rtp_context
typedef struct
janus_audiobridge_participant 
janus_audiobridge_participant
typedef struct
janus_audiobridge_rtp_relay_packet 
janus_audiobridge_rtp_relay_packet
typedef struct wav_header wav_header

Functions

janus_plugincreate (void)
int janus_audiobridge_init (janus_callbacks *callback, const char *config_path)
void janus_audiobridge_destroy (void)
int janus_audiobridge_get_api_compatibility (void)
int janus_audiobridge_get_version (void)
const char * janus_audiobridge_get_version_string (void)
const char * janus_audiobridge_get_description (void)
const char * janus_audiobridge_get_name (void)
const char * janus_audiobridge_get_author (void)
const char * janus_audiobridge_get_package (void)
void janus_audiobridge_create_session (janus_plugin_session *handle, int *error)
struct janus_plugin_resultjanus_audiobridge_handle_message (janus_plugin_session *handle, char *transaction, char *message, char *sdp_type, char *sdp)
void janus_audiobridge_setup_media (janus_plugin_session *handle)
void janus_audiobridge_incoming_rtp (janus_plugin_session *handle, int video, char *buf, int len)
void janus_audiobridge_incoming_rtcp (janus_plugin_session *handle, int video, char *buf, int len)
void janus_audiobridge_hangup_media (janus_plugin_session *handle)
void janus_audiobridge_destroy_session (janus_plugin_session *handle, int *error)
char * janus_audiobridge_query_session (janus_plugin_session *handle)
void janus_audiobridge_message_free (janus_audiobridge_message *msg)
void * janus_audiobridge_watchdog (void *data)

Detailed Description

Janus AudioBridge plugin.

Author
Lorenzo Miniero loren.nosp@m.zo@m.nosp@m.eetec.nosp@m.ho.c.nosp@m.om

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.cfg and includes a demo room for testing.

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

[<unique room ID>]
description = This is my awesome room
is_private = yes|no (private rooms don't appear when you do a 'list' request)
secret = <password needed for manipulating (e.g. destroying) the room>
sampling_rate = <sampling rate> (e.g., 16000 for wideband mixing)
record = true|false (whether this room should be recorded, default=false)
record_file =   /path/to/recording.wav (where to save the recording)

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 , destroy , exists, list, listparticipants and resetdecoder 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; 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; list lists all the available rooms, while listparticipants lists all the participants of a specific room and their details; finally, 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).

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.

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>,
        "description" : "<pretty name of the room, optional>",
        "secret" : "<password required to edit/destroy the room, optional>",
        "is_private" : <true|false, whether the room should appear in a list request>,
        "sampling" : <sampling rate of the room, optional, 16000 by default>,
        "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>
}

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>"
}

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>"
}

A successful destruction procedure will result in a destroyed response:

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

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>
}

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>",
                        "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>",
                        "muted" : <true|false, whether user is muted or not>
                },
                // 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"
}

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>,
        "display" : "<display name to have in the room; 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>
}

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:

{
        "request" : "configure",
        "muted" : <true|false, whether to unmute or mute>,
        "quality" : <0-10, Opus-related complexity to use, lower is higher quality; optional, default is 4>,
}

muted instructs the plugin to mute or unmute the participant; quality changes the complexity of the Opus encoder for the participant. 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.

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>",
        "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
        ]
}

Plugins

Macro Definition Documentation

#define BUFFER_SAMPLES   8000
#define DEFAULT_COMPLEXITY   4
#define JANUS_AUDIOBRIDGE_AUTHOR   "Meetecho s.r.l."
#define JANUS_AUDIOBRIDGE_DESCRIPTION   "This is a plugin implementing an audio conference bridge for Janus, mixing Opus streams."
#define JANUS_AUDIOBRIDGE_ERROR_ALREADY_JOINED   491
#define JANUS_AUDIOBRIDGE_ERROR_ID_EXISTS   490
#define JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT   484
#define JANUS_AUDIOBRIDGE_ERROR_INVALID_JSON   481
#define JANUS_AUDIOBRIDGE_ERROR_INVALID_REQUEST   482
#define JANUS_AUDIOBRIDGE_ERROR_LIBOPUS_ERROR   488
#define JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT   483
#define JANUS_AUDIOBRIDGE_ERROR_NO_MESSAGE   480
#define JANUS_AUDIOBRIDGE_ERROR_NO_SUCH_ROOM   485
#define JANUS_AUDIOBRIDGE_ERROR_NOT_JOINED   487
#define JANUS_AUDIOBRIDGE_ERROR_ROOM_EXISTS   486
#define JANUS_AUDIOBRIDGE_ERROR_UNAUTHORIZED   489
#define JANUS_AUDIOBRIDGE_ERROR_UNKNOWN_ERROR   499
#define JANUS_AUDIOBRIDGE_NAME   "JANUS AudioBridge plugin"
#define JANUS_AUDIOBRIDGE_PACKAGE   "janus.plugin.audiobridge"
#define JANUS_AUDIOBRIDGE_VERSION   8
#define JANUS_AUDIOBRIDGE_VERSION_STRING   "0.0.8"
#define OPUS_SAMPLES   160
#define sdp_template
Value:
"v=0\r\n" \
"o=- %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n" /* We need current time here */ \
"s=%s\r\n" /* Audio bridge name */ \
"t=0 0\r\n" \
"m=audio 1 RTP/SAVPF %d\r\n" /* Opus payload type */ \
"c=IN IP4 1.1.1.1\r\n" \
"a=rtpmap:%d opus/48000/2\r\n" /* Opus payload type */ \
"a=fmtp:%d maxplaybackrate=%"SCNu32"; stereo=0; sprop-stereo=0; useinbandfec=0\r\n" \
/* Opus payload type and room sampling rate */
#define USE_FEC   0

Typedef Documentation

typedef struct wav_header wav_header

Function Documentation

janus_plugin * create ( void  )
void janus_audiobridge_create_session ( janus_plugin_session handle,
int *  error 
)
void janus_audiobridge_destroy ( void  )
void janus_audiobridge_destroy_session ( janus_plugin_session handle,
int *  error 
)
int janus_audiobridge_get_api_compatibility ( void  )
const char * janus_audiobridge_get_author ( void  )
const char * janus_audiobridge_get_description ( void  )
const char * janus_audiobridge_get_name ( void  )
const char * janus_audiobridge_get_package ( void  )
int janus_audiobridge_get_version ( void  )
const char * janus_audiobridge_get_version_string ( void  )
struct janus_plugin_result * janus_audiobridge_handle_message ( janus_plugin_session handle,
char *  transaction,
char *  message,
char *  sdp_type,
char *  sdp 
)
read
void janus_audiobridge_hangup_media ( janus_plugin_session handle)
void janus_audiobridge_incoming_rtcp ( janus_plugin_session handle,
int  video,
char *  buf,
int  len 
)
void janus_audiobridge_incoming_rtp ( janus_plugin_session handle,
int  video,
char *  buf,
int  len 
)
int janus_audiobridge_init ( janus_callbacks callback,
const char *  config_path 
)
void janus_audiobridge_message_free ( janus_audiobridge_message msg)
char * janus_audiobridge_query_session ( janus_plugin_session handle)
void janus_audiobridge_setup_media ( janus_plugin_session handle)
void * janus_audiobridge_watchdog ( void *  data)