Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New programmatic API to ask for keyframes in SIP and NoSIP plugins #3517

Merged
merged 1 commit into from
Feb 19, 2025
Merged
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
100 changes: 97 additions & 3 deletions plugins/janus_nosip.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,11 @@
*
* An \c hangingup event will be sent back, as this is an asynchronous request.
*
* Finally, just as in the SIP and SIPre plugins, the multimedia session
* Finally, just as in the SIP plugin, the multimedia session
* can be recorded. Considering the NoSIP plugin also assumes two peers
* are in a call with each other (although it makes no assumptions on
* the signalling that ties them together), it works exactly the same
* way as the SIP and SIPre plugin do when it comes to recording.
* way as the SIP plugin does when it comes to recording.
* Specifically, you make use of the \c recording request to either start
* or stop a recording, using the following syntax:
*
Expand All @@ -147,6 +147,28 @@
* that will be used for the up-to-four recordings that may need to be enabled.
*
* A \c recordingupdated event is sent back in case the request is successful.
*
* To programmatically send a video keyframe request to either the WebRTC user
* or the SIP peer (or both), the \c keyframe request can be used. This
* request is particularly useful when the SIP peer doesn't support RTCP PLI,
* and so may use other mechanisms (e.g., via signalling) to ask for a keyframe
* to get video working. By using this request, the WebRTC user can ask Janus
* to originate a PLI programmatically. The direction of the keyframe request
* can be provided by using the \c user and \c peer properties: if \c user
* is \c TRUE a keyframe request will be sent by Janus to the WebRTC user;
* if \c peer is \c TRUE a keyframe request will be sent by Janus to the
* SIP peer. In both cases an RTCP PLI message will be sent. The syntax of
* the message is the following:
*
\verbatim
{
"request" : "keyframe",
"user" : <true|false; whether or not to send a keyframe request to the WebRTC user>,
"peer" : <true|false; whether or not to send a keyframe request to the SIP peer>
}
\endverbatim
*
* A \c keyframesent event is sent back in case the request is successful.
*/

#include "plugin.h"
Expand Down Expand Up @@ -256,6 +278,10 @@ static struct janus_json_parameter recording_parameters[] = {
{"peer_video", JANUS_JSON_BOOL, 0},
{"filename", JSON_STRING, 0}
};
static struct janus_json_parameter keyframe_parameters[] = {
{"user", JANUS_JSON_BOOL, 0},
{"peer", JANUS_JSON_BOOL, 0}
};

/* Useful stuff */
static volatile gint initialized = 0, stopping = 0;
Expand Down Expand Up @@ -318,6 +344,7 @@ typedef struct janus_nosip_media {
srtp_policy_t video_remote_policy, video_local_policy;
char *video_srtp_local_profile, *video_srtp_local_crypto;
gboolean video_send;
gboolean video_pli_supported;
janus_rtp_switching_context context;
int pipefd[2];
gboolean updated;
Expand Down Expand Up @@ -345,8 +372,8 @@ static GHashTable *sessions;
static janus_mutex sessions_mutex = JANUS_MUTEX_INITIALIZER;

static void janus_nosip_srtp_cleanup(janus_nosip_session *session);

static void janus_nosip_media_reset(janus_nosip_session *session);
static void janus_nosip_rtcp_pli_send(janus_nosip_session *session);

static void janus_nosip_session_destroy(janus_nosip_session *session) {
if(session && g_atomic_int_compare_and_exchange(&session->destroyed, 0, 1))
Expand Down Expand Up @@ -631,6 +658,7 @@ void janus_nosip_media_reset(janus_nosip_session *session) {
session->media.video_pt = -1;
session->media.video_pt_name = NULL; /* Immutable string, no need to free*/
session->media.video_send = TRUE;
session->media.video_pli_supported = FALSE;
session->media.video_orientation_extension_id = -1;
session->media.audio_level_extension_id = -1;
janus_rtp_switching_context_reset(&session->media.context);
Expand Down Expand Up @@ -964,6 +992,7 @@ void janus_nosip_create_session(janus_plugin_session *handle, int *error) {
session->media.video_pt = -1;
session->media.video_pt_name = NULL;
session->media.video_send = TRUE;
session->media.video_pli_supported = FALSE;
session->media.video_orientation_extension_id = -1;
session->media.audio_level_extension_id = -1;
/* Initialize the RTP context */
Expand Down Expand Up @@ -1755,6 +1784,28 @@ static void *janus_nosip_handler(void *data) {
/* Notify the result */
result = json_object();
json_object_set_new(result, "event", json_string("recordingupdated"));
} else if(!strcasecmp(request_text, "keyframe")) {
/* Programmatically send a keyframe request via RTCP PLI to
* either the WebRTC user, the SIP peer, or both of them */
JANUS_VALIDATE_JSON_OBJECT(root, keyframe_parameters,
error_code, error_cause, TRUE,
JANUS_NOSIP_ERROR_MISSING_ELEMENT, JANUS_NOSIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
gboolean user = json_is_true(json_object_get(root, "user"));
gboolean peer = json_is_true(json_object_get(root, "peer"));
if(user) {
/* Send a PLI to the WebRTC user */
gateway->send_pli(session->handle);
}
if(peer) {
/* Send a PLI to the SIP peer (but only if they negotiated it) */
if(session->media.video_pli_supported)
janus_nosip_rtcp_pli_send(session);
}
/* Notify the result */
result = json_object();
json_object_set_new(result, "event", json_string("keyframesent"));
} else {
JANUS_LOG(LOG_ERR, "Unknown request (%s)\n", request_text);
error_code = JANUS_NOSIP_ERROR_INVALID_REQUEST;
Expand Down Expand Up @@ -1913,6 +1964,9 @@ void janus_nosip_sdp_process(janus_nosip_session *session, janus_sdp *sdp, gbool
session->media.has_srtp_remote = TRUE;
}
}
} else if(m->type == JANUS_SDP_VIDEO && !strcasecmp(a->name, "rtcp-fb") && a->value) {
if(strstr(a->value, " pli"))
session->media.video_pli_supported = TRUE;
}
}
tempA = tempA->next;
Expand Down Expand Up @@ -2642,3 +2696,43 @@ static void *janus_nosip_relay_thread(void *data) {
return NULL;
}

/* Helper method to send an RTCP PLI to the peer */
static void janus_nosip_rtcp_pli_send(janus_nosip_session *session) {
if(!session || g_atomic_int_get(&session->destroyed)) {
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
return;
}
if(!session->media.has_video || session->media.video_rtcp_fd == -1)
return;
/* Generate a PLI */
char rtcp_buf[12];
int rtcp_len = 12;
janus_rtcp_pli((char *)&rtcp_buf, rtcp_len);
/* Fix SSRCs as the Janus core does */
JANUS_LOG(LOG_HUGE, "[NoSIP-%p] Fixing SSRCs (local %u, peer %u)\n",
session, session->media.video_ssrc, session->media.video_ssrc_peer);
janus_rtcp_fix_ssrc(NULL, (char *)rtcp_buf, rtcp_len, 1, session->media.video_ssrc, session->media.video_ssrc_peer);
/* Is SRTP involved? */
if(session->media.has_srtp_local) {
char sbuf[50];
memcpy(&sbuf, rtcp_buf, rtcp_len);
int protected = rtcp_len;
int res = srtp_protect_rtcp(session->media.video_srtp_out, &sbuf, &protected);
if(res != srtp_err_status_ok) {
JANUS_LOG(LOG_ERR, "[NoSIP-%p] Video SRTCP protect error... %s (len=%d-->%d)...\n",
session, janus_srtp_error_str(res), rtcp_len, protected);
} else {
/* Forward the message to the peer */
if(send(session->media.video_rtcp_fd, sbuf, protected, 0) < 0) {
JANUS_LOG(LOG_HUGE, "[NoSIP-%p] Error sending SRTCP video packet... %s (len=%d)...\n",
session, g_strerror(errno), protected);
}
}
} else {
/* Forward the message to the peer */
if(send(session->media.video_rtcp_fd, rtcp_buf, rtcp_len, 0) < 0) {
JANUS_LOG(LOG_HUGE, "[NoSIP-%p] Error sending RTCP video packet... %s (len=%d)...\n",
session, g_strerror(errno), rtcp_len);
}
}
}
68 changes: 65 additions & 3 deletions plugins/janus_sip.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
*
* The supported requests are \c register , \c unregister , \c call ,
* \progress , \c accept , \c decline , \c info , \c message , \c dtmf_info ,
* \c subscribe , \c unsubscribe , \c transfer , \c recording ,
* \c subscribe , \c unsubscribe , \c transfer , \c recording , \c keyframe ,
* \c hold , \c unhold , \c update and \c hangup . \c register can be used,
* as the name suggests, to register a username at a SIP registrar to
* call and be called, while \c unregister unregisters it; \c call is used
Expand Down Expand Up @@ -566,6 +566,28 @@
*
* A \c recordingupdated event is sent back in case the request is successful.
*
* To programmatically send a video keyframe request to either the WebRTC user
* or the SIP peer (or both), the \c keyframe request can be used. This
* request is particularly useful when the SIP peer doesn't support RTCP PLI,
* and so may use other mechanisms (e.g., via signalling) to ask for a keyframe
* to get video working. By using this request, the WebRTC user can ask Janus
* to originate a PLI programmatically. The direction of the keyframe request
* can be provided by using the \c user and \c peer properties: if \c user
* is \c TRUE a keyframe request will be sent by Janus to the WebRTC user;
* if \c peer is \c TRUE a keyframe request will be sent by Janus to the
* SIP peer. In both cases an RTCP PLI message will be sent. The syntax of
* the message is the following:
*
\verbatim
{
"request" : "keyframe",
"user" : <true|false; whether or not to send a keyframe request to the WebRTC user>,
"peer" : <true|false; whether or not to send a keyframe request to the SIP peer>
}
\endverbatim
*
* A \c keyframesent event is sent back in case the request is successful.
*
* \section sipmc Simultaneous SIP calls using the same account
*
* As anticipated in the previous sections, attaching to the SIP plugin
Expand Down Expand Up @@ -873,6 +895,10 @@ static struct janus_json_parameter sipmessage_parameters[] = {
{"headers", JSON_OBJECT, 0},
{"call_id", JANUS_JSON_STRING, 0}
};
static struct janus_json_parameter keyframe_parameters[] = {
{"user", JANUS_JSON_BOOL, 0},
{"peer", JANUS_JSON_BOOL, 0}
};

/* Useful stuff */
static volatile gint initialized = 0, stopping = 0;
Expand Down Expand Up @@ -5088,6 +5114,42 @@ static void *janus_sip_handler(void *data) {
/* Notify the result */
result = json_object();
json_object_set_new(result, "event", json_string("dtmfsent"));
} else if(!strcasecmp(request_text, "keyframe")) {
/* Programmatically send a keyframe request via RTCP PLI to
* either the WebRTC user, the SIP peer, or both of them */
if(!janus_sip_call_is_established(session)) {
JANUS_LOG(LOG_ERR, "Wrong state (not established? status=%s)\n", janus_sip_call_status_string(session->status));
g_snprintf(error_cause, 512, "Wrong state (not in a call?)");
goto error;
}
janus_mutex_lock(&session->mutex);
if(session->callee == NULL) {
janus_mutex_unlock(&session->mutex);
JANUS_LOG(LOG_ERR, "Wrong state (no callee?)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (no callee?)");
goto error;
}
janus_mutex_unlock(&session->mutex);
JANUS_VALIDATE_JSON_OBJECT(root, keyframe_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
gboolean user = json_is_true(json_object_get(root, "user"));
gboolean peer = json_is_true(json_object_get(root, "peer"));
if(user) {
/* Send a PLI to the WebRTC user */
gateway->send_pli(session->handle);
}
if(peer) {
/* Send a PLI to the SIP peer (but only if they negotiated it) */
if(session->media.video_pli_supported)
janus_sip_rtcp_pli_send(session);
}
/* Notify the result */
result = json_object();
json_object_set_new(result, "event", json_string("keyframesent"));
} else if(!strcasecmp(request_text, "reset")) {
/* Apparently, under some particular circumstances that we haven't
* managed to replicate ourselves yet, it can sometimes happen that
Expand Down Expand Up @@ -7677,8 +7739,8 @@ static void janus_sip_rtcp_pli_send(janus_sip_session *session) {
int rtcp_len = 12;
janus_rtcp_pli((char *)&rtcp_buf, rtcp_len);
/* Fix SSRCs as the Janus core does */
JANUS_LOG(LOG_HUGE, "[SIP] Fixing SSRCs (local %u, peer %u)\n",
session->media.video_ssrc, session->media.video_ssrc_peer);
JANUS_LOG(LOG_HUGE, "[SIP-%s] Fixing SSRCs (local %u, peer %u)\n",
session->account.username, session->media.video_ssrc, session->media.video_ssrc_peer);
janus_rtcp_fix_ssrc(NULL, (char *)rtcp_buf, rtcp_len, 1, session->media.video_ssrc, session->media.video_ssrc_peer);
/* Is SRTP involved? */
if(session->media.has_srtp_local_video) {
Expand Down