Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 102 additions & 2 deletions src/plugins/janus_audiobridge.c
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,8 @@ room-<unique room ID>: {
"port" : <port you want media to be sent to>,
"payload_type" : <payload type to use for RTP packets (optional; only needed in case Opus is used, automatic for G.711)>,
"audiolevel_ext" : <ID of the audiolevel RTP extension, if used (optional)>,
"fec" : <true|false, whether FEC from Janus to the user should be enabled for the Opus stream (optional; only needed in case Opus is used)>
"fec" : <true|false, whether FEC from Janus to the user should be enabled for the Opus stream (optional; only needed in case Opus is used)>,
"dtmf_pt" : <RTP payload type used for incoming RFC2833 DTMF packets. If set, RTP packets matching dtmf_pt will be emitted as DTMF events (optional)>
}
}
\endverbatim
Expand Down Expand Up @@ -974,6 +975,17 @@ room-<unique room ID>: {
* If you're interested in supporting scenarios where the AudioBridge
* "dials out" (e.g., for outgoing INVITES to SIP endpoints) check the
* \ref aboffer section.
* if a maching rtp with rfc2833 payload type is received the fallowing dtmf event is published.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typos: start with a capital letter, maching, rtp (packet), fallowing

*
\verbatim
{
"audiobridge" : "event",
"room" : <numeric ID of the room>,
"id" : <unique ID assigned to the participant>,
"dtmf_signal" : "<dtmf signal received, values: 1-9, *, #, A-D>",
"dtmf_duration" : <duration of the dtmf signal>
}
\endverbatim
*
* At this point, whether the participant will be interacting via WebRTC
* or plain RTP, the media-related settings of the participant can be
Expand All @@ -995,6 +1007,14 @@ room-<unique room ID>: {
"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; will be relative to mjrs_dir, if configured in the room>",
"group" : "<new group to assign to this participant, if enabled in the room (for forwarding purposes)>"
"rtp" : {
"ip" : "<IP address you want media to be sent to>",
"port" : <port you want media to be sent to>,
"payload_type" : <payload type to use for RTP packets (optional; only needed in case Opus is used, automatic for G.711)>,
"audiolevel_ext" : <ID of the audiolevel RTP extension, if used (optional)>,
"fec" : <true|false, whether FEC should be enabled for the Opus stream (optional; only needed in case Opus is used)>,
"dtmf_pt": <RFC2833 RTP payload type that dtmf signal will be received (optional)>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please rephrase

}
}
\endverbatim
*
Expand All @@ -1004,7 +1024,9 @@ room-<unique room ID>: {
* to a Janus .mjr file, and \c 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 \c ok event:
* related to the mixer stuff). \c rtp is used in case of plain RTP participant
* to set/update remote rtp information while keeping local rtp which is already
* been provided with joined event. A successful request will result in a \c ok event:
*
\verbatim
{
Expand Down Expand Up @@ -1423,6 +1445,7 @@ static struct janus_json_parameter rtp_parameters[] = {
{"payload_type", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
{"audiolevel_ext", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
{"fec", JANUS_JSON_BOOL, 0},
{"dtmf_pt", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}
};
static struct janus_json_parameter configure_parameters[] = {
{"muted", JANUS_JSON_BOOL, 0},
Expand Down Expand Up @@ -1735,6 +1758,12 @@ static void janus_audiobridge_file_free(janus_audiobridge_file *ctx) {
}
#endif

/* In case we need to support plain RTP participants, this struct helps with that */
typedef struct janus_audiobridge_plainrtp_dtmf {
uint16_t dtmf_event_id;
uint32_t timestamp;
} janus_audiobridge_plainrtp_dtmf;

/* In case we need to support plain RTP participants, this struct helps with that */
typedef struct janus_audiobridge_plainrtp_media {
char *remote_audio_ip;
Expand All @@ -1748,6 +1777,8 @@ typedef struct janus_audiobridge_plainrtp_media {
int pipefd[2];
GThread *thread;
volatile int initialized;
int dtmf_pt;
janus_audiobridge_plainrtp_dtmf latest_dtmf;
} janus_audiobridge_plainrtp_media;
static void janus_audiobridge_plainrtp_media_cleanup(janus_audiobridge_plainrtp_media *media);
static int janus_audiobridge_plainrtp_allocate_port(janus_audiobridge_plainrtp_media *media);
Expand Down Expand Up @@ -2353,6 +2384,51 @@ static int janus_audiobridge_create_opus_encoder_if_needed(janus_audiobridge_roo
return 0;
}

static uint16_t dtmf_keys[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#', 'A', 'B', 'C', 'D'};
/* Check peer RTP has RFC2833 and push event */
static void janus_audiobridge_send_dtmf_event(janus_audiobridge_participant *participant, char *buffer, int len) {
if(participant->plainrtp_media.dtmf_pt <= 0)
return;
janus_rtp_header *rtp_header = (janus_rtp_header *)buffer;
if(rtp_header->type != participant->plainrtp_media.dtmf_pt)
return;
int plen = 0;
char *payload_buffer = janus_rtp_payload(buffer, len, &plen);
if(plen < 0 || (size_t)plen < sizeof(janus_rtp_rfc2833_payload))
return;
janus_rtp_rfc2833_payload *rfc2833_payload = (janus_rtp_rfc2833_payload *)payload_buffer;
uint16_t duration = ntohs(rfc2833_payload->duration);
if(rfc2833_payload->end == 0)
return;

/* Set up last dtmf to avoid duplication */
if(participant->plainrtp_media.latest_dtmf.dtmf_event_id == rfc2833_payload->event && participant->plainrtp_media.latest_dtmf.timestamp == rtp_header->timestamp)
return;
participant->plainrtp_media.latest_dtmf.dtmf_event_id = rfc2833_payload->event;
participant->plainrtp_media.latest_dtmf.timestamp = rtp_header->timestamp;

/* Parse dtmf key */
uint16_t dtmf_key;
if(rfc2833_payload->event > 15)
return;
dtmf_key = dtmf_keys[rfc2833_payload->event];
char dtmf_key_str[2];
dtmf_key_str[0] = dtmf_key;
dtmf_key_str[1] = '\0';

/* Notify the application */
json_t *info = json_object();
json_object_set_new(info, "audiobridge", json_string("event"));
json_object_set_new(info, "room", string_ids ? json_string(participant->room->room_id_str) : json_integer(participant->room->room_id));
json_object_set_new(info, "id", string_ids ? json_string(participant->user_id_str) : json_integer(participant->user_id));
json_object_set_new(info, "dtmf_signal", json_string(dtmf_key_str));
json_object_set_new(info, "dtmf_duration", json_integer(duration));
int ret = gateway->push_event(participant->session->handle, &janus_audiobridge_plugin, NULL, info, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(info);
return;
}

static int janus_audiobridge_create_static_rtp_forwarder(janus_config_category *cat, janus_audiobridge_room *audiobridge) {
guint32 forwarder_id = 0;
janus_config_item *forwarder_id_item = janus_config_get(config, cat, janus_config_type_item, "rtp_forward_id");
Expand Down Expand Up @@ -7036,6 +7112,17 @@ static void *janus_audiobridge_handler(void *data) {
opus_encoder_ctl(participant->encoder, OPUS_SET_INBAND_FEC(participant->fec));
opus_encoder_ctl(participant->encoder, OPUS_SET_PACKET_LOSS_PERC(participant->expected_loss));
}
/* rfc2833 payload type is set */
json_t *dtmf_pt_json = json_object_get(rtp, "dtmf_pt");
if(dtmf_pt_json) {
int dtmf_pt = json_integer_value(dtmf_pt_json);
if(janus_is_dynamic_payload_type(dtmf_pt)) {
participant->plainrtp_media.dtmf_pt = dtmf_pt;
} else {
participant->plainrtp_media.dtmf_pt = -1;
JANUS_LOG(LOG_WARN, "Invalid dtmf_pt %"SCNi32",\n", dtmf_pt);
}
}
/* Create the socket */
janus_mutex_lock(&participant->pmutex);
janus_audiobridge_plainrtp_media_cleanup(&participant->plainrtp_media);
Expand Down Expand Up @@ -7292,6 +7379,17 @@ static void *janus_audiobridge_handler(void *data) {
const char *ip = json_string_value(json_object_get(rtp, "ip"));
uint16_t port = json_integer_value(json_object_get(rtp, "port"));
janus_mutex_lock(&participant->pmutex);
/* rfc2833 payload type is set */
json_t *dtmf_pt_json = json_object_get(rtp, "dtmf_pt");
if(dtmf_pt_json) {
int dtmf_pt = json_integer_value(dtmf_pt_json);
if(janus_is_dynamic_payload_type(dtmf_pt)) {
participant->plainrtp_media.dtmf_pt = dtmf_pt;
} else {
participant->plainrtp_media.dtmf_pt = -1;
JANUS_LOG(LOG_WARN, "Invalid dtmf_pt %"SCNi32",\n", dtmf_pt);
}
}
if(ip == NULL || port == 0) {
JANUS_LOG(LOG_ERR, "[AudioBridge-%p] Can't connect socket, no remote address provided\n", session);
} else {
Expand Down Expand Up @@ -9586,6 +9684,8 @@ static void *janus_audiobridge_plainrtp_relay_thread(void *data) {
/* Handle as a WebRTC RTP packet */
packet.length = bytes;
janus_audiobridge_incoming_rtp(session->handle, &packet);
/* Handle rtp if rfc2833 event*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that parsing and handling RFC2833 packets in janus_audiobridge_plainrtp_relay_thread is a good idea.
We postpone the inbound packets processing in janus_audiobridge_participant_thread, where packets are being fetched from the jitter buffer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, it seems a little tough for me to solve.
RTP packets other than Opus or G.711 are not directed to, and are not received by, the janus_audiobridge_participant_thread.
The janus_audiobridge_participant_thread gets RTP packets from the jitter buffer based on length:
ret = jitter_buffer_get(participant->jitter, &jbp, participant->codec == JANUS_AUDIOCODEC_OPUS ? 960 : 160, NULL);

Any suggestions would be welcome

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not the length, it's the expected timestamp jump.

janus_audiobridge_send_dtmf_event(participant, buffer, bytes);
continue;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/rtp.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ gboolean janus_is_rtp(char *buf, guint len) {
return ((header->type < 64) || (header->type >= 96));
}

gboolean janus_is_dynamic_payload_type(int payload_type){
if (96 >= payload_type || 127 <= payload_type) {
return FALSE;
}
return TRUE;
}

char *janus_rtp_payload(char *buf, int len, int *plen) {
if(!buf || len < 12)
return NULL;
Expand Down
5 changes: 5 additions & 0 deletions src/rtp.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ int janus_videocodec_pt(janus_videocodec vcodec);
* @param[in] len Length of the buffer to inspect */
gboolean janus_is_rtp(char *buf, guint len);

/*! \brief Checks if the given payload type falls within the dynamic range [96, 127].
* @param[in] payload_type The payload type value to check.
* @returns TRUE if the payload type is in the dynamic range, FALSE otherwise. */
gboolean janus_is_dynamic_payload_type(int payload_type);

/*! \brief Helper to quickly access the RTP payload, skipping header and extensions
* @param[in] buf The packet data
* @param[in] len The packet data length in bytes
Expand Down