From 897099d07ca2b0dd10f6100b5f8f90a1d3306dfb Mon Sep 17 00:00:00 2001 From: keremcadirci Date: Thu, 17 Apr 2025 14:30:51 +0300 Subject: [PATCH 1/2] Audiobridge, rfc2733 dtmf event is recognized and dtmf event is published --- src/plugins/janus_audiobridge.c | 109 +++++++++++++++++++++++++++++++- src/rtp.c | 7 ++ src/rtp.h | 4 ++ 3 files changed, 118 insertions(+), 2 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 7a359982ab..3e3a672072 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -946,7 +946,8 @@ room-: { "port" : , "payload_type" : , "audiolevel_ext" : , - "fec" : + "fec" : , + "dtmf_pt" : } } \endverbatim @@ -974,6 +975,19 @@ room-: { * 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. +\verbatim +{ + "audiobridge" : "event", + "room" : , + "id" : , + "result" : { + "event" : "dtmf", + "signal" : "", + "duration" : + } +} +\endverbatim * * At this point, whether the participant will be interacting via WebRTC * or plain RTP, the media-related settings of the participant can be @@ -995,6 +1009,14 @@ room-: { "record": ", "group" : "" + "rtp" : { + "ip" : "", + "port" : , + "payload_type" : , + "audiolevel_ext" : , + "fec" : , + "dtmf_pt": + } } \endverbatim * @@ -1004,7 +1026,9 @@ room-: { * 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 { @@ -1735,6 +1759,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_plainrtp_dtmf { + uint16_t dtmf_event_id; + uint32_t timestamp; +} janus_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; @@ -1748,6 +1778,8 @@ typedef struct janus_audiobridge_plainrtp_media { int pipefd[2]; GThread *thread; volatile int initialized; + int dtmf_pt; + janus_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); @@ -2353,6 +2385,54 @@ 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_send_rfc2833_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_t *result = json_object(); + json_object_set_new(result, "event", json_string("dtmf")); + json_object_set_new(result, "signal", json_string(dtmf_key_str)); + json_object_set_new(result, "duration", json_integer(duration)); + json_object_set_new(info, "result", result); + 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"); @@ -7036,6 +7116,15 @@ 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 */ + int dtmf_pt = json_integer_value(json_object_get(rtp, "dtmf_pt")); + if(janus_is_rfc2833_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); @@ -7292,6 +7381,18 @@ 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_rfc2833_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 { @@ -9586,6 +9687,10 @@ 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*/ + if(janus_is_rfc2833_payload_type(header->type) && header->type==participant->plainrtp_media.dtmf_pt) { + janus_send_rfc2833_event(participant, buffer, bytes); + } continue; } } diff --git a/src/rtp.c b/src/rtp.c index c810480031..ec3b0ae5a6 100644 --- a/src/rtp.c +++ b/src/rtp.c @@ -35,6 +35,13 @@ gboolean janus_is_rtp(char *buf, guint len) { return ((header->type < 64) || (header->type >= 96)); } +gboolean janus_is_rfc2833_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; diff --git a/src/rtp.h b/src/rtp.h index 365bd39054..a52ba40156 100644 --- a/src/rtp.h +++ b/src/rtp.h @@ -154,6 +154,10 @@ 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 Helper method to validate if payload type might be a RFC2833 (dtmf) between 96 and 127 + * @param[in] int Payload_Type */ +gboolean janus_is_rfc2833_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 From 09900a420a0a8880bc0d2eb22e290de3db5871b3 Mon Sep 17 00:00:00 2001 From: keremcadirci Date: Tue, 22 Apr 2025 23:53:19 +0300 Subject: [PATCH 2/2] Audiobridge, rfc2733 dtmf event updates - #3538 --- src/plugins/janus_audiobridge.c | 53 +++++++++++++++------------------ src/rtp.c | 4 +-- src/rtp.h | 7 +++-- 3 files changed, 30 insertions(+), 34 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 3e3a672072..e5ceed6bc9 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -947,7 +947,7 @@ room-: { "payload_type" : , "audiolevel_ext" : , "fec" : , - "dtmf_pt" : + "dtmf_pt" : } } \endverbatim @@ -976,16 +976,14 @@ room-: { * "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. + * \verbatim { "audiobridge" : "event", "room" : , "id" : , - "result" : { - "event" : "dtmf", - "signal" : "", - "duration" : - } + "dtmf_signal" : "", + "dtmf_duration" : } \endverbatim * @@ -1447,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}, @@ -1760,10 +1759,10 @@ 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_plainrtp_dtmf { +typedef struct janus_audiobridge_plainrtp_dtmf { uint16_t dtmf_event_id; uint32_t timestamp; -} janus_plainrtp_dtmf; +} janus_audiobridge_plainrtp_dtmf; /* In case we need to support plain RTP participants, this struct helps with that */ typedef struct janus_audiobridge_plainrtp_media { @@ -1779,7 +1778,7 @@ typedef struct janus_audiobridge_plainrtp_media { GThread *thread; volatile int initialized; int dtmf_pt; - janus_plainrtp_dtmf latest_dtmf; + 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); @@ -2387,7 +2386,7 @@ static int janus_audiobridge_create_opus_encoder_if_needed(janus_audiobridge_roo 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_send_rfc2833_event(janus_audiobridge_participant *participant, char *buffer, int len) { +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; @@ -2422,11 +2421,8 @@ static void janus_send_rfc2833_event(janus_audiobridge_participant *participant, 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_t *result = json_object(); - json_object_set_new(result, "event", json_string("dtmf")); - json_object_set_new(result, "signal", json_string(dtmf_key_str)); - json_object_set_new(result, "duration", json_integer(duration)); - json_object_set_new(info, "result", result); + 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); @@ -7117,13 +7113,15 @@ static void *janus_audiobridge_handler(void *data) { opus_encoder_ctl(participant->encoder, OPUS_SET_PACKET_LOSS_PERC(participant->expected_loss)); } /* rfc2833 payload type is set */ - int dtmf_pt = json_integer_value(json_object_get(rtp, "dtmf_pt")); - if(janus_is_rfc2833_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); + 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); @@ -7382,13 +7380,12 @@ static void *janus_audiobridge_handler(void *data) { 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"); + 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_rfc2833_payload_type(dtmf_pt)) { + if(janus_is_dynamic_payload_type(dtmf_pt)) { participant->plainrtp_media.dtmf_pt = dtmf_pt; - } - else { + } else { participant->plainrtp_media.dtmf_pt = -1; JANUS_LOG(LOG_WARN, "Invalid dtmf_pt %"SCNi32",\n", dtmf_pt); } @@ -9688,9 +9685,7 @@ static void *janus_audiobridge_plainrtp_relay_thread(void *data) { packet.length = bytes; janus_audiobridge_incoming_rtp(session->handle, &packet); /* Handle rtp if rfc2833 event*/ - if(janus_is_rfc2833_payload_type(header->type) && header->type==participant->plainrtp_media.dtmf_pt) { - janus_send_rfc2833_event(participant, buffer, bytes); - } + janus_audiobridge_send_dtmf_event(participant, buffer, bytes); continue; } } diff --git a/src/rtp.c b/src/rtp.c index ec3b0ae5a6..fa9f534322 100644 --- a/src/rtp.c +++ b/src/rtp.c @@ -35,8 +35,8 @@ gboolean janus_is_rtp(char *buf, guint len) { return ((header->type < 64) || (header->type >= 96)); } -gboolean janus_is_rfc2833_payload_type(int payload_type){ - if( 96 > payload_type || 127 < payload_type) { +gboolean janus_is_dynamic_payload_type(int payload_type){ + if (96 >= payload_type || 127 <= payload_type) { return FALSE; } return TRUE; diff --git a/src/rtp.h b/src/rtp.h index a52ba40156..a665c1883e 100644 --- a/src/rtp.h +++ b/src/rtp.h @@ -154,9 +154,10 @@ 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 Helper method to validate if payload type might be a RFC2833 (dtmf) between 96 and 127 - * @param[in] int Payload_Type */ -gboolean janus_is_rfc2833_payload_type(int payload_type); +/*! \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