From c45739bbb1983242d812309f5d9f9641a355a09e Mon Sep 17 00:00:00 2001 From: Lorenzo Miniero Date: Thu, 4 Jun 2020 10:41:52 +0200 Subject: [PATCH 01/82] Aligned to latest changes in master --- dtls-bio.c | 21 +- dtls.c | 138 +-- dtls.h | 8 +- ice.c | 2055 ++++++++++++++-------------------- ice.h | 292 +++-- janus.c | 804 +++++++------ plugins/janus_audiobridge.c | 45 +- plugins/janus_duktape.c | 20 +- plugins/janus_duktape_data.h | 1 + plugins/janus_echotest.c | 67 +- plugins/janus_lua.c | 20 +- plugins/janus_lua_data.h | 2 +- plugins/janus_nosip.c | 14 +- plugins/janus_recordplay.c | 107 +- plugins/janus_sip.c | 41 +- plugins/janus_streaming.c | 52 +- plugins/janus_textroom.c | 4 +- plugins/janus_videocall.c | 7 +- plugins/janus_videoroom.c | 166 +-- plugins/plugin.c | 5 +- plugins/plugin.h | 23 +- rtp.c | 334 +++--- rtp.h | 18 +- sdp-utils.c | 1054 ++++++++++------- sdp-utils.h | 209 ++-- sdp.c | 972 ++++++++-------- sdp.h | 28 +- 27 files changed, 3209 insertions(+), 3298 deletions(-) diff --git a/dtls-bio.c b/dtls-bio.c index 6e118eeb93..f9697df692 100644 --- a/dtls-bio.c +++ b/dtls-bio.c @@ -129,17 +129,12 @@ static int janus_dtls_bio_agent_write(BIO *bio, const char *in, int inl) { JANUS_LOG(LOG_ERR, "No DTLS-SRTP stack, no DTLS bridge...\n"); return -1; } - janus_ice_component *component = (janus_ice_component *)dtls->component; - if(component == NULL) { - JANUS_LOG(LOG_ERR, "No component, no DTLS bridge...\n"); + janus_ice_peerconnection *pc = (janus_ice_peerconnection *)dtls->pc; + if(pc == NULL) { + JANUS_LOG(LOG_ERR, "No WebRTC PeerConnection, no DTLS bridge...\n"); return -1; } - janus_ice_stream *stream = component->stream; - if(!stream) { - JANUS_LOG(LOG_ERR, "No stream, no DTLS bridge...\n"); - return -1; - } - janus_ice_handle *handle = stream->handle; + janus_ice_handle *handle = pc->handle; if(!handle || !handle->agent || !dtls->write_bio) { JANUS_LOG(LOG_ERR, "No handle/agent/bio, no DTLS bridge...\n"); return -1; @@ -149,17 +144,17 @@ static int janus_dtls_bio_agent_write(BIO *bio, const char *in, int inl) { /* FIXME Just a warning for now, this will need to be solved with proper fragmentation */ JANUS_LOG(LOG_WARN, "[%"SCNu64"] The DTLS stack is trying to send a packet of %d bytes, this may be larger than the MTU and get dropped!\n", handle->handle_id, inl); } - int bytes = nice_agent_send(handle->agent, component->stream_id, component->component_id, inl, in); + int bytes = nice_agent_send(handle->agent, pc->stream_id, pc->component_id, inl, in); if(bytes < inl) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error sending DTLS message on component %d of stream %d (%d)\n", handle->handle_id, component->component_id, stream->stream_id, bytes); + JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error sending DTLS message on component %d of stream %d (%d)\n", handle->handle_id, pc->component_id, pc->stream_id, bytes); } else { JANUS_LOG(LOG_HUGE, "[%"SCNu64"] >> >> ... and sent %d of those bytes on the socket\n", handle->handle_id, bytes); } /* Update stats (TODO Do the same for the last second window as well) * FIXME: the Data stats includes the bytes used for the handshake */ if(bytes > 0) { - component->out_stats.data.packets++; - component->out_stats.data.bytes += bytes; + pc->dtls_out_stats.info[0].packets++; + pc->dtls_out_stats.info[0].bytes += bytes; } return bytes; } diff --git a/dtls.c b/dtls.c index b61b0b49dd..f84c1ddb77 100644 --- a/dtls.c +++ b/dtls.c @@ -79,13 +79,10 @@ static void janus_dtls_notify_state_change(janus_dtls_srtp *dtls) { return; if(dtls == NULL) return; - janus_ice_component *component = (janus_ice_component *)dtls->component; - if(component == NULL) + janus_ice_peerconnection *pc = (janus_ice_peerconnection *)dtls->pc; + if(pc == NULL) return; - janus_ice_stream *stream = component->stream; - if(stream == NULL) - return; - janus_ice_handle *handle = stream->handle; + janus_ice_handle *handle = pc->handle; if(handle == NULL) return; janus_session *session = (janus_session *)handle->session; @@ -93,8 +90,8 @@ static void janus_dtls_notify_state_change(janus_dtls_srtp *dtls) { return; json_t *info = json_object(); json_object_set_new(info, "dtls", json_string(janus_get_dtls_srtp_state(dtls->dtls_state))); - json_object_set_new(info, "stream_id", json_integer(stream->stream_id)); - json_object_set_new(info, "component_id", json_integer(component->component_id)); + json_object_set_new(info, "stream_id", json_integer(pc->stream_id)); + json_object_set_new(info, "component_id", json_integer(pc->component_id)); json_object_set_new(info, "retransmissions", json_integer(dtls->retransmissions)); janus_events_notify_handlers(JANUS_EVENT_TYPE_WEBRTC, JANUS_EVENT_SUBTYPE_WEBRTC_DTLS, session->session_id, handle->handle_id, handle->opaque_id, info); @@ -498,7 +495,7 @@ gint janus_dtls_srtp_init(const char *server_pem, const char *server_key, const static void janus_dtls_srtp_free(const janus_refcount *dtls_ref) { janus_dtls_srtp *dtls = janus_refcount_containerof(dtls_ref, janus_dtls_srtp, ref); /* This stack can be destroyed, free all the resources */ - dtls->component = NULL; + dtls->pc = NULL; if(dtls->ssl != NULL) { SSL_free(dtls->ssl); dtls->ssl = NULL; @@ -540,18 +537,13 @@ void janus_dtls_srtp_cleanup(void) { } -janus_dtls_srtp *janus_dtls_srtp_create(void *ice_component, janus_dtls_role role) { - janus_ice_component *component = (janus_ice_component *)ice_component; - if(component == NULL) { - JANUS_LOG(LOG_ERR, "No component, no DTLS...\n"); - return NULL; - } - janus_ice_stream *stream = component->stream; - if(!stream) { - JANUS_LOG(LOG_ERR, "No stream, no DTLS...\n"); +janus_dtls_srtp *janus_dtls_srtp_create(void *ice_pc, janus_dtls_role role) { + janus_ice_peerconnection *pc = (janus_ice_peerconnection *)ice_pc; + if(pc == NULL) { + JANUS_LOG(LOG_ERR, "No WebRTC PeerConnection, no DTLS...\n"); return NULL; } - janus_ice_handle *handle = stream->handle; + janus_ice_handle *handle = pc->handle; if(!handle || !handle->agent) { JANUS_LOG(LOG_ERR, "No handle/agent, no DTLS...\n"); return NULL; @@ -615,7 +607,7 @@ janus_dtls_srtp *janus_dtls_srtp_create(void *ice_component, janus_dtls_role rol #endif /* Done */ dtls->dtls_connected = 0; - dtls->component = component; + dtls->pc = pc; return dtls; } @@ -642,13 +634,10 @@ int janus_dtls_srtp_create_sctp(janus_dtls_srtp *dtls) { #ifdef HAVE_SCTP if(dtls == NULL) return -1; - janus_ice_component *component = (janus_ice_component *)dtls->component; - if(component == NULL) + janus_ice_peerconnection *pc = (janus_ice_peerconnection *)dtls->pc; + if(pc == NULL) return -2; - janus_ice_stream *stream = component->stream; - if(!stream) - return -3; - janus_ice_handle *handle = stream->handle; + janus_ice_handle *handle = pc->handle; if(!handle || !handle->agent) return -4; if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT)) @@ -670,17 +659,12 @@ void janus_dtls_srtp_incoming_msg(janus_dtls_srtp *dtls, char *buf, uint16_t len JANUS_LOG(LOG_ERR, "No DTLS-SRTP stack, no incoming message...\n"); return; } - janus_ice_component *component = (janus_ice_component *)dtls->component; - if(component == NULL) { - JANUS_LOG(LOG_ERR, "No component, no DTLS...\n"); + janus_ice_peerconnection *pc = (janus_ice_peerconnection *)dtls->pc; + if(pc == NULL) { + JANUS_LOG(LOG_ERR, "No WebRTC PeerConnection, no DTLS...\n"); return; } - janus_ice_stream *stream = component->stream; - if(!stream) { - JANUS_LOG(LOG_ERR, "No stream, no DTLS...\n"); - return; - } - janus_ice_handle *handle = stream->handle; + janus_ice_handle *handle = pc->handle; if(!handle || !handle->agent) { JANUS_LOG(LOG_ERR, "No handle/agent, no DTLS...\n"); return; @@ -690,7 +674,7 @@ void janus_dtls_srtp_incoming_msg(janus_dtls_srtp *dtls, char *buf, uint16_t len return; } if(!dtls->ssl || !dtls->read_bio) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] No DTLS stuff for component %d in stream %d??\n", handle->handle_id, component->component_id, stream->stream_id); + JANUS_LOG(LOG_ERR, "[%"SCNu64"] No DTLS stuff for component %d in stream %d??\n", handle->handle_id, pc->component_id, pc->stream_id); return; } if(dtls->dtls_started == 0) { @@ -753,7 +737,7 @@ void janus_dtls_srtp_incoming_msg(janus_dtls_srtp *dtls, char *buf, uint16_t len unsigned char rfingerprint[EVP_MAX_MD_SIZE]; char remote_fingerprint[160]; char *rfp = (char *)&remote_fingerprint; - if(stream->remote_hashing && !strcasecmp(stream->remote_hashing, "sha-1")) { + if(pc->remote_hashing && !strcasecmp(pc->remote_hashing, "sha-1")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Computing sha-1 fingerprint of remote certificate...\n", handle->handle_id); X509_digest(rcert, EVP_sha1(), (unsigned char *)rfingerprint, &rsize); } else { @@ -769,8 +753,8 @@ void janus_dtls_srtp_incoming_msg(janus_dtls_srtp *dtls, char *buf, uint16_t len } *(rfp-1) = 0; JANUS_LOG(LOG_VERB, "[%"SCNu64"] Remote fingerprint (%s) of the client is %s\n", - handle->handle_id, stream->remote_hashing ? stream->remote_hashing : "sha-256", remote_fingerprint); - if(!strcasecmp(remote_fingerprint, stream->remote_fingerprint ? stream->remote_fingerprint : "(none)")) { + handle->handle_id, pc->remote_hashing ? pc->remote_hashing : "sha-256", remote_fingerprint); + if(!strcasecmp(remote_fingerprint, pc->remote_fingerprint ? pc->remote_fingerprint : "(none)")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Fingerprint is a match!\n", handle->handle_id); dtls->dtls_state = JANUS_DTLS_STATE_CONNECTED; dtls->dtls_connected = janus_get_monotonic_time(); @@ -778,7 +762,7 @@ void janus_dtls_srtp_incoming_msg(janus_dtls_srtp *dtls, char *buf, uint16_t len janus_dtls_notify_state_change(dtls); } else { /* FIXME NOT a match! MITM? */ - JANUS_LOG(LOG_ERR, "[%"SCNu64"] Fingerprint is NOT a match! got %s, expected %s\n", handle->handle_id, remote_fingerprint, stream->remote_fingerprint); + JANUS_LOG(LOG_ERR, "[%"SCNu64"] Fingerprint is NOT a match! got %s, expected %s\n", handle->handle_id, remote_fingerprint, pc->remote_fingerprint); dtls->dtls_state = JANUS_DTLS_STATE_FAILED; /* Notify event handlers */ janus_dtls_notify_state_change(dtls); @@ -830,7 +814,7 @@ void janus_dtls_srtp_incoming_msg(janus_dtls_srtp *dtls, char *buf, uint16_t len if(!SSL_export_keying_material(dtls->ssl, material, master_length*2, "EXTRACTOR-dtls_srtp", 19, NULL, 0, 0)) { /* Oops... */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Oops, couldn't extract SRTP keying material for component %d in stream %d?? (%s)\n", - handle->handle_id, component->component_id, stream->stream_id, ERR_reason_error_string(ERR_get_error())); + handle->handle_id, pc->component_id, pc->stream_id, ERR_reason_error_string(ERR_get_error())); goto done; } /* Key derivation (http://tools.ietf.org/html/rfc5764#section-4.2) */ @@ -920,21 +904,21 @@ void janus_dtls_srtp_incoming_msg(janus_dtls_srtp *dtls, char *buf, uint16_t len srtp_err_status_t res = srtp_create(&(dtls->srtp_in), &(dtls->remote_policy)); if(res != srtp_err_status_ok) { /* Something went wrong... */ - JANUS_LOG(LOG_ERR, "[%"SCNu64"] Oops, error creating inbound SRTP session for component %d in stream %d??\n", handle->handle_id, component->component_id, stream->stream_id); + JANUS_LOG(LOG_ERR, "[%"SCNu64"] Oops, error creating inbound SRTP session for component %d in stream %d??\n", handle->handle_id, pc->component_id, pc->stream_id); JANUS_LOG(LOG_ERR, "[%"SCNu64"] -- %d (%s)\n", handle->handle_id, res, janus_srtp_error_str(res)); goto done; } - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Created inbound SRTP session for component %d in stream %d\n", handle->handle_id, component->component_id, stream->stream_id); + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Created inbound SRTP session for component %d in stream %d\n", handle->handle_id, pc->component_id, pc->stream_id); res = srtp_create(&(dtls->srtp_out), &(dtls->local_policy)); if(res != srtp_err_status_ok) { /* Something went wrong... */ - JANUS_LOG(LOG_ERR, "[%"SCNu64"] Oops, error creating outbound SRTP session for component %d in stream %d??\n", handle->handle_id, component->component_id, stream->stream_id); + JANUS_LOG(LOG_ERR, "[%"SCNu64"] Oops, error creating outbound SRTP session for component %d in stream %d??\n", handle->handle_id, pc->component_id, pc->stream_id); JANUS_LOG(LOG_ERR, "[%"SCNu64"] -- %d (%s)\n", handle->handle_id, res, janus_srtp_error_str(res)); goto done; } dtls->srtp_profile = srtp_profile->id; dtls->srtp_valid = 1; - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Created outbound SRTP session for component %d in stream %d\n", handle->handle_id, component->component_id, stream->stream_id); + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Created outbound SRTP session for component %d in stream %d\n", handle->handle_id, pc->component_id, pc->stream_id); #ifdef HAVE_SCTP if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_DATA_CHANNELS)) { /* Create SCTP association as well */ @@ -946,7 +930,7 @@ void janus_dtls_srtp_incoming_msg(janus_dtls_srtp *dtls, char *buf, uint16_t len done: if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT) && dtls->srtp_valid) { /* Handshake successfully completed */ - janus_ice_dtls_handshake_done(handle, component); + janus_ice_dtls_handshake_done(handle); } else { /* Something went wrong in either DTLS or SRTP... tell the plugin about it */ janus_dtls_callback(dtls->ssl, SSL_CB_ALERT, 0); @@ -993,22 +977,17 @@ void janus_dtls_callback(const SSL *ssl, int where, int ret) { JANUS_LOG(LOG_ERR, "No DTLS session related to this alert...\n"); return; } - janus_ice_component *component = dtls->component; - if(component == NULL) { - JANUS_LOG(LOG_ERR, "No ICE component related to this alert...\n"); + janus_ice_peerconnection *pc = dtls->pc; + if(pc == NULL) { + JANUS_LOG(LOG_ERR, "No WebRTC PeerConnection related to this alert...\n"); return; } - janus_ice_stream *stream = component->stream; - if(!stream) { - JANUS_LOG(LOG_ERR, "No ICE stream related to this alert...\n"); - return; - } - janus_ice_handle *handle = stream->handle; + janus_ice_handle *handle = pc->handle; if(!handle) { JANUS_LOG(LOG_ERR, "No ICE handle related to this alert...\n"); return; } - JANUS_LOG(LOG_VERB, "[%"SCNu64"] DTLS alert triggered on stream %u (component %u), closing...\n", handle->handle_id, stream->stream_id, component->component_id); + JANUS_LOG(LOG_VERB, "[%"SCNu64"] DTLS alert triggered on stream %u (component %u), closing...\n", handle->handle_id, pc->stream_id, pc->component_id); janus_ice_webrtc_hangup(handle, "DTLS alert"); } @@ -1034,17 +1013,12 @@ int janus_dtls_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { void janus_dtls_sctp_data_ready(janus_dtls_srtp *dtls) { if(dtls == NULL) return; - janus_ice_component *component = (janus_ice_component *)dtls->component; - if(component == NULL) { - JANUS_LOG(LOG_ERR, "No component...\n"); - return; - } - janus_ice_stream *stream = component->stream; - if(!stream) { - JANUS_LOG(LOG_ERR, "No stream...\n"); + janus_ice_peerconnection *pc = (janus_ice_peerconnection *)dtls->pc; + if(pc == NULL) { + JANUS_LOG(LOG_ERR, "No WebRTC PeerConnection...\n"); return; } - janus_ice_handle *handle = stream->handle; + janus_ice_handle *handle = pc->handle; if(!handle || !handle->agent || !dtls->write_bio) { JANUS_LOG(LOG_ERR, "No handle...\n"); return; @@ -1074,17 +1048,12 @@ int janus_dtls_send_sctp_data(janus_dtls_srtp *dtls, char *buf, int len) { void janus_dtls_notify_sctp_data(janus_dtls_srtp *dtls, char *label, char *protocol, gboolean textdata, char *buf, int len) { if(dtls == NULL || buf == NULL || len < 1) return; - janus_ice_component *component = (janus_ice_component *)dtls->component; - if(component == NULL) { - JANUS_LOG(LOG_ERR, "No component...\n"); + janus_ice_peerconnection *pc = (janus_ice_peerconnection *)dtls->pc; + if(pc == NULL) { + JANUS_LOG(LOG_ERR, "No WebRTC PeerConnection...\n"); return; } - janus_ice_stream *stream = component->stream; - if(!stream) { - JANUS_LOG(LOG_ERR, "No stream...\n"); - return; - } - janus_ice_handle *handle = stream->handle; + janus_ice_handle *handle = pc->handle; if(!handle || !handle->agent || !dtls->write_bio) { JANUS_LOG(LOG_ERR, "No handle...\n"); return; @@ -1097,13 +1066,10 @@ gboolean janus_dtls_retry(gpointer stack) { janus_dtls_srtp *dtls = (janus_dtls_srtp *)stack; if(dtls == NULL) return FALSE; - janus_ice_component *component = (janus_ice_component *)dtls->component; - if(component == NULL) + janus_ice_peerconnection *pc = (janus_ice_peerconnection *)dtls->pc; + if(pc == NULL) return FALSE; - janus_ice_stream *stream = component->stream; - if(!stream) - goto stoptimer; - janus_ice_handle *handle = stream->handle; + janus_ice_handle *handle = pc->handle; if(!handle) goto stoptimer; if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_STOP)) @@ -1115,7 +1081,7 @@ gboolean janus_dtls_retry(gpointer stack) { if(janus_get_monotonic_time() - dtls->dtls_started >= 20*G_USEC_PER_SEC) { /* FIXME Should we really give up after 20 seconds waiting for DTLS? */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] DTLS taking too much time for component %d in stream %d...\n", - handle->handle_id, component->component_id, stream->stream_id); + handle->handle_id, pc->component_id, pc->stream_id); janus_ice_webrtc_hangup(handle, "DTLS timeout"); goto stoptimer; } @@ -1128,7 +1094,7 @@ gboolean janus_dtls_retry(gpointer stack) { JANUS_LOG(LOG_HUGE, "[%"SCNu64"] DTLSv1_get_timeout: %"SCNu64"\n", handle->handle_id, timeout_value); if(timeout_value == 0) { dtls->retransmissions++; - JANUS_LOG(LOG_VERB, "[%"SCNu64"] DTLS timeout on component %d of stream %d, retransmitting\n", handle->handle_id, component->component_id, stream->stream_id); + JANUS_LOG(LOG_VERB, "[%"SCNu64"] DTLS timeout on component %d of stream %d, retransmitting\n", handle->handle_id, pc->component_id, pc->stream_id); /* Notify event handlers */ janus_dtls_notify_state_change(dtls); /* Retransmit the packet */ @@ -1137,10 +1103,10 @@ gboolean janus_dtls_retry(gpointer stack) { return TRUE; stoptimer: - if(component->dtlsrt_source != NULL) { - g_source_destroy(component->dtlsrt_source); - g_source_unref(component->dtlsrt_source); - component->dtlsrt_source = NULL; + if(pc->dtlsrt_source != NULL) { + g_source_destroy(pc->dtlsrt_source); + g_source_unref(pc->dtlsrt_source); + pc->dtlsrt_source = NULL; } return FALSE; } diff --git a/dtls.h b/dtls.h index 152c0e2024..79b12e779e 100644 --- a/dtls.h +++ b/dtls.h @@ -64,8 +64,8 @@ typedef enum janus_dtls_state { /*! \brief Janus DTLS-SRTP handle */ typedef struct janus_dtls_srtp { - /*! \brief Opaque pointer to the component this DTLS-SRTP context belongs to */ - void *component; + /*! \brief Opaque pointer to the WebRTC PeerConnection this DTLS-SRTP context belongs to */ + void *pc; /*! \brief DTLS role of the server for this stream: 1=client, 0=server */ janus_dtls_role dtls_role; /*! \brief DTLS state of this component: -1=failed, 0=nothing, 1=trying, 2=connected */ @@ -108,10 +108,10 @@ typedef struct janus_dtls_srtp { /*! \brief Create a janus_dtls_srtp instance - * @param[in] component Opaque pointer to the component owning that will use the stack + * @param[in] pc Opaque pointer to the WebRTC PeerConnection owning the stack * @param[in] role The role of the DTLS stack (client/server) * @returns A new janus_dtls_srtp instance if successful, NULL otherwise */ -janus_dtls_srtp *janus_dtls_srtp_create(void *component, janus_dtls_role role); +janus_dtls_srtp *janus_dtls_srtp_create(void *pc, janus_dtls_role role); /*! \brief Start a DTLS handshake * @param[in] dtls The janus_dtls_srtp instance to start the handshake on */ void janus_dtls_srtp_handshake(janus_dtls_srtp *dtls); diff --git a/ice.c b/ice.c index fba220daeb..40c157d069 100644 --- a/ice.c +++ b/ice.c @@ -1,9 +1,13 @@ /*! \file ice.c * \author Lorenzo Miniero * \copyright GNU General Public License v3 - * \brief ICE/STUN/TURN processing - * \details Implementation (based on libnice) of the ICE process. The - * code handles the whole ICE process, from the gathering of candidates + * \brief Janus handles and ICE/STUN/TURN processing + * \details A Janus handle represents an abstraction of the communication + * between a user and a specific plugin, within a Janus session. This is + * particularly important in terms of media connectivity, as each handle + * can be associated with a single WebRTC PeerConnection. This code also + * contains the implementation (based on libnice) of a WebRTC PeerConnection. + * The code handles the whole ICE process, from the gathering of candidates * to the final setup of a virtual channel RTP and RTCP can be transported * on. Incoming RTP and RTCP packets from peers are relayed to the associated * plugins by means of the incoming_rtp and incoming_rtcp callbacks. Packets @@ -308,6 +312,7 @@ uint16_t rtp_range_max = 0; #define JANUS_ICE_PACKET_SCTP 4 /* Janus enqueued (S)RTP/(S)RTCP packet to send */ typedef struct janus_ice_queued_packet { + gint mindex; char *data; char *label; char *protocol; @@ -330,7 +335,7 @@ static janus_ice_queued_packet /* Janus NACKed packet we're tracking (to avoid duplicates) */ typedef struct janus_ice_nacked_packet { - janus_ice_handle *handle; + janus_ice_peerconnection_medium *medium; int vindex; guint16 seq_number; guint source_id; @@ -338,11 +343,11 @@ typedef struct janus_ice_nacked_packet { static gboolean janus_ice_nacked_packet_cleanup(gpointer user_data) { janus_ice_nacked_packet *pkt = (janus_ice_nacked_packet *)user_data; - if(pkt->handle->stream){ + if(pkt->medium && pkt->medium->pc && pkt->medium->pc->handle) { JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Cleaning up NACKed packet %"SCNu16" (SSRC %"SCNu32", vindex %d)...\n", - pkt->handle->handle_id, pkt->seq_number, pkt->handle->stream->video_ssrc_peer[pkt->vindex], pkt->vindex); - g_hash_table_remove(pkt->handle->stream->rtx_nacked[pkt->vindex], GUINT_TO_POINTER(pkt->seq_number)); - g_hash_table_remove(pkt->handle->stream->pending_nacked_cleanup, GUINT_TO_POINTER(pkt->source_id)); + pkt->medium->pc->handle->handle_id, pkt->seq_number, pkt->medium->ssrc_peer[pkt->vindex], pkt->vindex); + g_hash_table_remove(pkt->medium->rtx_nacked[pkt->vindex], GUINT_TO_POINTER(pkt->seq_number)); + g_hash_table_remove(pkt->medium->pending_nacked_cleanup, GUINT_TO_POINTER(pkt->source_id)); } return G_SOURCE_REMOVE; @@ -352,8 +357,8 @@ static gboolean janus_ice_nacked_packet_cleanup(gpointer user_data) { static void janus_ice_handle_free(const janus_refcount *handle_ref); static void janus_ice_webrtc_free(janus_ice_handle *handle); static void janus_ice_plugin_session_free(const janus_refcount *app_handle_ref); -static void janus_ice_stream_free(const janus_refcount *handle_ref); -static void janus_ice_component_free(const janus_refcount *handle_ref); +static void janus_ice_peerconnection_free(const janus_refcount *pc_ref); +static void janus_ice_peerconnection_medium_free(const janus_refcount *medium_ref); /* Custom GSource for outgoing traffic */ typedef struct janus_ice_outgoing_traffic { @@ -519,35 +524,28 @@ uint16_t janus_get_min_nack_queue(void) { return min_nack_queue; } /* Helper to clean old NACK packets in the buffer when they exceed the queue time limit */ -static void janus_cleanup_nack_buffer(gint64 now, janus_ice_stream *stream, gboolean audio, gboolean video) { - if(stream && stream->component) { - janus_ice_component *component = stream->component; - if(audio && component->audio_retransmit_buffer) { - janus_rtp_packet *p = (janus_rtp_packet *)g_queue_peek_head(component->audio_retransmit_buffer); - while(p && (!now || (now - p->created >= (gint64)stream->nack_queue_ms*1000))) { - /* Packet is too old, get rid of it */ - g_queue_pop_head(component->audio_retransmit_buffer); - /* Remove from hashtable too */ - janus_rtp_header *header = (janus_rtp_header *)p->data; - guint16 seq = ntohs(header->seq_number); - g_hash_table_remove(component->audio_retransmit_seqs, GUINT_TO_POINTER(seq)); - /* Free the packet */ - janus_ice_free_rtp_packet(p); - p = (janus_rtp_packet *)g_queue_peek_head(component->audio_retransmit_buffer); - } - } - if(video && component->video_retransmit_buffer) { - janus_rtp_packet *p = (janus_rtp_packet *)g_queue_peek_head(component->video_retransmit_buffer); - while(p && (!now || (now - p->created >= (gint64)stream->nack_queue_ms*1000))) { +static void janus_cleanup_nack_buffer(gint64 now, janus_ice_peerconnection *pc, gboolean audio, gboolean video) { + /* Iterate on all media */ + janus_ice_peerconnection_medium *medium = NULL; + uint mi=0; + for(mi=0; mimedia); mi++) { + medium = g_hash_table_lookup(pc->media, GUINT_TO_POINTER(mi)); + if(!medium) + continue; + if((medium->type == JANUS_MEDIA_AUDIO && !audio) || (medium->type == JANUS_MEDIA_VIDEO && !video)) + continue; + if(medium->retransmit_buffer) { + janus_rtp_packet *p = (janus_rtp_packet *)g_queue_peek_head(medium->retransmit_buffer); + while(p && (!now || (now - p->created >= (gint64)medium->nack_queue_ms*1000))) { /* Packet is too old, get rid of it */ - g_queue_pop_head(component->video_retransmit_buffer); + g_queue_pop_head(medium->retransmit_buffer); /* Remove from hashtable too */ janus_rtp_header *header = (janus_rtp_header *)p->data; guint16 seq = ntohs(header->seq_number); - g_hash_table_remove(component->video_retransmit_seqs, GUINT_TO_POINTER(seq)); + g_hash_table_remove(medium->retransmit_seqs, GUINT_TO_POINTER(seq)); /* Free the packet */ janus_ice_free_rtp_packet(p); - p = (janus_rtp_packet *)g_queue_peek_head(component->video_retransmit_buffer); + p = (janus_rtp_packet *)g_queue_peek_head(medium->retransmit_buffer); } } } @@ -670,7 +668,7 @@ static void janus_ice_notify_trickle(janus_ice_handle *handle, char *buffer) { json_object_set_new(event, "opaque_id", json_string(handle->opaque_id)); json_t *candidate = json_object(); if(buffer != NULL) { - json_object_set_new(candidate, "sdpMid", json_string(handle->stream_mid)); + json_object_set_new(candidate, "sdpMid", json_string(handle->pc_mid)); json_object_set_new(candidate, "sdpMLineIndex", json_integer(0)); json_object_set_new(candidate, "candidate", json_string(cbuffer)); } else { @@ -683,12 +681,12 @@ static void janus_ice_notify_trickle(janus_ice_handle *handle, char *buffer) { janus_session_notify_event(session, event); } -static void janus_ice_notify_media(janus_ice_handle *handle, gboolean video, gboolean up) { +static void janus_ice_notify_media(janus_ice_handle *handle, char *mid, gboolean video, gboolean up) { if(handle == NULL) return; /* Prepare JSON event to notify user/application */ - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Notifying that we %s receiving %s\n", - handle->handle_id, up ? "are" : "are NOT", video ? "video" : "audio"); + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Notifying that we %s receiving %s on mid %s\n", + handle->handle_id, up ? "are" : "are NOT", video ? "video" : "audio", mid); janus_session *session = (janus_session *)handle->session; if(session == NULL) return; @@ -698,6 +696,7 @@ static void janus_ice_notify_media(janus_ice_handle *handle, gboolean video, gbo json_object_set_new(event, "sender", json_integer(handle->handle_id)); if(opaqueid_in_api && handle->opaque_id != NULL) json_object_set_new(event, "opaque_id", json_string(handle->opaque_id)); + json_object_set_new(event, "mid", json_string(mid)); json_object_set_new(event, "type", json_string(video ? "video" : "audio")); json_object_set_new(event, "receiving", up ? json_true() : json_false()); if(!up && no_media_timer > 1) @@ -709,6 +708,7 @@ static void janus_ice_notify_media(janus_ice_handle *handle, gboolean video, gbo if(janus_events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "media", json_string(video ? "video" : "audio")); + json_object_set_new(info, "mid", json_string(mid)); json_object_set_new(info, "receiving", up ? json_true() : json_false()); if(!up && no_media_timer > 1) json_object_set_new(info, "seconds", json_integer(no_media_timer)); @@ -802,18 +802,18 @@ gint janus_ice_trickle_parse(janus_ice_handle *handle, json_t *candidate, const /* Parse it */ int sdpMLineIndex = mline ? json_integer_value(mline) : -1; const char *sdpMid = json_string_value(mid); - if(sdpMLineIndex > 0 || (handle->stream_mid && sdpMid && strcmp(handle->stream_mid, sdpMid))) { + if(sdpMLineIndex > 0 || (handle->pc_mid && sdpMid && strcmp(handle->pc_mid, sdpMid))) { /* FIXME We bundle everything, so we ignore candidates for anything beyond the first m-line */ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Got a mid='%s' candidate (index %d) but we're bundling, ignoring...\n", handle->handle_id, json_string_value(mid), sdpMLineIndex); return 0; } - janus_ice_stream *stream = handle->stream; - if(stream == NULL) { - *error = "Trickle error: invalid element type (no such stream)"; + janus_ice_peerconnection *pc = handle->pc; + if(pc == NULL) { + *error = "Trickle error: invalid element type (no such PeerConnection)"; return JANUS_ERROR_TRICKE_INVALID_STREAM; } - int res = janus_sdp_parse_candidate(stream, json_string_value(rc), 1); + int res = janus_sdp_parse_candidate(pc, json_string_value(rc), 1); if(res != 0) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse candidate... (%d)\n", handle->handle_id, res); /* FIXME Should we return an error? */ @@ -1431,9 +1431,9 @@ static void janus_ice_webrtc_free(janus_ice_handle *handle) { return; } handle->agent_created = 0; - if(handle->stream != NULL) { - janus_ice_stream_destroy(handle->stream); - handle->stream = NULL; + if(handle->pc != NULL) { + janus_ice_peerconnection_destroy(handle->pc); + handle->pc = NULL; } if(handle->agent != NULL) { if(G_IS_OBJECT(handle->agent)) @@ -1457,13 +1457,8 @@ static void janus_ice_webrtc_free(janus_ice_handle *handle) { handle->local_sdp = NULL; g_free(handle->remote_sdp); handle->remote_sdp = NULL; - handle->stream_mid = NULL; - g_free(handle->audio_mid); - handle->audio_mid = NULL; - g_free(handle->video_mid); - handle->video_mid = NULL; - g_free(handle->data_mid); - handle->data_mid = NULL; + g_free(handle->pc_mid); + handle->pc_mid = NULL; handle->thread = NULL; janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_NEW_DATACHAN_SDP); janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_READY); @@ -1478,156 +1473,58 @@ static void janus_ice_webrtc_free(janus_ice_handle *handle) { JANUS_LOG(LOG_INFO, "[%"SCNu64"] WebRTC resources freed; %p %p\n", handle->handle_id, handle, handle->session); } -void janus_ice_stream_destroy(janus_ice_stream *stream) { - if(stream == NULL) +void janus_ice_peerconnection_destroy(janus_ice_peerconnection *pc) { + if(pc == NULL) return; - if(stream->component != NULL) { - janus_ice_component_destroy(stream->component); - stream->component = NULL; - } - if(stream->pending_nacked_cleanup && g_hash_table_size(stream->pending_nacked_cleanup) > 0) { - GHashTableIter iter; - gpointer val; - g_hash_table_iter_init(&iter, stream->pending_nacked_cleanup); - while(g_hash_table_iter_next(&iter, NULL, &val)) { - GSource *source = val; - g_source_destroy(source); - } - g_hash_table_destroy(stream->pending_nacked_cleanup); - } - stream->pending_nacked_cleanup = NULL; - janus_ice_handle *handle = stream->handle; + /* Remove all media instances */ + g_hash_table_remove_all(pc->media); + g_hash_table_remove_all(pc->media_byssrc); + g_hash_table_remove_all(pc->media_bymid); + g_hash_table_remove_all(pc->media_bytype); + /* Get rid of the DTLS stack */ + if(pc->dtlsrt_source != NULL) { + g_source_destroy(pc->dtlsrt_source); + g_source_unref(pc->dtlsrt_source); + pc->dtlsrt_source = NULL; + } + if(pc->dtls != NULL) { + janus_dtls_srtp_destroy(pc->dtls); + janus_refcount_decrease(&pc->dtls->ref); + pc->dtls = NULL; + } + janus_ice_handle *handle = pc->handle; if(handle != NULL) { janus_refcount_decrease(&handle->ref); - stream->handle = NULL; + pc->handle = NULL; } - janus_refcount_decrease(&stream->ref); + janus_refcount_decrease(&pc->ref); } -static void janus_ice_stream_free(const janus_refcount *stream_ref) { - janus_ice_stream *stream = janus_refcount_containerof(stream_ref, janus_ice_stream, ref); - /* This stream can be destroyed, free all the resources */ - stream->handle = NULL; - g_free(stream->remote_hashing); - stream->remote_hashing = NULL; - g_free(stream->remote_fingerprint); - stream->remote_fingerprint = NULL; - g_free(stream->ruser); - stream->ruser = NULL; - g_free(stream->rpass); - stream->rpass = NULL; - g_free(stream->rid[0]); - stream->rid[0] = NULL; - g_free(stream->rid[1]); - stream->rid[1] = NULL; - g_free(stream->rid[2]); - stream->rid[2] = NULL; - g_list_free(stream->audio_payload_types); - stream->audio_payload_types = NULL; - g_list_free(stream->video_payload_types); - stream->video_payload_types = NULL; - if(stream->rtx_payload_types != NULL) - g_hash_table_destroy(stream->rtx_payload_types); - stream->rtx_payload_types = NULL; - if(stream->clock_rates != NULL) - g_hash_table_destroy(stream->clock_rates); - stream->clock_rates = NULL; - g_free(stream->audio_codec); - stream->audio_codec = NULL; - g_free(stream->video_codec); - stream->video_codec = NULL; - g_free(stream->audio_rtcp_ctx); - stream->audio_rtcp_ctx = NULL; - g_free(stream->video_rtcp_ctx[0]); - stream->video_rtcp_ctx[0] = NULL; - g_free(stream->video_rtcp_ctx[1]); - stream->video_rtcp_ctx[1] = NULL; - g_free(stream->video_rtcp_ctx[2]); - stream->video_rtcp_ctx[2] = NULL; - if(stream->rtx_nacked[0]) - g_hash_table_destroy(stream->rtx_nacked[0]); - stream->rtx_nacked[0] = NULL; - if(stream->rtx_nacked[1]) - g_hash_table_destroy(stream->rtx_nacked[1]); - stream->rtx_nacked[1] = NULL; - if(stream->rtx_nacked[2]) - g_hash_table_destroy(stream->rtx_nacked[2]); - stream->rtx_nacked[2] = NULL; - g_slist_free_full(stream->transport_wide_received_seq_nums, (GDestroyNotify)g_free); - stream->transport_wide_received_seq_nums = NULL; - stream->audio_first_ntp_ts = 0; - stream->audio_first_rtp_ts = 0; - stream->video_first_ntp_ts[0] = 0; - stream->video_first_ntp_ts[1] = 0; - stream->video_first_ntp_ts[2] = 0; - stream->video_first_rtp_ts[0] = 0; - stream->video_first_rtp_ts[1] = 0; - stream->video_first_rtp_ts[2] = 0; - stream->audio_last_rtp_ts = 0; - stream->audio_last_ntp_ts = 0; - stream->video_last_rtp_ts = 0; - stream->video_last_ntp_ts = 0; - g_free(stream); - stream = NULL; -} - -void janus_ice_component_destroy(janus_ice_component *component) { - if(component == NULL) - return; - janus_ice_stream *stream = component->stream; - if(stream != NULL) { - janus_refcount_decrease(&stream->ref); - component->stream = NULL; - } - janus_dtls_srtp_destroy(component->dtls); - janus_refcount_decrease(&component->ref); -} - -static void janus_ice_component_free(const janus_refcount *component_ref) { - janus_ice_component *component = janus_refcount_containerof(component_ref, janus_ice_component, ref); - if(component->icestate_source != NULL) { - g_source_destroy(component->icestate_source); - g_source_unref(component->icestate_source); - component->icestate_source = NULL; - } - if(component->dtlsrt_source != NULL) { - g_source_destroy(component->dtlsrt_source); - g_source_unref(component->dtlsrt_source); - component->dtlsrt_source = NULL; - } - if(component->dtls != NULL) { - janus_dtls_srtp_destroy(component->dtls); - janus_refcount_decrease(&component->dtls->ref); - component->dtls = NULL; - } - if(component->audio_retransmit_buffer != NULL) { - janus_rtp_packet *p = NULL; - while((p = (janus_rtp_packet *)g_queue_pop_head(component->audio_retransmit_buffer)) != NULL) { - /* Remove from hashtable too */ - janus_rtp_header *header = (janus_rtp_header *)p->data; - guint16 seq = ntohs(header->seq_number); - g_hash_table_remove(component->audio_retransmit_seqs, GUINT_TO_POINTER(seq)); - /* Free the packet */ - janus_ice_free_rtp_packet(p); - } - g_queue_free(component->audio_retransmit_buffer); - g_hash_table_destroy(component->audio_retransmit_seqs); - } - if(component->video_retransmit_buffer != NULL) { - janus_rtp_packet *p = NULL; - while((p = (janus_rtp_packet *)g_queue_pop_head(component->video_retransmit_buffer)) != NULL) { - /* Remove from hashtable too */ - janus_rtp_header *header = (janus_rtp_header *)p->data; - guint16 seq = ntohs(header->seq_number); - g_hash_table_remove(component->video_retransmit_seqs, GUINT_TO_POINTER(seq)); - /* Free the packet */ - janus_ice_free_rtp_packet(p); - } - g_queue_free(component->video_retransmit_buffer); - g_hash_table_destroy(component->video_retransmit_seqs); - } - if(component->candidates != NULL) { - GSList *i = NULL, *candidates = component->candidates; +static void janus_ice_peerconnection_free(const janus_refcount *pc_ref) { + janus_ice_peerconnection *pc = janus_refcount_containerof(pc_ref, janus_ice_peerconnection, ref); + /* This PeerConnection can be destroyed, free all the resources */ + pc->handle = NULL; + g_hash_table_destroy(pc->media); + g_hash_table_destroy(pc->media_byssrc); + g_hash_table_destroy(pc->media_bymid); + g_hash_table_destroy(pc->media_bytype); + if(pc->icestate_source != NULL) { + g_source_destroy(pc->icestate_source); + g_source_unref(pc->icestate_source); + pc->icestate_source = NULL; + } + g_free(pc->remote_hashing); + pc->remote_hashing = NULL; + g_free(pc->remote_fingerprint); + pc->remote_fingerprint = NULL; + g_free(pc->ruser); + pc->ruser = NULL; + g_free(pc->rpass); + pc->rpass = NULL; + g_slist_free_full(pc->transport_wide_received_seq_nums, (GDestroyNotify)g_free); + pc->transport_wide_received_seq_nums = NULL; + if(pc->candidates != NULL) { + GSList *i = NULL, *candidates = pc->candidates; for(i = candidates; i; i = i->next) { NiceCandidate *c = (NiceCandidate *) i->data; if(c != NULL) { @@ -1638,9 +1535,9 @@ static void janus_ice_component_free(const janus_refcount *component_ref) { g_slist_free(candidates); candidates = NULL; } - component->candidates = NULL; - if(component->local_candidates != NULL) { - GSList *i = NULL, *candidates = component->local_candidates; + pc->candidates = NULL; + if(pc->local_candidates != NULL) { + GSList *i = NULL, *candidates = pc->local_candidates; for(i = candidates; i; i = i->next) { gchar *c = (gchar *) i->data; g_free(c); @@ -1648,9 +1545,9 @@ static void janus_ice_component_free(const janus_refcount *component_ref) { g_slist_free(candidates); candidates = NULL; } - component->local_candidates = NULL; - if(component->remote_candidates != NULL) { - GSList *i = NULL, *candidates = component->remote_candidates; + pc->local_candidates = NULL; + if(pc->remote_candidates != NULL) { + GSList *i = NULL, *candidates = pc->remote_candidates; for(i = candidates; i; i = i->next) { gchar *c = (gchar *) i->data; g_free(c); @@ -1658,36 +1555,151 @@ static void janus_ice_component_free(const janus_refcount *component_ref) { g_slist_free(candidates); candidates = NULL; } - component->remote_candidates = NULL; - g_free(component->selected_pair); - component->selected_pair = NULL; - if(component->last_seqs_audio) - janus_seq_list_free(&component->last_seqs_audio); - if(component->last_seqs_video[0]) - janus_seq_list_free(&component->last_seqs_video[0]); - if(component->last_seqs_video[1]) - janus_seq_list_free(&component->last_seqs_video[1]); - if(component->last_seqs_video[2]) - janus_seq_list_free(&component->last_seqs_video[2]); - g_free(component); + pc->remote_candidates = NULL; + g_free(pc->selected_pair); + pc->selected_pair = NULL; + g_free(pc); + pc = NULL; +} + +janus_ice_peerconnection_medium *janus_ice_peerconnection_medium_create(janus_ice_handle *handle, janus_media_type type) { + if(handle == NULL || handle->pc == NULL) + return NULL; + janus_ice_peerconnection *pc = handle->pc; + janus_ice_peerconnection_medium *medium = g_malloc0(sizeof(janus_ice_peerconnection_medium)); + medium->pc = pc; + medium->type = type; + medium->mindex = g_hash_table_size(pc->media); + janus_mutex_init(&medium->mutex); + janus_refcount_init(&medium->ref, janus_ice_peerconnection_medium_free); + janus_refcount_increase(&pc->ref); + g_hash_table_insert(pc->media, GINT_TO_POINTER(medium->mindex), medium); + /* If this is audio or video, fill in some other fields too */ + if(type == JANUS_MEDIA_AUDIO || type == JANUS_MEDIA_VIDEO) { + medium->payload_type = -1; + medium->rtx_payload_type = -1; + medium->ssrc = janus_random_uint32(); /* FIXME Should we look for conflicts? */ + if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX)) { + /* Create an SSRC for RFC4588 as well */ + medium->ssrc_rtx = janus_random_uint32(); /* FIXME Should we look for conflicts? */ + } + medium->rtcp_ctx[0] = g_malloc0(sizeof(janus_rtcp_context)); + medium->rtcp_ctx[0]->tb = (type == JANUS_MEDIA_VIDEO ? 90000 : 48000); /* May change later */ + /* We can address media by SSRC */ + g_hash_table_insert(pc->media_byssrc, GINT_TO_POINTER(medium->ssrc), medium); + janus_refcount_increase(&medium->ref); + if(medium->ssrc_rtx > 0) { + g_hash_table_insert(pc->media_byssrc, GINT_TO_POINTER(medium->ssrc_rtx), medium); + janus_refcount_increase(&medium->ref); + } + g_hash_table_insert(pc->media_bytype, GINT_TO_POINTER(type), medium); + janus_refcount_increase(&medium->ref); + } + /* For backwards compatibility, we address media by type too (e.g., first video stream) */ + g_hash_table_insert(pc->media_bytype, GINT_TO_POINTER(type), medium); + janus_refcount_increase(&medium->ref); + return medium; +} + +static void janus_ice_peerconnection_medium_destroy(janus_ice_peerconnection_medium *medium) { + if(medium == NULL) + return; + janus_ice_peerconnection *pc = medium->pc; + if(pc != NULL) { + janus_refcount_decrease(&pc->ref); + medium->pc = NULL; + } + janus_refcount_decrease(&medium->ref); +} + +static void janus_ice_peerconnection_medium_dereference(janus_ice_peerconnection_medium *medium) { + if(medium == NULL) + return; + janus_refcount_decrease(&medium->ref); +} + +static void janus_ice_peerconnection_medium_free(const janus_refcount *medium_ref) { + janus_ice_peerconnection_medium *medium = janus_refcount_containerof(medium_ref, janus_ice_peerconnection_medium, ref); + g_free(medium->mid); + g_free(medium->rid[0]); + medium->rid[0] = NULL; + g_free(medium->rid[1]); + medium->rid[1] = NULL; + g_free(medium->rid[2]); + medium->rid[2] = NULL; + g_list_free(medium->payload_types); + medium->payload_types = NULL; + if(medium->rtx_payload_types != NULL) + g_hash_table_destroy(medium->rtx_payload_types); + medium->rtx_payload_types = NULL; + if(medium->clock_rates != NULL) + g_hash_table_destroy(medium->clock_rates); + medium->clock_rates = NULL; + g_free(medium->codec); + medium->codec = NULL; + g_free(medium->rtcp_ctx[0]); + medium->rtcp_ctx[0] = NULL; + g_free(medium->rtcp_ctx[1]); + medium->rtcp_ctx[1] = NULL; + g_free(medium->rtcp_ctx[2]); + medium->rtcp_ctx[2] = NULL; + if(medium->rtx_nacked[0]) + g_hash_table_destroy(medium->rtx_nacked[0]); + medium->rtx_nacked[0] = NULL; + if(medium->rtx_nacked[1]) + g_hash_table_destroy(medium->rtx_nacked[1]); + medium->rtx_nacked[1] = NULL; + if(medium->rtx_nacked[2]) + g_hash_table_destroy(medium->rtx_nacked[2]); + medium->rtx_nacked[2] = NULL; + if(medium->pending_nacked_cleanup && g_hash_table_size(medium->pending_nacked_cleanup) > 0) { + GHashTableIter iter; + gpointer val; + g_hash_table_iter_init(&iter, medium->pending_nacked_cleanup); + while(g_hash_table_iter_next(&iter, NULL, &val)) { + GSource *source = val; + g_source_destroy(source); + } + g_hash_table_destroy(medium->pending_nacked_cleanup); + } + medium->pending_nacked_cleanup = NULL; + if(medium->retransmit_buffer != NULL) { + janus_rtp_packet *p = NULL; + while((p = (janus_rtp_packet *)g_queue_pop_head(medium->retransmit_buffer)) != NULL) { + /* Remove from hashtable too */ + janus_rtp_header *header = (janus_rtp_header *)p->data; + guint16 seq = ntohs(header->seq_number); + g_hash_table_remove(medium->retransmit_seqs, GUINT_TO_POINTER(seq)); + /* Free the packet */ + janus_ice_free_rtp_packet(p); + } + g_queue_free(medium->retransmit_buffer); + g_hash_table_destroy(medium->retransmit_seqs); + } + if(medium->last_seqs[0]) + janus_seq_list_free(&medium->last_seqs[0]); + if(medium->last_seqs[1]) + janus_seq_list_free(&medium->last_seqs[1]); + if(medium->last_seqs[2]) + janus_seq_list_free(&medium->last_seqs[2]); + g_free(medium); //~ janus_mutex_unlock(&handle->mutex); } /* Call plugin slow_link callback if a minimum of lost packets are detected within a second */ static void -janus_slow_link_update(janus_ice_component *component, janus_ice_handle *handle, - gboolean video, gboolean uplink, guint lost) { +janus_slow_link_update(janus_ice_peerconnection_medium *medium, janus_ice_handle *handle, + gboolean uplink, guint lost) { /* We keep the counters in different janus_ice_stats objects, depending on the direction */ - guint sl_lost_last_count = uplink ? - (video ? component->in_stats.sl_lost_count_video : component->in_stats.sl_lost_count_audio) : - (video ? component->out_stats.sl_lost_count_video : component->out_stats.sl_lost_count_audio); + gboolean video = (medium->type == JANUS_MEDIA_VIDEO); + guint sl_lost_last_count = uplink ? medium->in_stats.sl_lost_count : medium->out_stats.sl_lost_count; guint sl_lost_recently = (lost >= sl_lost_last_count) ? (lost - sl_lost_last_count) : 0; if(slowlink_threshold > 0 && sl_lost_recently >= slowlink_threshold) { /* Tell the plugin */ janus_plugin *plugin = (janus_plugin *)handle->app; if(plugin && plugin->slow_link && janus_plugin_session_is_alive(handle->app_handle) && !g_atomic_int_get(&handle->destroyed)) - plugin->slow_link(handle->app_handle, uplink, video); + plugin->slow_link(handle->app_handle, medium->mindex, video, uplink); /* Notify the user/application too */ janus_session *session = (janus_session *)handle->session; if(session != NULL) { @@ -1697,6 +1709,7 @@ janus_slow_link_update(janus_ice_component *component, janus_ice_handle *handle, json_object_set_new(event, "sender", json_integer(handle->handle_id)); if(opaqueid_in_api && handle->opaque_id != NULL) json_object_set_new(event, "opaque_id", json_string(handle->opaque_id)); + json_object_set_new(event, "mid", json_string(medium->mid)); json_object_set_new(event, "media", json_string(video ? "video" : "audio")); json_object_set_new(event, "uplink", uplink ? json_true() : json_false()); json_object_set_new(event, "lost", json_integer(sl_lost_recently)); @@ -1706,6 +1719,7 @@ janus_slow_link_update(janus_ice_component *component, janus_ice_handle *handle, /* Finally, notify event handlers */ if(janus_events_is_enabled()) { json_t *info = json_object(); + json_object_set_new(info, "mid", json_string(medium->mid)); json_object_set_new(info, "media", json_string(video ? "video" : "audio")); json_object_set_new(info, "slow_link", json_string(uplink ? "uplink" : "downlink")); json_object_set_new(info, "lost_lastsec", json_integer(sl_lost_recently)); @@ -1716,40 +1730,31 @@ janus_slow_link_update(janus_ice_component *component, janus_ice_handle *handle, } /* Update the counter */ if(uplink) { - if(video) - component->in_stats.sl_lost_count_video = lost; - else - component->in_stats.sl_lost_count_audio = lost; + medium->in_stats.sl_lost_count = lost; } else { - if(video) - component->out_stats.sl_lost_count_video = lost; - else - component->out_stats.sl_lost_count_audio = lost; + medium->out_stats.sl_lost_count = lost; } } /* ICE state check timer (needed to check if a failed really is definitive or if things can still improve) */ static gboolean janus_ice_check_failed(gpointer data) { - janus_ice_component *component = (janus_ice_component *)data; - if(component == NULL) - return FALSE; - janus_ice_stream *stream = component->stream; - if(!stream) + janus_ice_peerconnection *pc = (janus_ice_peerconnection *)data; + if(!pc) goto stoptimer; - janus_ice_handle *handle = stream->handle; + janus_ice_handle *handle = pc->handle; if(!handle) goto stoptimer; if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_STOP) || janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT)) goto stoptimer; - if(component->state == NICE_COMPONENT_STATE_CONNECTED || component->state == NICE_COMPONENT_STATE_READY) { + if(pc->state == NICE_COMPONENT_STATE_CONNECTED || pc->state == NICE_COMPONENT_STATE_READY) { /* ICE succeeded in the meanwhile, get rid of this timer */ JANUS_LOG(LOG_VERB, "[%"SCNu64"] ICE succeeded, disabling ICE state check timer!\n", handle->handle_id); goto stoptimer; } /* Still in the failed state, how much time passed since we first detected it? */ - if(janus_get_monotonic_time() - component->icefailed_detected < 5*G_USEC_PER_SEC) { + if(janus_get_monotonic_time() - pc->icefailed_detected < 5*G_USEC_PER_SEC) { /* Let's wait a little longer */ return TRUE; } @@ -1759,29 +1764,29 @@ static gboolean janus_ice_check_failed(gpointer data) { gboolean alert_set = janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT); /* We may still be waiting for something... but we don't wait forever */ gboolean do_wait = TRUE; - if(janus_get_monotonic_time() - component->icefailed_detected >= 15*G_USEC_PER_SEC) { + if(janus_get_monotonic_time() - pc->icefailed_detected >= 15*G_USEC_PER_SEC) { do_wait = FALSE; } if(!do_wait || (handle && trickle_recv && answer_recv && !alert_set)) { /* FIXME Should we really give up for what may be a failure in only one of the media? */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] ICE failed for component %d in stream %d...\n", - handle->handle_id, component->component_id, stream->stream_id); + handle->handle_id, pc->component_id, pc->stream_id); janus_ice_webrtc_hangup(handle, "ICE failed"); goto stoptimer; } /* Let's wait a little longer */ JANUS_LOG(LOG_WARN, "[%"SCNu64"] ICE failed for component %d in stream %d, but we're still waiting for some info so we don't care... (trickle %s, answer %s, alert %s)\n", - handle->handle_id, component->component_id, stream->stream_id, + handle->handle_id, pc->component_id, pc->stream_id, trickle_recv ? "received" : "pending", answer_recv ? "received" : "pending", alert_set ? "set" : "not set"); return TRUE; stoptimer: - if(component->icestate_source != NULL) { - g_source_destroy(component->icestate_source); - g_source_unref(component->icestate_source); - component->icestate_source = NULL; + if(pc->icestate_source != NULL) { + g_source_destroy(pc->icestate_source); + g_source_unref(pc->icestate_source); + pc->icestate_source = NULL; } return FALSE; } @@ -1793,12 +1798,12 @@ static void janus_ice_cb_candidate_gathering_done(NiceAgent *agent, guint stream return; JANUS_LOG(LOG_VERB, "[%"SCNu64"] Gathering done for stream %d\n", handle->handle_id, stream_id); handle->cdone++; - janus_ice_stream *stream = handle->stream; - if(!stream || stream->stream_id != stream_id) { + janus_ice_peerconnection *pc = handle->pc; + if(!pc || pc->stream_id != stream_id) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] No stream %d??\n", handle->handle_id, stream_id); return; } - stream->cdone = 1; + pc->cdone = 1; /* If we're doing full-trickle, send an event to the user too */ if(janus_full_trickle_enabled) { /* Send a "trickle" event with completed:true to the browser */ @@ -1816,17 +1821,12 @@ static void janus_ice_cb_component_state_changed(NiceAgent *agent, guint stream_ } JANUS_LOG(LOG_VERB, "[%"SCNu64"] Component state changed for component %d in stream %d: %d (%s)\n", handle->handle_id, component_id, stream_id, state, janus_get_ice_state_name(state)); - janus_ice_stream *stream = handle->stream; - if(!stream || stream->stream_id != stream_id) { + janus_ice_peerconnection *pc = handle->pc; + if(!pc || pc->stream_id != stream_id) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] No stream %d??\n", handle->handle_id, stream_id); return; } - janus_ice_component *component = stream->component; - if(!component || component->component_id != component_id) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] No component %d in stream %d??\n", handle->handle_id, component_id, stream_id); - return; - } - component->state = state; + pc->state = state; /* Notify event handlers */ if(janus_events_is_enabled()) { janus_session *session = (janus_session *)handle->session; @@ -1851,11 +1851,11 @@ static void janus_ice_cb_component_state_changed(NiceAgent *agent, guint stream_ answer_recv ? "received" : "pending", alert_set ? "set" : "not set"); /* In case we haven't started a timer yet, let's do it now */ - if(component->icestate_source == NULL && component->icefailed_detected == 0) { - component->icefailed_detected = janus_get_monotonic_time(); - component->icestate_source = g_timeout_source_new(500); - g_source_set_callback(component->icestate_source, janus_ice_check_failed, component, NULL); - guint id = g_source_attach(component->icestate_source, handle->mainctx); + if(pc->icestate_source == NULL && pc->icefailed_detected == 0) { + pc->icefailed_detected = janus_get_monotonic_time(); + pc->icestate_source = g_timeout_source_new(500); + g_source_set_callback(pc->icestate_source, janus_ice_check_failed, pc, NULL); + guint id = g_source_attach(pc->icestate_source, handle->mainctx); JANUS_LOG(LOG_VERB, "[%"SCNu64"] Creating ICE state check timer with ID %u\n", handle->handle_id, id); } } @@ -1878,16 +1878,11 @@ static void janus_ice_cb_new_selected_pair (NiceAgent *agent, guint stream_id, g #else JANUS_LOG(LOG_VERB, "[%"SCNu64"] New selected pair for component %d in stream %d: %s <-> %s\n", handle ? handle->handle_id : 0, component_id, stream_id, local->foundation, remote->foundation); #endif - janus_ice_stream *stream = handle->stream; - if(!stream || stream->stream_id != stream_id) { + janus_ice_peerconnection *pc = handle->pc; + if(!pc || pc->stream_id != stream_id) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] No stream %d??\n", handle->handle_id, stream_id); return; } - janus_ice_component *component = stream->component; - if(!component || component->component_id != component_id) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] No component %d in stream %d??\n", handle->handle_id, component_id, stream_id); - return; - } char sp[200]; #ifndef HAVE_LIBNICE_TCP g_snprintf(sp, 200, "%s <-> %s", local, remote); @@ -1936,10 +1931,10 @@ static void janus_ice_cb_new_selected_pair (NiceAgent *agent, guint stream_id, g raddress, rport, rtype, remote->transport == NICE_CANDIDATE_TRANSPORT_UDP ? "udp" : "tcp"); #endif gboolean newpair = FALSE; - if(component->selected_pair == NULL || strcmp(sp, component->selected_pair)) { + if(pc->selected_pair == NULL || strcmp(sp, pc->selected_pair)) { newpair = TRUE; - gchar *prev_selected_pair = component->selected_pair; - component->selected_pair = g_strdup(sp); + gchar *prev_selected_pair = pc->selected_pair; + pc->selected_pair = g_strdup(sp); g_clear_pointer(&prev_selected_pair, g_free); } /* Notify event handlers */ @@ -1971,13 +1966,13 @@ static void janus_ice_cb_new_selected_pair (NiceAgent *agent, guint stream_id, g session->session_id, handle->handle_id, handle->opaque_id, info); } /* Have we been here before? (might happen, when trickling) */ - if(component->component_connected > 0) + if(pc->connected > 0) return; /* FIXME Clear the queue */ janus_ice_clear_queued_packets(handle); /* Now we can start the DTLS handshake (FIXME This was on the 'connected' state notification, before) */ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Component is ready enough, starting DTLS handshake...\n", handle->handle_id); - component->component_connected = janus_get_monotonic_time(); + pc->connected = janus_get_monotonic_time(); /* Start the DTLS handshake, at last */ #if GLIB_CHECK_VERSION(2, 46, 0) g_async_queue_push_front(handle->queued_packets, &janus_ice_dtls_handshake); @@ -2030,16 +2025,11 @@ static void janus_ice_cb_new_local_candidate (NiceAgent *agent, NiceCandidate *c /* New remote candidate for a component we don't need anymore (rtcp-mux) */ return; } - janus_ice_stream *stream = handle->stream; - if(!stream || stream->stream_id != stream_id) { + janus_ice_peerconnection *pc = handle->pc; + if(!pc || pc->stream_id != stream_id) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] No stream %d??\n", handle->handle_id, stream_id); return; } - janus_ice_component *component = stream->component; - if(!component || component->component_id != component_id) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] No component %d in stream %d??\n", handle->handle_id, component_id, stream_id); - return; - } #ifndef HAVE_LIBNICE_TCP /* Get local candidates and look for the related foundation */ NiceCandidate *candidate = NULL; @@ -2132,16 +2122,11 @@ static void janus_ice_cb_new_remote_candidate (NiceAgent *agent, NiceCandidate * /* New remote candidate for a component we don't need anymore (rtcp-mux) */ return; } - janus_ice_stream *stream = handle->stream; - if(!stream || stream->stream_id != stream_id) { + janus_ice_peerconnection *pc = handle->pc; + if(!pc || pc->stream_id != stream_id) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] No stream %d??\n", handle->handle_id, stream_id); return; } - janus_ice_component *component = stream->component; - if(!component || component->component_id != component_id) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] No component %d in stream %d??\n", handle->handle_id, component_id, stream_id); - return; - } #ifndef HAVE_LIBNICE_TCP /* Get remote candidates and look for the related foundation */ NiceCandidate *candidate = NULL; @@ -2239,7 +2224,7 @@ static void janus_ice_cb_new_remote_candidate (NiceAgent *agent, NiceCandidate * } /* Now parse the candidate as if we received it from the Janus API */ - int res = janus_sdp_parse_candidate(stream, buffer, 1); + int res = janus_sdp_parse_candidate(pc, buffer, 1); if(res != 0) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse prflx candidate... (%d)\n", handle->handle_id, res); } @@ -2252,23 +2237,18 @@ static void janus_ice_cb_new_remote_candidate (NiceAgent *agent, NiceCandidate * } static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id, guint len, gchar *buf, gpointer ice) { - janus_ice_component *component = (janus_ice_component *)ice; - if(!component) { + janus_ice_peerconnection *pc = (janus_ice_peerconnection *)ice; + if(!pc) { JANUS_LOG(LOG_ERR, "No component %d in stream %d??\n", component_id, stream_id); return; } - janus_ice_stream *stream = component->stream; - if(!stream) { - JANUS_LOG(LOG_ERR, "No stream %d??\n", stream_id); - return; - } - janus_ice_handle *handle = stream->handle; + janus_ice_handle *handle = pc->handle; if(!handle) { JANUS_LOG(LOG_ERR, "No handle for stream %d??\n", stream_id); return; } janus_session *session = (janus_session *)handle->session; - if(!component->dtls) { /* Still waiting for the DTLS stack */ + if(!pc->dtls) { /* Still waiting for the DTLS stack */ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Still waiting for the DTLS stack for component %d in stream %d...\n", handle->handle_id, component_id, stream_id); return; } @@ -2280,130 +2260,119 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp if(janus_is_dtls(buf) || (!janus_is_rtp(buf, len) && !janus_is_rtcp(buf, len))) { /* This is DTLS: either handshake stuff, or data coming from SCTP DataChannels */ JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Looks like DTLS!\n", handle->handle_id); - janus_dtls_srtp_incoming_msg(component->dtls, buf, len); + janus_dtls_srtp_incoming_msg(pc->dtls, buf, len); /* Update stats (TODO Do the same for the last second window as well) */ - component->in_stats.data.packets++; - component->in_stats.data.bytes += len; + pc->dtls_in_stats.info[0].packets++; + pc->dtls_in_stats.info[0].bytes += len; return; } /* Not DTLS... RTP or RTCP? (http://tools.ietf.org/html/rfc5761#section-4) */ if(janus_is_rtp(buf, len)) { /* This is RTP */ - if(janus_is_webrtc_encryption_enabled() && (!component->dtls || !component->dtls->srtp_valid || !component->dtls->srtp_in)) { + if(janus_is_webrtc_encryption_enabled() && (!pc->dtls || !pc->dtls->srtp_valid || !pc->dtls->srtp_in)) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Missing valid SRTP session (packet arrived too early?), skipping...\n", handle->handle_id); } else { janus_rtp_header *header = (janus_rtp_header *)buf; guint32 packet_ssrc = ntohl(header->ssrc); - /* Is this audio or video? */ + /* Which medium does this refer to? Is this audio or video? */ int video = 0, vindex = 0, rtx = 0; - /* Bundled streams, check SSRC */ - video = ((stream->video_ssrc_peer[0] == packet_ssrc - || stream->video_ssrc_peer_rtx[0] == packet_ssrc - || stream->video_ssrc_peer[1] == packet_ssrc - || stream->video_ssrc_peer_rtx[1] == packet_ssrc - || stream->video_ssrc_peer[2] == packet_ssrc - || stream->video_ssrc_peer_rtx[2] == packet_ssrc) ? 1 : 0); - if(!video && stream->audio_ssrc_peer != packet_ssrc) { - /* Apparently we were not told the peer SSRCs, try the RTP mid extension (or payload types) */ - gboolean found = FALSE; - if(handle->stream->mid_ext_id > 0) { + janus_ice_peerconnection_medium *medium = g_hash_table_lookup(pc->media_byssrc, GINT_TO_POINTER(packet_ssrc)); + if(medium == NULL) { + /* SSRC not found, try the mid/rid RTP extensions if in use */ + if(pc->mid_ext_id > 0) { char sdes_item[16]; - if(janus_rtp_header_extension_parse_mid(buf, len, handle->stream->mid_ext_id, sdes_item, sizeof(sdes_item)) == 0) { - if(handle->audio_mid && !strcmp(handle->audio_mid, sdes_item)) { - /* It's audio */ - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Unadvertized SSRC (%"SCNu32") is audio! (mid %s)\n", handle->handle_id, packet_ssrc, sdes_item); - video = 0; - stream->audio_ssrc_peer = packet_ssrc; - found = TRUE; - } else if(handle->video_mid && !strcmp(handle->video_mid, sdes_item)) { - /* It's video */ - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Unadvertized SSRC (%"SCNu32") is video! (mid %s)\n", handle->handle_id, packet_ssrc, sdes_item); - video = 1; + if(janus_rtp_header_extension_parse_mid(buf, len, pc->mid_ext_id, sdes_item, sizeof(sdes_item)) == 0) { + medium = g_hash_table_lookup(pc->media_bymid, sdes_item); + if(medium != NULL) { + /* Found! Associate this SSRC to this stream */ + JANUS_LOG(LOG_VERB, "[%"SCNu64"] SSRC %"SCNu32" is associated to mid %s\n", + handle->handle_id, packet_ssrc, medium->mid); + gboolean found = FALSE; /* Check if simulcasting is involved */ - if(stream->rid[0] == NULL || stream->rid_ext_id < 1) { - stream->video_ssrc_peer[0] = packet_ssrc; + if(medium->rid[0] == NULL || pc->rid_ext_id < 1) { + medium->ssrc_peer[0] = packet_ssrc; found = TRUE; } else { - if(janus_rtp_header_extension_parse_rid(buf, len, stream->rid_ext_id, sdes_item, sizeof(sdes_item)) == 0) { + if(janus_rtp_header_extension_parse_rid(buf, len, pc->rid_ext_id, sdes_item, sizeof(sdes_item)) == 0) { /* Try the RTP stream ID */ - if(stream->rid[0] != NULL && !strcmp(stream->rid[0], sdes_item)) { + if(medium->rid[0] != NULL && !strcmp(medium->rid[0], sdes_item)) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Simulcasting: rid=%s\n", handle->handle_id, sdes_item); - stream->video_ssrc_peer[0] = packet_ssrc; - vindex = 0; + medium->ssrc_peer[0] = packet_ssrc; found = TRUE; - } else if(stream->rid[1] != NULL && !strcmp(stream->rid[1], sdes_item)) { + } else if(medium->rid[1] != NULL && !strcmp(medium->rid[1], sdes_item)) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Simulcasting #1: rid=%s\n", handle->handle_id, sdes_item); - stream->video_ssrc_peer[1] = packet_ssrc; - vindex = 1; + medium->ssrc_peer[1] = packet_ssrc; found = TRUE; - } else if(stream->rid[2] != NULL && !strcmp(stream->rid[2], sdes_item)) { + } else if(medium->rid[2] != NULL && !strcmp(medium->rid[2], sdes_item)) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Simulcasting #2: rid=%s\n", handle->handle_id, sdes_item); - stream->video_ssrc_peer[2] = packet_ssrc; - vindex = 2; + medium->ssrc_peer[2] = packet_ssrc; found = TRUE; } else { JANUS_LOG(LOG_WARN, "[%"SCNu64"] -- Simulcasting: unknown rid %s..?\n", handle->handle_id, sdes_item); } - } else if(stream->ridrtx_ext_id > 0 && - janus_rtp_header_extension_parse_rid(buf, len, stream->ridrtx_ext_id, sdes_item, sizeof(sdes_item)) == 0) { + } else if(pc->ridrtx_ext_id > 0 && + janus_rtp_header_extension_parse_rid(buf, len, pc->ridrtx_ext_id, sdes_item, sizeof(sdes_item)) == 0) { /* Try the repaired RTP stream ID */ - if(stream->rid[0] != NULL && !strcmp(stream->rid[0], sdes_item)) { + if(medium->rid[0] != NULL && !strcmp(medium->rid[0], sdes_item)) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Simulcasting: rid=%s (rtx)\n", handle->handle_id, sdes_item); - stream->video_ssrc_peer_rtx[0] = packet_ssrc; - vindex = 0; - rtx = 1; + medium->ssrc_peer_rtx[0] = packet_ssrc; found = TRUE; - } else if(stream->rid[1] != NULL && !strcmp(stream->rid[1], sdes_item)) { + } else if(medium->rid[1] != NULL && !strcmp(medium->rid[1], sdes_item)) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Simulcasting #1: rid=%s (rtx)\n", handle->handle_id, sdes_item); - stream->video_ssrc_peer_rtx[1] = packet_ssrc; - vindex = 1; - rtx = 1; + medium->ssrc_peer_rtx[1] = packet_ssrc; found = TRUE; - } else if(stream->rid[2] != NULL && !strcmp(stream->rid[2], sdes_item)) { + } else if(medium->rid[2] != NULL && !strcmp(medium->rid[2], sdes_item)) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Simulcasting #2: rid=%s (rtx)\n", handle->handle_id, sdes_item); - stream->video_ssrc_peer_rtx[2] = packet_ssrc; - vindex = 2; - rtx = 1; + medium->ssrc_peer_rtx[2] = packet_ssrc; found = TRUE; } else { JANUS_LOG(LOG_WARN, "[%"SCNu64"] -- Simulcasting: unknown rid %s..?\n", handle->handle_id, sdes_item); } } } + if(found) { + g_hash_table_insert(pc->media_byssrc, GINT_TO_POINTER(packet_ssrc), medium); + janus_refcount_increase(&medium->ref); + } else { + medium = NULL; + } } } } - if(!found) { - JANUS_LOG(LOG_WARN, "[%"SCNu64"] Not video and not audio? dropping (SSRC %"SCNu32")...\n", handle->handle_id, packet_ssrc); - return; - } } + if(medium == NULL) { + JANUS_LOG(LOG_WARN, "[%"SCNu64"] Unknown SSRC, dropping packet (SSRC %"SCNu32")...\n", + handle->handle_id, packet_ssrc); + return; + } + video = (medium->type == JANUS_MEDIA_VIDEO); /* Make sure we're prepared to receive this media packet */ - if((!video && !stream->audio_recv) || (video && !stream->video_recv)) + if(!medium->recv) return; /* If this is video, check if this is simulcast and/or a retransmission using RFC4588 */ - if(video) { - if(stream->video_ssrc_peer[1] == packet_ssrc) { + vindex = 0; + if(video && medium->ssrc_peer[0] != packet_ssrc) { + if(medium->ssrc_peer[1] == packet_ssrc) { /* FIXME Simulcast (1) */ JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Simulcast #1 (SSRC %"SCNu32")...\n", handle->handle_id, packet_ssrc); vindex = 1; - } else if(stream->video_ssrc_peer[2] == packet_ssrc) { + } else if(medium->ssrc_peer[2] == packet_ssrc) { /* FIXME Simulcast (2) */ JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Simulcast #2 (SSRC %"SCNu32")...\n", handle->handle_id, packet_ssrc); vindex = 2; } else { /* Maybe a video retransmission using RFC4588? */ - if(stream->video_ssrc_peer_rtx[0] == packet_ssrc) { + if(medium->ssrc_peer_rtx[0] == packet_ssrc) { rtx = 1; vindex = 0; JANUS_LOG(LOG_HUGE, "[%"SCNu64"] RFC4588 rtx packet on video (SSRC %"SCNu32")...\n", handle->handle_id, packet_ssrc); - } else if(stream->video_ssrc_peer_rtx[1] == packet_ssrc) { + } else if(medium->ssrc_peer_rtx[1] == packet_ssrc) { rtx = 1; vindex = 1; JANUS_LOG(LOG_HUGE, "[%"SCNu64"] RFC4588 rtx packet on video #%d (SSRC %"SCNu32")...\n", handle->handle_id, vindex, packet_ssrc); - } else if(stream->video_ssrc_peer_rtx[2] == packet_ssrc) { + } else if(medium->ssrc_peer_rtx[2] == packet_ssrc) { rtx = 1; vindex = 2; JANUS_LOG(LOG_HUGE, "[%"SCNu64"] RFC4588 rtx packet on video #%d (SSRC %"SCNu32")...\n", @@ -2414,7 +2383,7 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp int buflen = len; srtp_err_status_t res = janus_is_webrtc_encryption_enabled() ? - srtp_unprotect(component->dtls->srtp_in, buf, &buflen) : srtp_err_status_ok; + srtp_unprotect(pc->dtls->srtp_in, buf, &buflen) : srtp_err_status_ok; if(res != srtp_err_status_ok) { if(res != srtp_err_status_replay_fail && res != srtp_err_status_replay_old) { /* Only print the error if it's not a 'replay fail' or 'replay old' (which is probably just the result of us NACKing a packet) */ @@ -2423,16 +2392,12 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp JANUS_LOG(LOG_ERR, "[%"SCNu64"] SRTP unprotect error: %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")\n", handle->handle_id, janus_srtp_error_str(res), len, buflen, timestamp, seq); } } else { - if(video) { - if(stream->video_ssrc_peer[0] == 0) { - stream->video_ssrc_peer[0] = ntohl(header->ssrc); - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC: %u\n", handle->handle_id, stream->video_ssrc_peer[0]); - } - } else { - if(stream->audio_ssrc_peer == 0) { - stream->audio_ssrc_peer = ntohl(header->ssrc); - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer audio SSRC: %u\n", handle->handle_id, stream->audio_ssrc_peer); - } + if(medium->ssrc_peer[0] == 0) { + medium->ssrc_peer[0] = ntohl(header->ssrc); + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer #%d (%s) SSRC: %u\n", + handle->handle_id, medium->mindex, + medium->type == JANUS_MEDIA_VIDEO ? "video" : "audio", + medium->ssrc_peer[0]); } /* Do we need to dump this packet for debugging? */ if(g_atomic_int_get(&handle->dump_packets)) @@ -2448,8 +2413,8 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp if(rtx) { /* The original sequence number is in the first two bytes of the payload */ /* Rewrite the header with the info from the original packet (payload type, SSRC, sequence number) */ - header->type = stream->video_payload_type; - packet_ssrc = stream->video_ssrc_peer[vindex]; + header->type = medium->payload_type; + packet_ssrc = medium->ssrc_peer[vindex]; header->ssrc = htonl(packet_ssrc); if(plen > 0) { memcpy(&header->seq_number, payload, 2); @@ -2459,50 +2424,50 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp plen -= 2; memmove(payload, payload+2, plen); header = (janus_rtp_header *)buf; - if(stream->rid_ext_id > 1 && stream->ridrtx_ext_id > 1) { + if(pc->rid_ext_id > 1 && pc->ridrtx_ext_id > 1) { /* Replace the 'repaired' extension ID as well with the 'regular' one */ - janus_rtp_header_extension_replace_id(buf, buflen, stream->ridrtx_ext_id, stream->rid_ext_id); + janus_rtp_header_extension_replace_id(buf, buflen, pc->ridrtx_ext_id, pc->rid_ext_id); } } } /* Check if we need to handle transport wide cc */ - if(stream->do_transport_wide_cc) { + if(pc->do_transport_wide_cc) { guint16 transport_seq_num; /* Get transport wide seq num */ - if(janus_rtp_header_extension_parse_transport_wide_cc(buf, buflen, stream->transport_wide_cc_ext_id, &transport_seq_num)==0) { + if(janus_rtp_header_extension_parse_transport_wide_cc(buf, buflen, pc->transport_wide_cc_ext_id, &transport_seq_num) == 0) { /* Get current timestamp */ struct timeval now; gettimeofday(&now,0); /* Create pair */ janus_rtcp_transport_wide_cc_stats *stats = g_malloc0(sizeof(janus_rtcp_transport_wide_cc_stats)); /* Check if we have a sequence wrap */ - if(transport_seq_num<0x0FFF && (stream->transport_wide_cc_last_seq_num&0xFFFF)>0xF000) { + if(transport_seq_num<0x0FFF && (pc->transport_wide_cc_last_seq_num&0xFFFF)>0xF000) { /* Increase cycles */ - stream->transport_wide_cc_cycles++; + pc->transport_wide_cc_cycles++; } /* Get extended value */ - guint32 transport_ext_seq_num = stream->transport_wide_cc_cycles<<16 | transport_seq_num; + guint32 transport_ext_seq_num = pc->transport_wide_cc_cycles<<16 | transport_seq_num; /* Store last received transport seq num */ - stream->transport_wide_cc_last_seq_num = transport_seq_num; + pc->transport_wide_cc_last_seq_num = transport_seq_num; /* Set stats values */ stats->transport_seq_num = transport_ext_seq_num; stats->timestamp = (((guint64)now.tv_sec)*1E6+now.tv_usec); /* Lock and append to received list */ - janus_mutex_lock(&stream->mutex); - stream->transport_wide_received_seq_nums = g_slist_prepend(stream->transport_wide_received_seq_nums, stats); - janus_mutex_unlock(&stream->mutex); + janus_mutex_lock(&pc->mutex); + pc->transport_wide_received_seq_nums = g_slist_prepend(pc->transport_wide_received_seq_nums, stats); + janus_mutex_unlock(&pc->mutex); } } - if(video) { + if(medium->do_nacks) { /* Check if this packet is a duplicate: can happen with RFC4588 */ guint16 seqno = ntohs(header->seq_number); - int nstate = stream->rtx_nacked[vindex] ? - GPOINTER_TO_INT(g_hash_table_lookup(stream->rtx_nacked[vindex], GUINT_TO_POINTER(seqno))) : 0; + int nstate = medium->rtx_nacked[vindex] ? + GPOINTER_TO_INT(g_hash_table_lookup(medium->rtx_nacked[vindex], GUINT_TO_POINTER(seqno))) : 0; if(nstate == 1) { /* Packet was NACKed and this is the first time we receive it: change state to received */ JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Received NACKed packet %"SCNu16" (SSRC %"SCNu32", vindex %d)...\n", handle->handle_id, seqno, packet_ssrc, vindex); - g_hash_table_insert(stream->rtx_nacked[vindex], GUINT_TO_POINTER(seqno), GUINT_TO_POINTER(2)); + g_hash_table_insert(medium->rtx_nacked[vindex], GUINT_TO_POINTER(seqno), GUINT_TO_POINTER(2)); } else if(nstate == 2) { /* We already received this packet: drop it */ JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Detected duplicate packet %"SCNu16" (SSRC %"SCNu32", vindex %d)...\n", @@ -2522,68 +2487,54 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp } /* Backup the RTP header before passing it to the proper RTP switching context */ janus_rtp_header backup = *header; - if(!video) { - if(stream->audio_ssrc_peer_orig == 0) - stream->audio_ssrc_peer_orig = packet_ssrc; - janus_rtp_header_update(header, &stream->rtp_ctx[0], FALSE, 0); - header->ssrc = htonl(stream->audio_ssrc_peer_orig); - } else { - if(stream->video_ssrc_peer_orig[vindex] == 0) - stream->video_ssrc_peer_orig[vindex] = packet_ssrc; - janus_rtp_header_update(header, &stream->rtp_ctx[vindex], TRUE, 0); - header->ssrc = htonl(stream->video_ssrc_peer_orig[vindex]); - } + if(medium->ssrc_peer_orig[vindex] == 0) + medium->ssrc_peer_orig[vindex] = packet_ssrc; + janus_rtp_header_update(header, &medium->rtp_ctx[vindex], medium->type == JANUS_MEDIA_VIDEO, 0); + header->ssrc = htonl(medium->ssrc_peer_orig[vindex]); /* Keep track of payload types too */ - if(!video && stream->audio_payload_type < 0) { - stream->audio_payload_type = header->type; - if(stream->audio_codec == NULL) { - const char *codec = janus_get_codec_from_pt(handle->local_sdp, stream->audio_payload_type); - if(codec != NULL) - stream->audio_codec = g_strdup(codec); - } - } else if(video && stream->video_payload_type < 0) { - stream->video_payload_type = header->type; + if(medium->payload_type < 0) { + medium->payload_type = header->type; if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX) && - stream->rtx_payload_types && g_hash_table_size(stream->rtx_payload_types) > 0) { - stream->video_rtx_payload_type = GPOINTER_TO_INT(g_hash_table_lookup(stream->rtx_payload_types, GINT_TO_POINTER(stream->video_payload_type))); + medium->rtx_payload_types && g_hash_table_size(medium->rtx_payload_types) > 0) { + medium->rtx_payload_type = GPOINTER_TO_INT(g_hash_table_lookup(medium->rtx_payload_types, GINT_TO_POINTER(medium->payload_type))); JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Retransmissions will have payload type %d\n", - handle->handle_id, stream->video_rtx_payload_type); + handle->handle_id, medium->rtx_payload_type); } - if(stream->video_codec == NULL) { - const char *codec = janus_get_codec_from_pt(handle->local_sdp, stream->video_payload_type); + if(medium->codec == NULL) { + const char *codec = janus_get_codec_from_pt(handle->local_sdp, medium->payload_type); if(codec != NULL) - stream->video_codec = g_strdup(codec); + medium->codec = g_strdup(codec); } - if(stream->video_is_keyframe == NULL && stream->video_codec != NULL) { - if(!strcasecmp(stream->video_codec, "vp8")) - stream->video_is_keyframe = &janus_vp8_is_keyframe; - else if(!strcasecmp(stream->video_codec, "vp9")) - stream->video_is_keyframe = &janus_vp9_is_keyframe; - else if(!strcasecmp(stream->video_codec, "h264")) - stream->video_is_keyframe = &janus_h264_is_keyframe; - else if(!strcasecmp(stream->video_codec, "av1")) - stream->video_is_keyframe = &janus_av1_is_keyframe; - else if(!strcasecmp(stream->video_codec, "h265")) - stream->video_is_keyframe = &janus_h265_is_keyframe; + if(medium->type == JANUS_MEDIA_VIDEO && medium->video_is_keyframe == NULL && medium->codec != NULL) { + if(!strcasecmp(medium->codec, "vp8")) + medium->video_is_keyframe = &janus_vp8_is_keyframe; + else if(!strcasecmp(medium->codec, "vp9")) + medium->video_is_keyframe = &janus_vp9_is_keyframe; + else if(!strcasecmp(medium->codec, "h264")) + medium->video_is_keyframe = &janus_h264_is_keyframe; + else if(!strcasecmp(medium->codec, "av1")) + medium->video_is_keyframe = &janus_av1_is_keyframe; + else if(!strcasecmp(medium->codec, "h265")) + medium->video_is_keyframe = &janus_h265_is_keyframe; } } /* Prepare the data to pass to the responsible plugin */ - janus_plugin_rtp rtp = { .video = video, .buffer = buf, .length = buflen }; + janus_plugin_rtp rtp = { .mindex = medium->mindex, .video = video, .buffer = buf, .length = buflen }; janus_plugin_rtp_extensions_reset(&rtp.extensions); /* Parse RTP extensions before involving the plugin */ - if(stream->audiolevel_ext_id != -1) { + if(pc->audiolevel_ext_id != -1) { gboolean vad = FALSE; int level = -1; if(janus_rtp_header_extension_parse_audio_level(buf, buflen, - stream->audiolevel_ext_id, &vad, &level) == 0) { + pc->audiolevel_ext_id, &vad, &level) == 0) { rtp.extensions.audio_level = level; rtp.extensions.audio_level_vad = vad; } } - if(stream->videoorientation_ext_id != -1) { + if(pc->videoorientation_ext_id != -1) { gboolean c = FALSE, f = FALSE, r1 = FALSE, r0 = FALSE; if(janus_rtp_header_extension_parse_video_orientation(buf, buflen, - stream->videoorientation_ext_id, &c, &f, &r1, &r0) == 0) { + pc->videoorientation_ext_id, &c, &f, &r1, &r0) == 0) { rtp.extensions.video_rotation = 0; if(r1 && r0) rtp.extensions.video_rotation = 270; @@ -2606,54 +2557,33 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp /* Update stats (overall data received, and data received in the last second) */ if(buflen > 0) { gint64 now = janus_get_monotonic_time(); - if(!video) { - if(component->in_stats.audio.bytes == 0 || component->in_stats.audio.notified_lastsec) { - /* We either received our first audio packet, or we started receiving it again after missing more than a second */ - component->in_stats.audio.notified_lastsec = FALSE; - janus_ice_notify_media(handle, FALSE, TRUE); - } - /* Overall audio data */ - component->in_stats.audio.packets++; - component->in_stats.audio.bytes += buflen; - /* Last second audio data */ - if(component->in_stats.audio.updated == 0) - component->in_stats.audio.updated = now; - if(now > component->in_stats.audio.updated && - now - component->in_stats.audio.updated >= G_USEC_PER_SEC) { - component->in_stats.audio.bytes_lastsec = component->in_stats.audio.bytes_lastsec_temp; - component->in_stats.audio.bytes_lastsec_temp = 0; - component->in_stats.audio.updated = now; - } - component->in_stats.audio.bytes_lastsec_temp += buflen; - } else { - if(component->in_stats.video[vindex].bytes == 0 || component->in_stats.video[vindex].notified_lastsec) { - /* We either received our first video packet, or we started receiving it again after missing more than a second */ - component->in_stats.video[vindex].notified_lastsec = FALSE; - janus_ice_notify_media(handle, TRUE, TRUE); - } - /* Overall video data for this SSRC */ - component->in_stats.video[vindex].packets++; - component->in_stats.video[vindex].bytes += buflen; - /* Last second video data for this SSRC */ - if(component->in_stats.video[vindex].updated == 0) - component->in_stats.video[vindex].updated = now; - if(now > component->in_stats.video[vindex].updated && - now - component->in_stats.video[vindex].updated >= G_USEC_PER_SEC) { - component->in_stats.video[vindex].bytes_lastsec = component->in_stats.video[vindex].bytes_lastsec_temp; - component->in_stats.video[vindex].bytes_lastsec_temp = 0; - component->in_stats.video[vindex].updated = now; - } - component->in_stats.video[vindex].bytes_lastsec_temp += buflen; + if(medium->in_stats.info[vindex].bytes == 0 || medium->in_stats.info[vindex].notified_lastsec) { + /* We either received our first packet, or we started receiving it again after missing more than a second */ + medium->in_stats.info[vindex].notified_lastsec = FALSE; + janus_ice_notify_media(handle, medium->mid, medium->type == JANUS_MEDIA_VIDEO, TRUE); } + /* Overall video data for this SSRC */ + medium->in_stats.info[vindex].packets++; + medium->in_stats.info[vindex].bytes += buflen; + /* Last second video data for this SSRC */ + if(medium->in_stats.info[vindex].updated == 0) + medium->in_stats.info[vindex].updated = now; + if(now > medium->in_stats.info[vindex].updated && + now - medium->in_stats.info[vindex].updated >= G_USEC_PER_SEC) { + medium->in_stats.info[vindex].bytes_lastsec = medium->in_stats.info[vindex].bytes_lastsec_temp; + medium->in_stats.info[vindex].bytes_lastsec_temp = 0; + medium->in_stats.info[vindex].updated = now; + } + medium->in_stats.info[vindex].bytes_lastsec_temp += buflen; } /* Update the RTCP context as well */ - rtcp_context *rtcp_ctx = video ? stream->video_rtcp_ctx[vindex] : stream->audio_rtcp_ctx; - gboolean retransmissions_disabled = (!video && !component->do_audio_nacks) || (video && !component->do_video_nacks); + rtcp_context *rtcp_ctx = medium->rtcp_ctx[vindex]; + gboolean retransmissions_disabled = !medium->do_nacks; janus_rtcp_process_incoming_rtp(rtcp_ctx, buf, buflen, (video && rtx) ? TRUE : FALSE, (video && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX)), - retransmissions_disabled, stream->clock_rates + retransmissions_disabled, medium->clock_rates ); /* Keep track of RTP sequence numbers, in case we need to NACK them */ @@ -2664,18 +2594,18 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp } guint16 new_seqn = ntohs(header->seq_number); /* If this is video, check if this is a keyframe: if so, we empty our NACK queue */ - if(video && stream->video_is_keyframe) { - if(stream->video_is_keyframe(payload, plen)) { + if(video && medium->video_is_keyframe) { + if(medium->video_is_keyframe(payload, plen)) { if(rtcp_ctx && (int16_t)(new_seqn - rtcp_ctx->max_seq_nr) > 0) { JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Keyframe received with a highest sequence number, resetting NACK queue\n", handle->handle_id); - janus_seq_list_free(&component->last_seqs_video[vindex]); + janus_seq_list_free(&medium->last_seqs[vindex]); } } } guint16 cur_seqn; int last_seqs_len = 0; - janus_mutex_lock(&component->mutex); - janus_seq_info **last_seqs = video ? &component->last_seqs_video[vindex] : &component->last_seqs_audio; + janus_mutex_lock(&medium->mutex); + janus_seq_info **last_seqs = &medium->last_seqs[vindex]; janus_seq_info *cur_seq = *last_seqs; if(cur_seq) { cur_seq = cur_seq->prev; @@ -2726,21 +2656,21 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp /* Keep track of this sequence number, we need to avoid duplicates */ JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Tracking NACKed packet %"SCNu16" (SSRC %"SCNu32", vindex %d)...\n", handle->handle_id, cur_seq->seq, packet_ssrc, vindex); - if(stream->rtx_nacked[vindex] == NULL) - stream->rtx_nacked[vindex] = g_hash_table_new(NULL, NULL); - g_hash_table_insert(stream->rtx_nacked[vindex], GUINT_TO_POINTER(cur_seq->seq), GINT_TO_POINTER(1)); + if(medium->rtx_nacked[vindex] == NULL) + medium->rtx_nacked[vindex] = g_hash_table_new(NULL, NULL); + g_hash_table_insert(medium->rtx_nacked[vindex], GUINT_TO_POINTER(cur_seq->seq), GINT_TO_POINTER(1)); /* We don't track it forever, though: add a timed source to remove it in a few seconds */ janus_ice_nacked_packet *np = g_malloc(sizeof(janus_ice_nacked_packet)); - np->handle = handle; + np->medium = medium; np->seq_number = cur_seq->seq; np->vindex = vindex; - if(stream->pending_nacked_cleanup == NULL) - stream->pending_nacked_cleanup = g_hash_table_new(NULL, NULL); + if(medium->pending_nacked_cleanup == NULL) + medium->pending_nacked_cleanup = g_hash_table_new(NULL, NULL); GSource *timeout_source = g_timeout_source_new_seconds(5); g_source_set_callback(timeout_source, janus_ice_nacked_packet_cleanup, np, (GDestroyNotify)g_free); np->source_id = g_source_attach(timeout_source, handle->mainctx); g_source_unref(timeout_source); - g_hash_table_insert(stream->pending_nacked_cleanup, GUINT_TO_POINTER(np->source_id), timeout_source); + g_hash_table_insert(medium->pending_nacked_cleanup, GUINT_TO_POINTER(np->source_id), timeout_source); } } else if(cur_seq->state == SEQ_NACKED && now - cur_seq->ts > SEQ_NACKED_WAIT) { JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Missed sequence number %"SCNu16" (%s stream #%d), sending 2nd NACK\n", @@ -2771,27 +2701,22 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp if(res > 0) { /* Set the right local and remote SSRC in the RTCP packet */ janus_rtcp_fix_ssrc(NULL, nackbuf, res, 1, - video ? stream->video_ssrc : stream->audio_ssrc, - video ? stream->video_ssrc_peer[vindex] : stream->audio_ssrc_peer); - janus_plugin_rtcp rtcp = { .video = video, .buffer = nackbuf, .length = res }; + medium->ssrc, medium->ssrc_peer[vindex]); + janus_plugin_rtcp rtcp = { .mindex = medium->mindex, .video = video, .buffer = nackbuf, .length = res }; janus_ice_relay_rtcp_internal(handle, &rtcp, FALSE); } /* Update stats */ - component->nack_sent_recent_cnt += nacks_count; - if(video) { - component->out_stats.video[vindex].nacks += nacks_count; - } else { - component->out_stats.audio.nacks += nacks_count; - } + medium->nack_sent_recent_cnt += nacks_count; + medium->out_stats.info[vindex].nacks += nacks_count; } - if(component->nack_sent_recent_cnt && - (now - component->nack_sent_log_ts) > 5*G_USEC_PER_SEC) { + if(medium->nack_sent_recent_cnt && + (now - medium->nack_sent_log_ts) > 5*G_USEC_PER_SEC) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Sent NACKs for %u missing packets (%s stream #%d)\n", - handle->handle_id, component->nack_sent_recent_cnt, video ? "video" : "audio", vindex); - component->nack_sent_recent_cnt = 0; - component->nack_sent_log_ts = now; + handle->handle_id, medium->nack_sent_recent_cnt, video ? "video" : "audio", vindex); + medium->nack_sent_recent_cnt = 0; + medium->nack_sent_log_ts = now; } - janus_mutex_unlock(&component->mutex); + janus_mutex_unlock(&medium->mutex); g_slist_free(nacks); nacks = NULL; } @@ -2800,12 +2725,12 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp } else if(janus_is_rtcp(buf, len)) { /* This is RTCP */ JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Got an RTCP packet\n", handle->handle_id); - if(janus_is_webrtc_encryption_enabled() && (!component->dtls || !component->dtls->srtp_valid || !component->dtls->srtp_in)) { + if(janus_is_webrtc_encryption_enabled() && (!pc->dtls || !pc->dtls->srtp_valid || !pc->dtls->srtp_in)) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Missing valid SRTP session (packet arrived too early?), skipping...\n", handle->handle_id); } else { int buflen = len; srtp_err_status_t res = janus_is_webrtc_encryption_enabled() ? - srtp_unprotect_rtcp(component->dtls->srtp_in, buf, &buflen) : srtp_err_status_ok; + srtp_unprotect_rtcp(pc->dtls->srtp_in, buf, &buflen) : srtp_err_status_ok; if(res != srtp_err_status_ok) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] SRTCP unprotect error: %s (len=%d-->%d)\n", handle->handle_id, janus_srtp_error_str(res), len, buflen); } else { @@ -2817,96 +2742,39 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp if(janus_rtcp_has_bye(buf, buflen)) { /* Note: we used to use this as a trigger to close the PeerConnection, but not anymore * Discussion here, https://groups.google.com/forum/#!topic/meetecho-janus/4XtfbYB7Jvc */ - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Got RTCP BYE on stream %u (component %u)\n", handle->handle_id, stream->stream_id, component->component_id); + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Got RTCP BYE on stream %u (component %u)\n", handle->handle_id, stream_id, component_id); } /* Is this audio or video? */ int video = 0, vindex = 0; - if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX)) { - janus_rtcp_swap_report_blocks(buf, buflen, stream->video_ssrc_rtx); - } /* Bundled streams, should we check the SSRCs? */ - if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AUDIO)) { - /* No audio has been negotiated, definitely video */ - JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Incoming RTCP, bundling: this is video (no audio has been negotiated)\n", handle->handle_id); - video = 1; - /* Check the remote SSRC, compare it to what we have: in case - * we're simulcasting, let's compare to the other SSRCs too */ - guint32 rtcp_ssrc = janus_rtcp_get_sender_ssrc(buf, buflen); - if(rtcp_ssrc == 0) { - /* No SSRC, maybe an empty RR? */ + guint32 rtcp_ssrc = janus_rtcp_get_sender_ssrc(buf, buflen); + janus_ice_peerconnection_medium *medium = g_hash_table_lookup(pc->media_byssrc, GINT_TO_POINTER(rtcp_ssrc)); + if(medium == NULL) { + /* We don't know the remote SSRC: this can happen for recvonly clients + * (see https://groups.google.com/forum/#!topic/discuss-webrtc/5yuZjV7lkNc) + * Check the local SSRC, compare it to what we have */ + rtcp_ssrc = janus_rtcp_get_receiver_ssrc(buf, buflen); + medium = g_hash_table_lookup(pc->media_byssrc, GINT_TO_POINTER(rtcp_ssrc)); + if(medium == NULL) { + if(rtcp_ssrc > 0) { + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Unknown SSRC, dropping RTCP packet (SSRC %"SCNu32")...\n", + handle->handle_id, rtcp_ssrc); + } return; } - if(stream->video_ssrc_peer[0] && rtcp_ssrc == stream->video_ssrc_peer[0]) { - vindex = 0; - } else if(stream->video_ssrc_peer[1] && rtcp_ssrc == stream->video_ssrc_peer[1]) { + } + video = (medium->type == JANUS_MEDIA_VIDEO); + /* If this is video, check if this is simulcast */ + if(video) { + if(medium->ssrc_peer[1] == rtcp_ssrc) { vindex = 1; - } else if(stream->video_ssrc_peer[2] && rtcp_ssrc == stream->video_ssrc_peer[2]) { + } else if(medium->ssrc_peer[2] == rtcp_ssrc) { vindex = 2; - } else { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Dropping RTCP packet with unknown SSRC (%"SCNu32")\n", handle->handle_id, rtcp_ssrc); - return; - } - JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Incoming RTCP, bundling: this is %s (remote SSRC: video=%"SCNu32" #%d, got %"SCNu32")\n", - handle->handle_id, video ? "video" : "audio", stream->video_ssrc_peer[vindex], vindex, rtcp_ssrc); - } else if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO)) { - /* No video has been negotiated, definitely audio */ - JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Incoming RTCP, bundling: this is audio (no video has been negotiated)\n", handle->handle_id); - video = 0; - } else { - if(stream->audio_ssrc_peer == 0 || stream->video_ssrc_peer[0] == 0) { - /* We don't know the remote SSRC: this can happen for recvonly clients - * (see https://groups.google.com/forum/#!topic/discuss-webrtc/5yuZjV7lkNc) - * Check the local SSRC, compare it to what we have */ - guint32 rtcp_ssrc = janus_rtcp_get_receiver_ssrc(buf, buflen); - if(rtcp_ssrc == 0) { - /* No SSRC, maybe an empty RR? */ - return; - } - if(rtcp_ssrc == stream->audio_ssrc) { - video = 0; - } else if(rtcp_ssrc == stream->video_ssrc) { - video = 1; - } else if(rtcp_ssrc == stream->video_ssrc_rtx) { - /* rtx SSRC, we don't care */ - return; - } else if(janus_rtcp_has_fir(buf, buflen) || janus_rtcp_has_pli(buf, buflen) || janus_rtcp_get_remb(buf, buflen)) { - /* Mh, no SR or RR? Try checking if there's any FIR, PLI or REMB */ - video = 1; - } else { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Dropping RTCP packet with unknown SSRC (%"SCNu32")\n", handle->handle_id, rtcp_ssrc); - return; - } - JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Incoming RTCP, bundling: this is %s (local SSRC: video=%"SCNu32", audio=%"SCNu32", got %"SCNu32")\n", - handle->handle_id, video ? "video" : "audio", stream->video_ssrc, stream->audio_ssrc, rtcp_ssrc); - } else { - /* Check the remote SSRC, compare it to what we have: in case - * we're simulcasting, let's compare to the other SSRCs too */ - guint32 rtcp_ssrc = janus_rtcp_get_sender_ssrc(buf, buflen); - if(rtcp_ssrc == 0) { - /* No SSRC, maybe an empty RR? */ - return; - } - if(rtcp_ssrc == stream->audio_ssrc_peer) { - video = 0; - } else if(rtcp_ssrc == stream->video_ssrc_peer[0]) { - video = 1; - } else if(stream->video_ssrc_peer[1] && rtcp_ssrc == stream->video_ssrc_peer[1]) { - video = 1; - vindex = 1; - } else if(stream->video_ssrc_peer[2] && rtcp_ssrc == stream->video_ssrc_peer[2]) { - video = 1; - vindex = 2; - } else { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Dropping RTCP packet with unknown SSRC (%"SCNu32")\n", handle->handle_id, rtcp_ssrc); - return; - } - JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Incoming RTCP, bundling: this is %s (remote SSRC: video=%"SCNu32" #%d, audio=%"SCNu32", got %"SCNu32")\n", - handle->handle_id, video ? "video" : "audio", stream->video_ssrc_peer[vindex], vindex, stream->audio_ssrc_peer, rtcp_ssrc); } } /* Let's process this RTCP (compound?) packet, and update the RTCP context for this stream in case */ - rtcp_context *rtcp_ctx = video ? stream->video_rtcp_ctx[vindex] : stream->audio_rtcp_ctx; + rtcp_context *rtcp_ctx = medium->rtcp_ctx[vindex]; uint32_t rtt = rtcp_ctx ? rtcp_ctx->rtt : 0; if(janus_rtcp_parse(rtcp_ctx, buf, buflen) < 0) { /* Drop the packet if the parsing function returns with an error */ @@ -2914,20 +2782,19 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp } if(rtcp_ctx && rtcp_ctx->rtt != rtt) { /* Check the current RTT, to see if we need to update the size of the queue: we take - * the highest RTT (audio or video) and add 100ms just to be conservative */ - uint32_t audio_rtt = janus_rtcp_context_get_rtt(stream->audio_rtcp_ctx), - video_rtt = janus_rtcp_context_get_rtt(stream->video_rtcp_ctx[0]); - uint16_t nack_queue_ms = (audio_rtt > video_rtt ? audio_rtt : video_rtt) + 100; + * the RTT (should we include all media?) and add 100ms just to be conservative */ + uint32_t medium_rtt = janus_rtcp_context_get_rtt(medium->rtcp_ctx[0]); + uint16_t nack_queue_ms = medium_rtt + 100; if(nack_queue_ms > DEFAULT_MAX_NACK_QUEUE) nack_queue_ms = DEFAULT_MAX_NACK_QUEUE; else if(nack_queue_ms < min_nack_queue) nack_queue_ms = min_nack_queue; - uint16_t mavg = rtt ? ((7*stream->nack_queue_ms + nack_queue_ms)/8) : nack_queue_ms; + uint16_t mavg = rtt ? ((7*medium->nack_queue_ms + nack_queue_ms)/8) : nack_queue_ms; if(mavg > DEFAULT_MAX_NACK_QUEUE) mavg = DEFAULT_MAX_NACK_QUEUE; else if(mavg < min_nack_queue) mavg = min_nack_queue; - stream->nack_queue_ms = mavg; + medium->nack_queue_ms = mavg; } JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Got %s RTCP (%d bytes)\n", handle->handle_id, video ? "video" : "audio", buflen); @@ -2935,13 +2802,13 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp gint64 now = janus_get_monotonic_time(); GSList *nacks = janus_rtcp_get_nacks(buf, buflen); guint nacks_count = g_slist_length(nacks); - if(nacks_count && ((!video && component->do_audio_nacks) || (video && component->do_video_nacks))) { + if(nacks_count && medium->do_nacks) { /* Handle NACK */ JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Just got some NACKS (%d) we should handle...\n", handle->handle_id, nacks_count); - GHashTable *retransmit_seqs = (video ? component->video_retransmit_seqs : component->audio_retransmit_seqs); + GHashTable *retransmit_seqs = medium->retransmit_seqs; GSList *list = (retransmit_seqs != NULL ? nacks : NULL); int retransmits_cnt = 0; - janus_mutex_lock(&component->mutex); + janus_mutex_lock(&medium->mutex); while(list) { unsigned int seqnr = GPOINTER_TO_UINT(list->data); JANUS_LOG(LOG_DBG, "[%"SCNu64"] >> %u\n", handle->handle_id, seqnr); @@ -2963,6 +2830,7 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp retransmits_cnt++; /* Enqueue it */ janus_ice_queued_packet *pkt = g_malloc(sizeof(janus_ice_queued_packet)); + pkt->mindex = medium->mindex; pkt->data = g_malloc(p->length+SRTP_MAX_TAG_LEN); memcpy(pkt->data, p->data, p->length); pkt->length = p->length; @@ -2980,10 +2848,10 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp /* We are: overwrite the RTP header (which means we'll need a new SRTP encrypt) */ pkt->encrypted = FALSE; janus_rtp_header *header = (janus_rtp_header *)pkt->data; - header->type = stream->video_rtx_payload_type; - header->ssrc = htonl(stream->video_ssrc_rtx); - component->rtx_seq_number++; - header->seq_number = htons(component->rtx_seq_number); + header->type = medium->rtx_payload_type; + header->ssrc = htonl(medium->ssrc_rtx); + medium->rtx_seq_number++; + header->seq_number = htons(medium->rtx_seq_number); } if(handle->queued_packets != NULL) { #if GLIB_CHECK_VERSION(2, 46, 0) @@ -2999,34 +2867,30 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp } list = list->next; } - component->retransmit_recent_cnt += retransmits_cnt; + medium->retransmit_recent_cnt += retransmits_cnt; /* FIXME Remove the NACK compound packet, we've handled it */ buflen = janus_rtcp_remove_nacks(buf, buflen); /* Update stats */ - if(video) { - component->in_stats.video[vindex].nacks += nacks_count; - } else { - component->in_stats.audio.nacks += nacks_count; - } - janus_mutex_unlock(&component->mutex); + medium->in_stats.info[vindex].nacks += nacks_count; + janus_mutex_unlock(&medium->mutex); g_slist_free(nacks); nacks = NULL; } - if(component->retransmit_recent_cnt && - now - component->retransmit_log_ts > 5*G_USEC_PER_SEC) { + if(medium->retransmit_recent_cnt && + now - medium->retransmit_log_ts > 5*G_USEC_PER_SEC) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Retransmitted %u packets due to NACK (%s stream #%d)\n", - handle->handle_id, component->retransmit_recent_cnt, video ? "video" : "audio", vindex); - component->retransmit_recent_cnt = 0; - component->retransmit_log_ts = now; + handle->handle_id, medium->retransmit_recent_cnt, video ? "video" : "audio", vindex); + medium->retransmit_recent_cnt = 0; + medium->retransmit_log_ts = now; } /* Fix packet data for RTCP SR and RTCP RR */ - janus_rtp_switching_context *rtp_ctx = video ? &stream->rtp_ctx[vindex] : &stream->rtp_ctx[0]; - uint32_t base_ts = video ? rtp_ctx->v_base_ts : rtp_ctx->a_base_ts; - uint32_t base_ts_prev = video ? rtp_ctx->v_base_ts_prev : rtp_ctx->a_base_ts_prev; - uint32_t ssrc_peer = video ? stream->video_ssrc_peer_orig[vindex] : stream->audio_ssrc_peer_orig; - uint32_t ssrc_local = video ? stream->video_ssrc : stream->audio_ssrc; - uint32_t ssrc_expected = video ? rtp_ctx->v_last_ssrc : rtp_ctx->a_last_ssrc; + janus_rtp_switching_context *rtp_ctx = &medium->rtp_ctx[vindex]; + uint32_t base_ts = rtp_ctx->base_ts; + uint32_t base_ts_prev = rtp_ctx->base_ts_prev; + uint32_t ssrc_peer = medium->ssrc_peer_orig[vindex]; + uint32_t ssrc_local = medium->ssrc; + uint32_t ssrc_expected = rtp_ctx->last_ssrc; if (janus_rtcp_fix_report_data(buf, buflen, base_ts, base_ts_prev, ssrc_peer, ssrc_local, ssrc_expected, video) < 0) { /* Drop packet in case of parsing error or SSRC different from the one expected. */ /* This might happen at the very beginning of the communication or early after */ @@ -3034,7 +2898,7 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp return; } - janus_plugin_rtcp rtcp = { .video = video, .buffer = buf, .length = buflen }; + janus_plugin_rtcp rtcp = { .mindex = medium->mindex, .video = video, .buffer = buf, .length = buflen }; janus_plugin *plugin = (janus_plugin *)handle->app; if(plugin && plugin->incoming_rtcp && handle->app_handle && !g_atomic_int_get(&handle->app_handle->stopped) && @@ -3045,11 +2909,11 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp return; } else { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Not RTP and not RTCP... may these be data channels?\n", handle->handle_id); - janus_dtls_srtp_incoming_msg(component->dtls, buf, len); + janus_dtls_srtp_incoming_msg(pc->dtls, buf, len); /* Update stats (only overall data received) */ if(len > 0) { - component->in_stats.data.packets++; - component->in_stats.data.bytes += len; + pc->dtls_in_stats.info[0].packets++; + pc->dtls_in_stats.info[0].bytes += len; } return; } @@ -3071,12 +2935,9 @@ void janus_ice_incoming_data(janus_ice_handle *handle, char *label, char *protoc static int janus_ice_candidate_to_string(janus_ice_handle *handle, NiceCandidate *c, char *buffer, int buflen, gboolean log_candidate, gboolean force_private, guint public_ip_index) { if(!handle || !handle->agent || !c || !buffer || buflen < 1) return -1; - janus_ice_stream *stream = handle->stream; - if(!stream) + janus_ice_peerconnection *pc = handle->pc; + if(!pc) return -2; - janus_ice_component *component = stream->component; - if(!component) - return -3; char *host_ip = NULL; gboolean ipv6 = (nice_address_ip_version(&c->addr) == 6); if(nat_1_1_enabled && !force_private) { @@ -3218,14 +3079,14 @@ static int janus_ice_candidate_to_string(janus_ice_handle *handle, NiceCandidate JANUS_LOG(LOG_VERB, "[%"SCNu64"] %s\n", handle->handle_id, buffer); if(log_candidate) { /* Save for the summary, in case we need it */ - component->local_candidates = g_slist_append(component->local_candidates, g_strdup(buffer)); + pc->local_candidates = g_slist_append(pc->local_candidates, g_strdup(buffer)); /* Notify event handlers */ if(janus_events_is_enabled()) { janus_session *session = (janus_session *)handle->session; json_t *info = json_object(); json_object_set_new(info, "local-candidate", json_string(buffer)); - json_object_set_new(info, "stream_id", json_integer(stream->stream_id)); - json_object_set_new(info, "component_id", json_integer(component->component_id)); + json_object_set_new(info, "stream_id", json_integer(pc->stream_id)); + json_object_set_new(info, "component_id", json_integer(pc->component_id)); janus_events_notify_handlers(JANUS_EVENT_TYPE_WEBRTC, JANUS_EVENT_SUBTYPE_WEBRTC_LCAND, session->session_id, handle->handle_id, handle->opaque_id, info); } @@ -3236,23 +3097,18 @@ static int janus_ice_candidate_to_string(janus_ice_handle *handle, NiceCandidate void janus_ice_candidates_to_sdp(janus_ice_handle *handle, janus_sdp_mline *mline, guint stream_id, guint component_id) { if(!handle || !handle->agent || !mline) return; - janus_ice_stream *stream = handle->stream; - if(!stream || stream->stream_id != stream_id) { + janus_ice_peerconnection *pc = handle->pc; + if(!pc || pc->stream_id != stream_id) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] No stream %d??\n", handle->handle_id, stream_id); return; } - janus_ice_component *component = stream->component; - if(!component || component->component_id != component_id) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] No component %d in stream %d??\n", handle->handle_id, component_id, stream_id); - return; - } NiceAgent *agent = handle->agent; /* Iterate on all */ gchar buffer[200]; GSList *candidates, *i; candidates = nice_agent_get_local_candidates (agent, stream_id, component_id); JANUS_LOG(LOG_VERB, "[%"SCNu64"] We have %d candidates for Stream #%d, Component #%d\n", handle->handle_id, g_slist_length(candidates), stream_id, component_id); - gboolean log_candidates = (component->local_candidates == NULL); + gboolean log_candidates = (pc->local_candidates == NULL); for(i = candidates; i; i = i->next) { NiceCandidate *c = (NiceCandidate *) i->data; gboolean ipv6 = (nice_address_ip_version(&c->addr) == 6); @@ -3309,21 +3165,16 @@ void janus_ice_add_remote_candidate(janus_ice_handle *handle, NiceCandidate *c) void janus_ice_setup_remote_candidates(janus_ice_handle *handle, guint stream_id, guint component_id) { if(!handle || !handle->agent) return; - janus_ice_stream *stream = handle->stream; - if(!stream || stream->stream_id != stream_id) { + janus_ice_peerconnection *pc = handle->pc; + if(!pc || pc->stream_id != stream_id) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] No such stream %d: cannot setup remote candidates for component %d\n", handle->handle_id, stream_id, component_id); return; } - janus_ice_component *component = stream->component; - if(!component || component->component_id != component_id) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] No such component %d in stream %d: cannot setup remote candidates\n", handle->handle_id, component_id, stream_id); - return; - } - if(component->process_started) { + if(pc->process_started) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Component %d in stream %d has already been set up\n", handle->handle_id, component_id, stream_id); return; } - if(!component->candidates || !component->candidates->data) { + if(!pc->candidates || !pc->candidates->data) { if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_TRICKLE) || janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALL_TRICKLES)) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] No remote candidates for component %d in stream %d: was the remote SDP parsed?\n", handle->handle_id, component_id, stream_id); @@ -3331,10 +3182,10 @@ void janus_ice_setup_remote_candidates(janus_ice_handle *handle, guint stream_id return; } JANUS_LOG(LOG_VERB, "[%"SCNu64"] ## Setting remote candidates: stream %d, component %d (%u in the list)\n", - handle->handle_id, stream_id, component_id, g_slist_length(component->candidates)); + handle->handle_id, stream_id, component_id, g_slist_length(pc->candidates)); /* Add all candidates */ NiceCandidate *c = NULL; - GSList *gsc = component->candidates; + GSList *gsc = pc->candidates; while(gsc) { c = (NiceCandidate *) gsc->data; JANUS_LOG(LOG_VERB, "[%"SCNu64"] Queueing candidate %p (startup)\n", handle->handle_id, c); @@ -3350,17 +3201,17 @@ void janus_ice_setup_remote_candidates(janus_ice_handle *handle, guint stream_id #endif g_main_context_wakeup(handle->mainctx); } - component->process_started = TRUE; + pc->process_started = TRUE; } -int janus_ice_setup_local(janus_ice_handle *handle, int offer, int audio, int video, int data, int trickle) { +int janus_ice_setup_local(janus_ice_handle *handle, gboolean offer, gboolean trickle) { if(!handle) return -1; if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AGENT)) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Agent already exists?\n", handle->handle_id); return -2; } - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Setting ICE locally: got %s (%d audios, %d videos)\n", handle->handle_id, offer ? "OFFER" : "ANSWER", audio, video); + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Setting ICE locally: got %s\n", handle->handle_id, offer ? "OFFER" : "ANSWER"); g_atomic_int_set(&handle->closepc, 0); janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AGENT); janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_START); @@ -3374,22 +3225,6 @@ int janus_ice_setup_local(janus_ice_handle *handle, int offer, int audio, int vi janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ICE_RESTART); janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RESEND_TRICKLES); - /* Note: in case this is not an OFFER, we don't know whether any medium are supported on the other side or not yet */ - if(audio) { - janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AUDIO); - } else { - janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AUDIO); - } - if(video) { - janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO); - } else { - janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO); - } - if(data) { - janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_DATA_CHANNELS); - } else { - janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_DATA_CHANNELS); - } /* Note: in case this is not an OFFER, we don't know whether ICE trickling is supported on the other side or not yet */ if(offer && trickle) { janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_TRICKLE); @@ -3537,61 +3372,21 @@ int janus_ice_setup_local(janus_ice_handle *handle, int offer, int audio, int vi handle->cdone = 0; handle->stream_id = 0; - /* If this is our first offer, let's generate some mids */ - if(!offer) { - if(audio) { - if(handle->audio_mid == NULL) - handle->audio_mid = g_strdup("audio"); - if(handle->stream_mid == NULL) - handle->stream_mid = handle->audio_mid; - } - if(video) { - if(handle->video_mid == NULL) - handle->video_mid = g_strdup("video"); - if(handle->stream_mid == NULL) - handle->stream_mid = handle->video_mid; - } -#ifdef HAVE_SCTP - if(data) { - if(handle->data_mid == NULL) - handle->data_mid = g_strdup("data"); - if(handle->stream_mid == NULL) - handle->stream_mid = handle->data_mid; - } -#endif - } - /* Now create an ICE stream for all the media we'll handle */ + /* Now create a ICE stream for all the media we'll handle */ handle->stream_id = nice_agent_add_stream(handle->agent, 1); if(dscp_ef > 0) { /* A DSCP value was configured, shift it and pass it to libnice as a TOS */ nice_agent_set_stream_tos(handle->agent, handle->stream_id, dscp_ef << 2); } - janus_ice_stream *stream = g_malloc0(sizeof(janus_ice_stream)); - janus_refcount_init(&stream->ref, janus_ice_stream_free); + /* Create the PeerConnection object */ + janus_ice_peerconnection *pc = g_malloc0(sizeof(janus_ice_peerconnection)); + janus_refcount_init(&pc->ref, janus_ice_peerconnection_free); janus_refcount_increase(&handle->ref); - stream->stream_id = handle->stream_id; - stream->handle = handle; - stream->audio_payload_type = -1; - stream->video_payload_type = -1; - stream->video_rtx_payload_type = -1; - stream->nack_queue_ms = min_nack_queue; + pc->stream_id = handle->stream_id; + pc->handle = handle; /* FIXME By default, if we're being called we're DTLS clients, but this may be changed by ICE... */ - stream->dtls_role = offer ? JANUS_DTLS_ROLE_CLIENT : JANUS_DTLS_ROLE_ACTPASS; - if(audio) { - stream->audio_ssrc = janus_random_uint32(); /* FIXME Should we look for conflicts? */ - stream->audio_rtcp_ctx = g_malloc0(sizeof(janus_rtcp_context)); - stream->audio_rtcp_ctx->tb = 48000; /* May change later */ - } - if(video) { - stream->video_ssrc = janus_random_uint32(); /* FIXME Should we look for conflicts? */ - if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX)) { - /* Create an SSRC for RFC4588 as well */ - stream->video_ssrc_rtx = janus_random_uint32(); /* FIXME Should we look for conflicts? */ - } - stream->video_rtcp_ctx[0] = g_malloc0(sizeof(janus_rtcp_context)); - stream->video_rtcp_ctx[0]->tb = 90000; - } - janus_mutex_init(&stream->mutex); + pc->dtls_role = offer ? JANUS_DTLS_ROLE_CLIENT : JANUS_DTLS_ROLE_ACTPASS; + janus_mutex_init(&pc->mutex); if(!have_turnrest_credentials) { /* No TURN REST API server and credentials, any static ones? */ if(janus_turn_server != NULL) { @@ -3621,15 +3416,13 @@ int janus_ice_setup_local(janus_ice_handle *handle, int offer, int audio, int vi } #endif } - handle->stream = stream; - janus_ice_component *component = g_malloc0(sizeof(janus_ice_component)); - janus_refcount_init(&component->ref, janus_ice_component_free); - component->stream = stream; - janus_refcount_increase(&stream->ref); - component->stream_id = stream->stream_id; - component->component_id = 1; - janus_mutex_init(&component->mutex); - stream->component = component; + handle->pc = pc; + /* Create the media instances we need */ + pc->media = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_ice_peerconnection_medium_destroy); + pc->media_byssrc = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_ice_peerconnection_medium_dereference); + pc->media_bymid = g_hash_table_new_full(g_str_hash, g_str_equal, + (GDestroyNotify)g_free, (GDestroyNotify)janus_ice_peerconnection_medium_dereference); + pc->media_bytype = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_ice_peerconnection_medium_dereference); #ifdef HAVE_PORTRANGE /* FIXME: libnice supports this since 0.1.0, but the 0.1.3 on Fedora fails with an undefined reference! */ nice_agent_set_port_range(handle->agent, handle->stream_id, 1, rtp_range_min, rtp_range_max); @@ -3642,7 +3435,7 @@ int janus_ice_setup_local(janus_ice_handle *handle, int offer, int audio, int vi return -1; } nice_agent_attach_recv(handle->agent, handle->stream_id, 1, g_main_loop_get_context(handle->mainloop), - janus_ice_cb_nice_recv, component); + janus_ice_cb_nice_recv, pc); #ifdef HAVE_TURNRESTAPI if(turnrest_credentials != NULL) { janus_turnrest_response_destroy(turnrest_credentials); @@ -3650,15 +3443,15 @@ int janus_ice_setup_local(janus_ice_handle *handle, int offer, int audio, int vi } #endif /* Create DTLS-SRTP context, at last */ - component->dtls = janus_dtls_srtp_create(component, stream->dtls_role); - if(!component->dtls) { + pc->dtls = janus_dtls_srtp_create(pc, pc->dtls_role); + if(!pc->dtls) { /* FIXME We should clear some resources... */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error creating DTLS-SRTP stack...\n", handle->handle_id); janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AGENT); janus_ice_webrtc_hangup(handle, "DTLS-SRTP stack error"); return -1; } - janus_refcount_increase(&component->dtls->ref); + janus_refcount_increase(&pc->dtls->ref); /* If we're doing full-tricke, start gathering asynchronously */ if(janus_full_trickle_enabled) { #if GLIB_CHECK_VERSION(2, 46, 0) @@ -3672,7 +3465,7 @@ int janus_ice_setup_local(janus_ice_handle *handle, int offer, int audio, int vi } void janus_ice_restart(janus_ice_handle *handle) { - if(!handle || !handle->agent || !handle->stream) + if(!handle || !handle->agent || !handle->pc) return; /* Restart ICE */ if(nice_agent_restart(handle->agent) == FALSE) { @@ -3685,19 +3478,16 @@ void janus_ice_resend_trickles(janus_ice_handle *handle) { if(!handle || !handle->agent) return; janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RESEND_TRICKLES); - janus_ice_stream *stream = handle->stream; - if(!stream) - return; - janus_ice_component *component = stream->component; - if(!component) + janus_ice_peerconnection *pc = handle->pc; + if(!pc) return; NiceAgent *agent = handle->agent; /* Iterate on all existing local candidates */ gchar buffer[200]; GSList *candidates, *i; - candidates = nice_agent_get_local_candidates (agent, stream->stream_id, component->component_id); + candidates = nice_agent_get_local_candidates (agent, pc->stream_id, pc->component_id); JANUS_LOG(LOG_VERB, "[%"SCNu64"] We have %d candidates for Stream #%d, Component #%d\n", - handle->handle_id, g_slist_length(candidates), stream->stream_id, component->component_id); + handle->handle_id, g_slist_length(candidates), pc->stream_id, pc->component_id); for(i = candidates; i; i = i->next) { NiceCandidate *c = (NiceCandidate *) i->data; if(c->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) { @@ -3730,34 +3520,35 @@ static gint rtcp_transport_wide_cc_stats_comparator(gconstpointer item1, gconstp } static gboolean janus_ice_outgoing_transport_wide_cc_feedback(gpointer user_data) { janus_ice_handle *handle = (janus_ice_handle *)user_data; - janus_ice_stream *stream = handle->stream; - if(stream && stream->video_recv && stream->do_transport_wide_cc) { + janus_ice_peerconnection *pc = handle->pc; + janus_ice_peerconnection_medium *medium = pc ? g_hash_table_lookup(pc->media_bytype, GINT_TO_POINTER(JANUS_MEDIA_VIDEO)) : NULL; + if(pc && pc->do_transport_wide_cc && medium) { /* Create a transport wide feedback message */ size_t size = 1300; char rtcpbuf[1300]; /* Order packet list */ - stream->transport_wide_received_seq_nums = g_slist_sort(stream->transport_wide_received_seq_nums, + pc->transport_wide_received_seq_nums = g_slist_sort(pc->transport_wide_received_seq_nums, rtcp_transport_wide_cc_stats_comparator); /* Create full stats queue */ GQueue *packets = g_queue_new(); /* For all packets */ GSList *it = NULL; - for(it = stream->transport_wide_received_seq_nums; it; it = it->next) { + for(it = pc->transport_wide_received_seq_nums; it; it = it->next) { /* Get stat */ janus_rtcp_transport_wide_cc_stats *stats = (janus_rtcp_transport_wide_cc_stats *)it->data; /* Get transport seq */ guint32 transport_seq_num = stats->transport_seq_num; /* Check if it is an out of order */ - if(transport_seq_num < stream->transport_wide_cc_last_feedback_seq_num) { + if(transport_seq_num < pc->transport_wide_cc_last_feedback_seq_num) { /* Skip, it was already reported as lost */ g_free(stats); continue; } /* If not first */ - if(stream->transport_wide_cc_last_feedback_seq_num) { + if(pc->transport_wide_cc_last_feedback_seq_num) { /* For each lost */ guint32 i = 0; - for(i = stream->transport_wide_cc_last_feedback_seq_num+1; itransport_wide_cc_last_feedback_seq_num+1; itransport_wide_cc_last_feedback_seq_num = transport_seq_num; + pc->transport_wide_cc_last_feedback_seq_num = transport_seq_num; /* Add this one */ g_queue_push_tail(packets, stats); } /* Free and reset stats list */ - g_slist_free(stream->transport_wide_received_seq_nums); - stream->transport_wide_received_seq_nums = NULL; + g_slist_free(pc->transport_wide_received_seq_nums); + pc->transport_wide_received_seq_nums = NULL; /* Create and enqueue RTCP packets */ guint packets_len = 0; while((packets_len = g_queue_get_length(packets)) > 0) { @@ -3797,13 +3588,13 @@ static gboolean janus_ice_outgoing_transport_wide_cc_feedback(gpointer user_data packets_to_process = packets; } /* Get feedback packet count and increase it for next one */ - guint8 feedback_packet_count = stream->transport_wide_cc_feedback_count++; + guint8 feedback_packet_count = pc->transport_wide_cc_feedback_count++; /* Create RTCP packet */ int len = janus_rtcp_transport_wide_cc_feedback(rtcpbuf, size, - stream->video_ssrc, stream->video_ssrc_peer[0], feedback_packet_count, packets_to_process); + medium->ssrc, medium->ssrc_peer[0], feedback_packet_count, packets_to_process); /* Enqueue it, we'll send it later */ if(len > 0) { - janus_plugin_rtcp rtcp = { .video = TRUE, .buffer = rtcpbuf, .length = len }; + janus_plugin_rtcp rtcp = { .mindex = medium->mindex, .video = TRUE, .buffer = rtcpbuf, .length = len }; janus_ice_relay_rtcp_internal(handle, &rtcp, FALSE); } if(packets_to_process != packets) { @@ -3818,134 +3609,84 @@ static gboolean janus_ice_outgoing_transport_wide_cc_feedback(gpointer user_data static gboolean janus_ice_outgoing_rtcp_handle(gpointer user_data) { janus_ice_handle *handle = (janus_ice_handle *)user_data; - janus_ice_stream *stream = handle->stream; - /* Audio */ - if(stream && stream->audio_send && stream->component && stream->component->out_stats.audio.packets > 0) { - /* Create a SR/SDES compound */ - int srlen = 28; - int sdeslen = 16; - char rtcpbuf[srlen+sdeslen]; - memset(rtcpbuf, 0, sizeof(rtcpbuf)); - rtcp_sr *sr = (rtcp_sr *)&rtcpbuf; - sr->header.version = 2; - sr->header.type = RTCP_SR; - sr->header.rc = 0; - sr->header.length = htons((srlen/4)-1); - sr->ssrc = htonl(stream->audio_ssrc); - struct timeval tv; - gettimeofday(&tv, NULL); - uint32_t s = tv.tv_sec + 2208988800u; - uint32_t u = tv.tv_usec; - uint32_t f = (u << 12) + (u << 8) - ((u * 3650) >> 6); - sr->si.ntp_ts_msw = htonl(s); - sr->si.ntp_ts_lsw = htonl(f); - /* Compute an RTP timestamp coherent with the NTP one */ - rtcp_context *rtcp_ctx = stream->audio_rtcp_ctx; - if(rtcp_ctx == NULL) { - sr->si.rtp_ts = htonl(stream->audio_last_rtp_ts); /* FIXME */ - } else { - int64_t ntp = tv.tv_sec*G_USEC_PER_SEC + tv.tv_usec; - uint32_t rtp_ts = ((ntp-stream->audio_last_ntp_ts)*(rtcp_ctx->tb))/1000000 + stream->audio_last_rtp_ts; - sr->si.rtp_ts = htonl(rtp_ts); - } - sr->si.s_packets = htonl(stream->component->out_stats.audio.packets); - sr->si.s_octets = htonl(stream->component->out_stats.audio.bytes); - rtcp_sdes *sdes = (rtcp_sdes *)&rtcpbuf[28]; - janus_rtcp_sdes_cname((char *)sdes, sdeslen, "janus", 5); - sdes->chunk.ssrc = htonl(stream->audio_ssrc); - /* Enqueue it, we'll send it later */ - janus_plugin_rtcp rtcp = { .video = FALSE, .buffer = rtcpbuf, .length = srlen+sdeslen }; - janus_ice_relay_rtcp_internal(handle, &rtcp, FALSE); - /* Check if we detected too many losses, and send a slowlink event in case */ - guint lost = janus_rtcp_context_get_lost_all(rtcp_ctx, TRUE); - janus_slow_link_update(stream->component, handle, FALSE, TRUE, lost); - } - if(stream && stream->audio_recv) { - /* Create a RR too */ - int rrlen = 32; - char rtcpbuf[32]; - memset(rtcpbuf, 0, sizeof(rtcpbuf)); - rtcp_rr *rr = (rtcp_rr *)&rtcpbuf; - rr->header.version = 2; - rr->header.type = RTCP_RR; - rr->header.rc = 1; - rr->header.length = htons((rrlen/4)-1); - rr->ssrc = htonl(stream->audio_ssrc); - janus_rtcp_report_block(stream->audio_rtcp_ctx, &rr->rb[0]); - rr->rb[0].ssrc = htonl(stream->audio_ssrc_peer); - /* Enqueue it, we'll send it later */ - janus_plugin_rtcp rtcp = { .video = FALSE, .buffer = rtcpbuf, .length = 32 }; - janus_ice_relay_rtcp_internal(handle, &rtcp, FALSE); - /* Check if we detected too many losses, and send a slowlink event in case */ - guint lost = janus_rtcp_context_get_lost_all(stream->audio_rtcp_ctx, FALSE); - janus_slow_link_update(stream->component, handle, FALSE, FALSE, lost); - } - /* Now do the same for video */ - if(stream && stream->video_send && stream->component && stream->component->out_stats.video[0].packets > 0) { - /* Create a SR/SDES compound */ - int srlen = 28; - int sdeslen = 16; - char rtcpbuf[srlen+sdeslen]; - memset(rtcpbuf, 0, sizeof(rtcpbuf)); - rtcp_sr *sr = (rtcp_sr *)&rtcpbuf; - sr->header.version = 2; - sr->header.type = RTCP_SR; - sr->header.rc = 0; - sr->header.length = htons((srlen/4)-1); - sr->ssrc = htonl(stream->video_ssrc); - struct timeval tv; - gettimeofday(&tv, NULL); - uint32_t s = tv.tv_sec + 2208988800u; - uint32_t u = tv.tv_usec; - uint32_t f = (u << 12) + (u << 8) - ((u * 3650) >> 6); - sr->si.ntp_ts_msw = htonl(s); - sr->si.ntp_ts_lsw = htonl(f); - /* Compute an RTP timestamp coherent with the NTP one */ - rtcp_context *rtcp_ctx = stream->video_rtcp_ctx[0]; - if(rtcp_ctx == NULL) { - sr->si.rtp_ts = htonl(stream->video_last_rtp_ts); /* FIXME */ - } else { - int64_t ntp = tv.tv_sec*G_USEC_PER_SEC + tv.tv_usec; - uint32_t rtp_ts = ((ntp-stream->video_last_ntp_ts)*(rtcp_ctx->tb))/1000000 + stream->video_last_rtp_ts; - sr->si.rtp_ts = htonl(rtp_ts); + janus_ice_peerconnection *pc = handle->pc; + /* Iterate on all media */ + janus_ice_peerconnection_medium *medium = NULL; + uint mi=0; + for(mi=0; mimedia); mi++) { + medium = g_hash_table_lookup(pc->media, GUINT_TO_POINTER(mi)); + if(!medium || (medium->type != JANUS_MEDIA_AUDIO && medium->type != JANUS_MEDIA_VIDEO)) + continue; + if(medium->out_stats.info[0].packets > 0) { + /* Create a SR/SDES compound */ + int srlen = 28; + int sdeslen = 16; + char rtcpbuf[srlen+sdeslen]; + memset(rtcpbuf, 0, sizeof(rtcpbuf)); + rtcp_sr *sr = (rtcp_sr *)&rtcpbuf; + sr->header.version = 2; + sr->header.type = RTCP_SR; + sr->header.rc = 0; + sr->header.length = htons((srlen/4)-1); + sr->ssrc = htonl(medium->ssrc); + struct timeval tv; + gettimeofday(&tv, NULL); + uint32_t s = tv.tv_sec + 2208988800u; + uint32_t u = tv.tv_usec; + uint32_t f = (u << 12) + (u << 8) - ((u * 3650) >> 6); + sr->si.ntp_ts_msw = htonl(s); + sr->si.ntp_ts_lsw = htonl(f); + /* Compute an RTP timestamp coherent with the NTP one */ + rtcp_context *rtcp_ctx = medium->rtcp_ctx[0]; + if(rtcp_ctx == NULL) { + sr->si.rtp_ts = htonl(medium->last_rtp_ts); /* FIXME */ + } else { + int64_t ntp = tv.tv_sec*G_USEC_PER_SEC + tv.tv_usec; + uint32_t rtp_ts = ((ntp-medium->last_ntp_ts)*(rtcp_ctx->tb))/1000000 + medium->last_rtp_ts; + sr->si.rtp_ts = htonl(rtp_ts); + } + sr->si.s_packets = htonl(medium->out_stats.info[0].packets); + sr->si.s_octets = htonl(medium->out_stats.info[0].bytes); + rtcp_sdes *sdes = (rtcp_sdes *)&rtcpbuf[28]; + janus_rtcp_sdes_cname((char *)sdes, sdeslen, "janus", 5); + sdes->chunk.ssrc = htonl(medium->ssrc); + /* Enqueue it, we'll send it later */ + janus_plugin_rtcp rtcp = { .mindex = medium->mindex, + .video = (medium->type == JANUS_MEDIA_VIDEO), .buffer = rtcpbuf, .length = srlen+sdeslen }; + janus_ice_relay_rtcp_internal(handle, &rtcp, FALSE); + /* Check if we detected too many losses, and send a slowlink event in case */ + guint lost = janus_rtcp_context_get_lost_all(rtcp_ctx, TRUE); + janus_slow_link_update(medium, handle, TRUE, lost); } - sr->si.s_packets = htonl(stream->component->out_stats.video[0].packets); - sr->si.s_octets = htonl(stream->component->out_stats.video[0].bytes); - rtcp_sdes *sdes = (rtcp_sdes *)&rtcpbuf[28]; - janus_rtcp_sdes_cname((char *)sdes, sdeslen, "janus", 5); - sdes->chunk.ssrc = htonl(stream->video_ssrc); - /* Enqueue it, we'll send it later */ - janus_plugin_rtcp rtcp = { .video = TRUE, .buffer = rtcpbuf, .length = srlen+sdeslen }; - janus_ice_relay_rtcp_internal(handle, &rtcp, FALSE); - /* Check if we detected too many losses, and send a slowlink event in case */ - guint lost = janus_rtcp_context_get_lost_all(rtcp_ctx, TRUE); - janus_slow_link_update(stream->component, handle, TRUE, TRUE, lost); - } - if(stream && stream->video_recv) { - /* Create a RR too (for each SSRC, if we're simulcasting) */ - int vindex=0; - for(vindex=0; vindex<3; vindex++) { - if(stream->video_rtcp_ctx[vindex] && stream->video_rtcp_ctx[vindex]->rtp_recvd) { - /* Create a RR */ - int rrlen = 32; - char rtcpbuf[32]; - memset(rtcpbuf, 0, sizeof(rtcpbuf)); - rtcp_rr *rr = (rtcp_rr *)&rtcpbuf; - rr->header.version = 2; - rr->header.type = RTCP_RR; - rr->header.rc = 1; - rr->header.length = htons((rrlen/4)-1); - rr->ssrc = htonl(stream->video_ssrc); - janus_rtcp_report_block(stream->video_rtcp_ctx[vindex], &rr->rb[0]); - rr->rb[0].ssrc = htonl(stream->video_ssrc_peer[vindex]); - /* Enqueue it, we'll send it later */ - janus_plugin_rtcp rtcp = { .video = TRUE, .buffer = rtcpbuf, .length = 32 }; - janus_ice_relay_rtcp_internal(handle, &rtcp, FALSE); + if(medium->recv) { + /* Create a RR too (for each SSRC, if we're simulcasting) */ + int vindex=0; + for(vindex=0; vindex<3; vindex++) { + if(medium->rtcp_ctx[vindex] && medium->rtcp_ctx[vindex]->rtp_recvd) { + /* Create a RR */ + int rrlen = 32; + char rtcpbuf[32]; + memset(rtcpbuf, 0, sizeof(rtcpbuf)); + rtcp_rr *rr = (rtcp_rr *)&rtcpbuf; + rr->header.version = 2; + rr->header.type = RTCP_RR; + rr->header.rc = 1; + rr->header.length = htons((rrlen/4)-1); + rr->ssrc = htonl(medium->ssrc); + janus_rtcp_report_block(medium->rtcp_ctx[vindex], &rr->rb[0]); + rr->rb[0].ssrc = htonl(medium->ssrc_peer[vindex]); + /* Enqueue it, we'll send it later */ + janus_plugin_rtcp rtcp = { .mindex = medium->mindex, + .video = (medium->type == JANUS_MEDIA_VIDEO), .buffer = rtcpbuf, .length = 32 }; + janus_ice_relay_rtcp_internal(handle, &rtcp, FALSE); + if(vindex == 0) { + /* Check if we detected too many losses, and send a slowlink event in case */ + guint lost = janus_rtcp_context_get_lost_all(medium->rtcp_ctx[vindex], FALSE); + janus_slow_link_update(medium, handle, FALSE, lost); + } + } } } - /* Check if we detected too many losses, and send a slowlink event in case */ - guint lost = janus_rtcp_context_get_lost_all(stream->video_rtcp_ctx[0], FALSE); - janus_slow_link_update(stream->component, handle, TRUE, FALSE, lost); } if(twcc_period == 1000) { /* The Transport Wide CC feedback period is 1s as well, send it here */ @@ -3960,134 +3701,87 @@ static gboolean janus_ice_outgoing_stats_handle(gpointer user_data) { janus_session *session = (janus_session *)handle->session; gint64 now = janus_get_monotonic_time(); /* Reset the last second counters if too much time passed with no data in or out */ - janus_ice_stream *stream = handle->stream; - if(stream == NULL || stream->component == NULL) + janus_ice_peerconnection *pc = handle->pc; + if(pc == NULL) return G_SOURCE_CONTINUE; - janus_ice_component *component = stream->component; - /* Audio */ - gint64 last = component->in_stats.audio.updated; - if(last && now > last && now-last >= 2*G_USEC_PER_SEC && component->in_stats.audio.bytes_lastsec_temp > 0) { - component->in_stats.audio.bytes_lastsec = 0; - component->in_stats.audio.bytes_lastsec_temp = 0; - } - last = component->out_stats.audio.updated; - if(last && now > last && now-last >= 2*G_USEC_PER_SEC && component->out_stats.audio.bytes_lastsec_temp > 0) { - component->out_stats.audio.bytes_lastsec = 0; - component->out_stats.audio.bytes_lastsec_temp = 0; - } - /* Video */ - int vindex = 0; - for(vindex=0; vindex < 3; vindex++) { - gint64 last = component->in_stats.video[vindex].updated; - if(last && now > last && now-last >= 2*G_USEC_PER_SEC && component->in_stats.video[vindex].bytes_lastsec_temp > 0) { - component->in_stats.video[vindex].bytes_lastsec = 0; - component->in_stats.video[vindex].bytes_lastsec_temp = 0; - } - last = component->out_stats.video[vindex].updated; - if(last && now > last && now-last >= 2*G_USEC_PER_SEC && component->out_stats.video[vindex].bytes_lastsec_temp > 0) { - component->out_stats.video[vindex].bytes_lastsec = 0; - component->out_stats.video[vindex].bytes_lastsec_temp = 0; - } - } - /* Now let's see if we need to notify the user about no incoming audio or video */ - if(no_media_timer > 0 && component->dtls && component->dtls->dtls_connected > 0 && (now - component->dtls->dtls_connected >= G_USEC_PER_SEC)) { - /* Audio */ - gint64 last = component->in_stats.audio.updated; - if(!component->in_stats.audio.notified_lastsec && last && - !component->in_stats.audio.bytes_lastsec && !component->in_stats.audio.bytes_lastsec_temp && - now-last >= (gint64)no_media_timer*G_USEC_PER_SEC) { - /* We missed more than no_second_timer seconds of audio! */ - component->in_stats.audio.notified_lastsec = TRUE; - JANUS_LOG(LOG_WARN, "[%"SCNu64"] Didn't receive audio for more than %d seconds...\n", handle->handle_id, no_media_timer); - janus_ice_notify_media(handle, FALSE, FALSE); - } - /* Video */ - last = component->in_stats.video[0].updated; - if(!component->in_stats.video[0].notified_lastsec && last && - !component->in_stats.video[0].bytes_lastsec && !component->in_stats.video[0].bytes_lastsec_temp && - now-last >= (gint64)no_media_timer*G_USEC_PER_SEC) { - /* We missed more than no_second_timer seconds of video! */ - component->in_stats.video[0].notified_lastsec = TRUE; - JANUS_LOG(LOG_WARN, "[%"SCNu64"] Didn't receive video for more than a second...\n", handle->handle_id); - janus_ice_notify_media(handle, TRUE, FALSE); + /* Iterate on all media */ + janus_ice_peerconnection_medium *medium = NULL; + uint mi=0; + for(mi=0; mimedia); mi++) { + medium = g_hash_table_lookup(pc->media, GUINT_TO_POINTER(mi)); + if(!medium) + continue; + int vindex = 0; + for(vindex=0; vindex < 3; vindex++) { + gint64 last = medium->in_stats.info[vindex].updated; + if(last && now > last && now-last >= 2*G_USEC_PER_SEC && medium->in_stats.info[vindex].bytes_lastsec_temp > 0) { + medium->in_stats.info[vindex].bytes_lastsec = 0; + medium->in_stats.info[vindex].bytes_lastsec_temp = 0; + } + last = medium->out_stats.info[vindex].updated; + if(last && now > last && now-last >= 2*G_USEC_PER_SEC && medium->out_stats.info[vindex].bytes_lastsec_temp > 0) { + medium->out_stats.info[vindex].bytes_lastsec = 0; + medium->out_stats.info[vindex].bytes_lastsec_temp = 0; + } } - } - /* We also send live stats to event handlers every tot-seconds (configurable) */ - handle->last_event_stats++; - if(janus_ice_event_stats_period > 0 && handle->last_event_stats >= janus_ice_event_stats_period) { - handle->last_event_stats = 0; - /* Audio */ - if(janus_events_is_enabled() && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AUDIO)) { - if(stream && stream->audio_rtcp_ctx) { - json_t *info = json_object(); - json_object_set_new(info, "media", json_string("audio")); - json_object_set_new(info, "base", json_integer(stream->audio_rtcp_ctx->tb)); - json_object_set_new(info, "rtt", json_integer(janus_rtcp_context_get_rtt(stream->audio_rtcp_ctx))); - json_object_set_new(info, "lost", json_integer(janus_rtcp_context_get_lost_all(stream->audio_rtcp_ctx, FALSE))); - json_object_set_new(info, "lost-by-remote", json_integer(janus_rtcp_context_get_lost_all(stream->audio_rtcp_ctx, TRUE))); - json_object_set_new(info, "jitter-local", json_integer(janus_rtcp_context_get_jitter(stream->audio_rtcp_ctx, FALSE))); - json_object_set_new(info, "jitter-remote", json_integer(janus_rtcp_context_get_jitter(stream->audio_rtcp_ctx, TRUE))); - json_object_set_new(info, "in-link-quality", json_integer(janus_rtcp_context_get_in_link_quality(stream->audio_rtcp_ctx))); - json_object_set_new(info, "in-media-link-quality", json_integer(janus_rtcp_context_get_in_media_link_quality(stream->audio_rtcp_ctx))); - json_object_set_new(info, "out-link-quality", json_integer(janus_rtcp_context_get_out_link_quality(stream->audio_rtcp_ctx))); - json_object_set_new(info, "out-media-link-quality", json_integer(janus_rtcp_context_get_out_media_link_quality(stream->audio_rtcp_ctx))); - if(stream->component) { - json_object_set_new(info, "packets-received", json_integer(stream->component->in_stats.audio.packets)); - json_object_set_new(info, "packets-sent", json_integer(stream->component->out_stats.audio.packets)); - json_object_set_new(info, "bytes-received", json_integer(stream->component->in_stats.audio.bytes)); - json_object_set_new(info, "bytes-sent", json_integer(stream->component->out_stats.audio.bytes)); - json_object_set_new(info, "bytes-received-lastsec", json_integer(stream->component->in_stats.audio.bytes_lastsec)); - json_object_set_new(info, "bytes-sent-lastsec", json_integer(stream->component->out_stats.audio.bytes_lastsec)); - json_object_set_new(info, "nacks-received", json_integer(stream->component->in_stats.audio.nacks)); - json_object_set_new(info, "nacks-sent", json_integer(stream->component->out_stats.audio.nacks)); - json_object_set_new(info, "retransmissions-received", json_integer(stream->audio_rtcp_ctx->retransmitted)); - } - janus_events_notify_handlers(JANUS_EVENT_TYPE_MEDIA, JANUS_EVENT_SUBTYPE_MEDIA_STATS, - session->session_id, handle->handle_id, handle->opaque_id, info); + /* Now let's see if we need to notify the user about no incoming audio or video */ + if(no_media_timer > 0 && pc->dtls && pc->dtls->dtls_connected > 0 && (now - pc->dtls->dtls_connected >= G_USEC_PER_SEC)) { + gint64 last = medium->in_stats.info[0].updated; + if(!medium->in_stats.info[0].notified_lastsec && last && + !medium->in_stats.info[0].bytes_lastsec && !medium->in_stats.info[0].bytes_lastsec_temp && + now-last >= (gint64)no_media_timer*G_USEC_PER_SEC) { + /* We missed more than no_second_timer seconds of video! */ + medium->in_stats.info[0].notified_lastsec = TRUE; + JANUS_LOG(LOG_WARN, "[%"SCNu64"] Didn't receive video for more than a second...\n", handle->handle_id); + janus_ice_notify_media(handle, medium->mid, medium->type == JANUS_MEDIA_VIDEO, FALSE); } } - /* Do the same for video */ - if(janus_events_is_enabled() && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO)) { - int vindex=0; - for(vindex=0; vindex<3; vindex++) { - if(stream && stream->video_rtcp_ctx[vindex]) { - json_t *info = json_object(); - if(vindex == 0) - json_object_set_new(info, "media", json_string("video")); - else if(vindex == 1) - json_object_set_new(info, "media", json_string("video-sim1")); - else - json_object_set_new(info, "media", json_string("video-sim2")); - json_object_set_new(info, "base", json_integer(stream->video_rtcp_ctx[vindex]->tb)); - if(vindex == 0) - json_object_set_new(info, "rtt", json_integer(janus_rtcp_context_get_rtt(stream->video_rtcp_ctx[vindex]))); - json_object_set_new(info, "lost", json_integer(janus_rtcp_context_get_lost_all(stream->video_rtcp_ctx[vindex], FALSE))); - json_object_set_new(info, "lost-by-remote", json_integer(janus_rtcp_context_get_lost_all(stream->video_rtcp_ctx[vindex], TRUE))); - json_object_set_new(info, "jitter-local", json_integer(janus_rtcp_context_get_jitter(stream->video_rtcp_ctx[vindex], FALSE))); - json_object_set_new(info, "jitter-remote", json_integer(janus_rtcp_context_get_jitter(stream->video_rtcp_ctx[vindex], TRUE))); - json_object_set_new(info, "in-link-quality", json_integer(janus_rtcp_context_get_in_link_quality(stream->video_rtcp_ctx[vindex]))); - json_object_set_new(info, "in-media-link-quality", json_integer(janus_rtcp_context_get_in_media_link_quality(stream->video_rtcp_ctx[vindex]))); - json_object_set_new(info, "out-link-quality", json_integer(janus_rtcp_context_get_out_link_quality(stream->video_rtcp_ctx[vindex]))); - json_object_set_new(info, "out-media-link-quality", json_integer(janus_rtcp_context_get_out_media_link_quality(stream->video_rtcp_ctx[vindex]))); - if(stream->component) { - json_object_set_new(info, "packets-received", json_integer(stream->component->in_stats.video[vindex].packets)); - json_object_set_new(info, "packets-sent", json_integer(stream->component->out_stats.video[vindex].packets)); - json_object_set_new(info, "bytes-received", json_integer(stream->component->in_stats.video[vindex].bytes)); - json_object_set_new(info, "bytes-sent", json_integer(stream->component->out_stats.video[vindex].bytes)); - json_object_set_new(info, "bytes-received-lastsec", json_integer(stream->component->in_stats.video[vindex].bytes_lastsec)); - json_object_set_new(info, "bytes-sent-lastsec", json_integer(stream->component->out_stats.video[vindex].bytes_lastsec)); - json_object_set_new(info, "nacks-received", json_integer(stream->component->in_stats.video[vindex].nacks)); - json_object_set_new(info, "nacks-sent", json_integer(stream->component->out_stats.video[vindex].nacks)); - json_object_set_new(info, "retransmissions-received", json_integer(stream->video_rtcp_ctx[vindex]->retransmitted)); + /* We also send live stats to event handlers every tot-seconds (configurable) */ + handle->last_event_stats++; + if(janus_ice_event_stats_period > 0 && handle->last_event_stats >= janus_ice_event_stats_period) { + handle->last_event_stats = 0; + if(janus_events_is_enabled()) { + int vindex=0; + for(vindex=0; vindex<3; vindex++) { + if(medium && medium->rtcp_ctx[vindex]) { + json_t *info = json_object(); + json_object_set_new(info, "mid", json_string(medium->mid)); + json_object_set_new(info, "mindex", json_integer(medium->mindex)); + if(vindex == 0) + json_object_set_new(info, "media", json_string(medium->type == JANUS_MEDIA_VIDEO ? "video" : "audio")); + else if(vindex == 1) + json_object_set_new(info, "media", json_string("video-sim1")); + else + json_object_set_new(info, "media", json_string("video-sim2")); + json_object_set_new(info, "base", json_integer(medium->rtcp_ctx[vindex]->tb)); + if(vindex == 0) + json_object_set_new(info, "rtt", json_integer(janus_rtcp_context_get_rtt(medium->rtcp_ctx[vindex]))); + json_object_set_new(info, "lost", json_integer(janus_rtcp_context_get_lost_all(medium->rtcp_ctx[vindex], FALSE))); + json_object_set_new(info, "lost-by-remote", json_integer(janus_rtcp_context_get_lost_all(medium->rtcp_ctx[vindex], TRUE))); + json_object_set_new(info, "jitter-local", json_integer(janus_rtcp_context_get_jitter(medium->rtcp_ctx[vindex], FALSE))); + json_object_set_new(info, "jitter-remote", json_integer(janus_rtcp_context_get_jitter(medium->rtcp_ctx[vindex], TRUE))); + json_object_set_new(info, "in-link-quality", json_integer(janus_rtcp_context_get_in_link_quality(medium->rtcp_ctx[vindex]))); + json_object_set_new(info, "in-media-link-quality", json_integer(janus_rtcp_context_get_in_media_link_quality(medium->rtcp_ctx[vindex]))); + json_object_set_new(info, "out-link-quality", json_integer(janus_rtcp_context_get_out_link_quality(medium->rtcp_ctx[vindex]))); + json_object_set_new(info, "out-media-link-quality", json_integer(janus_rtcp_context_get_out_media_link_quality(medium->rtcp_ctx[vindex]))); + json_object_set_new(info, "packets-received", json_integer(medium->in_stats.info[vindex].packets)); + json_object_set_new(info, "packets-sent", json_integer(medium->out_stats.info[vindex].packets)); + json_object_set_new(info, "bytes-received", json_integer(medium->in_stats.info[vindex].bytes)); + json_object_set_new(info, "bytes-sent", json_integer(medium->out_stats.info[vindex].bytes)); + json_object_set_new(info, "bytes-received-lastsec", json_integer(medium->in_stats.info[vindex].bytes_lastsec)); + json_object_set_new(info, "bytes-sent-lastsec", json_integer(medium->out_stats.info[vindex].bytes_lastsec)); + json_object_set_new(info, "nacks-received", json_integer(medium->in_stats.info[vindex].nacks)); + json_object_set_new(info, "nacks-sent", json_integer(medium->out_stats.info[vindex].nacks)); + json_object_set_new(info, "retransmissions-received", json_integer(medium->rtcp_ctx[vindex]->retransmitted)); + janus_events_notify_handlers(JANUS_EVENT_TYPE_MEDIA, JANUS_EVENT_SUBTYPE_MEDIA_STATS, + session->session_id, handle->handle_id, handle->opaque_id, info); } - janus_events_notify_handlers(JANUS_EVENT_TYPE_MEDIA, JANUS_EVENT_SUBTYPE_MEDIA_STATS, - session->session_id, handle->handle_id, handle->opaque_id, info); } } } } /* Should we clean up old NACK buffers for any of the streams? */ - janus_cleanup_nack_buffer(now, handle->stream, TRUE, TRUE); + janus_cleanup_nack_buffer(now, handle->pc, TRUE, TRUE); /* Check if we should also print a summary of SRTP-related errors */ handle->last_srtp_summary++; if(handle->last_srtp_summary == 0 || handle->last_srtp_summary == 2) { @@ -4104,8 +3798,8 @@ static gboolean janus_ice_outgoing_stats_handle(gpointer user_data) { static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janus_ice_queued_packet *pkt) { janus_session *session = (janus_session *)handle->session; - janus_ice_stream *stream = handle->stream; - janus_ice_component *component = stream ? stream->component : NULL; + janus_ice_peerconnection *pc = handle->pc; + janus_ice_peerconnection_medium *medium = NULL; if(pkt == &janus_ice_start_gathering) { /* Start gathering candidates */ if(handle->agent == NULL) { @@ -4124,8 +3818,8 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu candidates = g_slist_append(candidates, c); } guint count = g_slist_length(candidates); - if(stream != NULL && component != NULL && count > 0) { - int added = nice_agent_set_remote_candidates(handle->agent, stream->stream_id, component->component_id, candidates); + if(pc != NULL && count > 0) { + int added = nice_agent_set_remote_candidates(handle->agent, pc->stream_id, pc->component_id, candidates); if(added < 0 || (guint)added != count) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Failed to add some remote candidates (added %u, expected %u)\n", handle->handle_id, added, count); @@ -4139,24 +3833,24 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu } else if(pkt == &janus_ice_dtls_handshake) { if(!janus_is_webrtc_encryption_enabled()) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] WebRTC encryption disabled, skipping DTLS handshake\n", handle->handle_id); - janus_ice_dtls_handshake_done(handle, component); + janus_ice_dtls_handshake_done(handle); return G_SOURCE_CONTINUE; - } else if(!component) { - JANUS_LOG(LOG_WARN, "[%"SCNu64"] ICE component not initialized, aborting DTLS handshake\n", handle->handle_id); + } else if(!pc) { + JANUS_LOG(LOG_WARN, "[%"SCNu64"] PeerConnection not initialized, aborting DTLS handshake\n", handle->handle_id); return G_SOURCE_CONTINUE; } /* Start the DTLS handshake */ - janus_dtls_srtp_handshake(component->dtls); + janus_dtls_srtp_handshake(pc->dtls); /* Create retransmission timer */ - component->dtlsrt_source = g_timeout_source_new(50); - g_source_set_callback(component->dtlsrt_source, janus_dtls_retry, component->dtls, NULL); - guint id = g_source_attach(component->dtlsrt_source, handle->mainctx); + pc->dtlsrt_source = g_timeout_source_new(50); + g_source_set_callback(pc->dtlsrt_source, janus_dtls_retry, pc->dtls, NULL); + guint id = g_source_attach(pc->dtlsrt_source, handle->mainctx); JANUS_LOG(LOG_VERB, "[%"SCNu64"] Creating retransmission timer with ID %u\n", handle->handle_id, id); return G_SOURCE_CONTINUE; } else if(pkt == &janus_ice_hangup_peerconnection) { /* The media session is over, send an alert on all streams and components */ - if(handle->stream && handle->stream->component && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_READY)) { - janus_dtls_srtp_send_alert(handle->stream->component->dtls); + if(handle->pc && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_READY)) { + janus_dtls_srtp_send_alert(handle->pc->dtls); } /* Notify the plugin about the fact this PeerConnection has just gone */ janus_plugin *plugin = (janus_plugin *)handle->app; @@ -4236,7 +3930,7 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu /* Now let's get on with the packet */ if(pkt == NULL) return G_SOURCE_CONTINUE; - if(pkt->data == NULL || stream == NULL) { + if(pkt->data == NULL || pc == NULL) { janus_ice_free_queued_packet(pkt); return G_SOURCE_CONTINUE; } @@ -4246,31 +3940,38 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu janus_ice_free_queued_packet(pkt); return G_SOURCE_CONTINUE; } - if(!stream->cdone) { - if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT) && !stream->noerrorlog) { + if(!pc->cdone) { + if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT) && !pc->noerrorlog) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] No candidates not gathered yet for stream??\n", handle->handle_id); - stream->noerrorlog = TRUE; /* Don't flood with the same error all over again */ + pc->noerrorlog = TRUE; /* Don't flood with the same error all over again */ } janus_ice_free_queued_packet(pkt); return G_SOURCE_CONTINUE; } + /* Find the right medium instance */ + medium = g_hash_table_lookup(pc->media, GINT_TO_POINTER(pkt->mindex)); + if(medium == NULL) { + JANUS_LOG(LOG_ERR, "[%"SCNu64"] No medium #%d associated to this packet??\n", handle->handle_id, pkt->mindex); + janus_ice_free_queued_packet(pkt); + return G_SOURCE_CONTINUE; + } if(pkt->control) { /* RTCP */ int video = (pkt->type == JANUS_ICE_PACKET_VIDEO); - stream->noerrorlog = FALSE; - if(janus_is_webrtc_encryption_enabled() && (!component->dtls || !component->dtls->srtp_valid || !component->dtls->srtp_out)) { - if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT) && !component->noerrorlog) { + pc->noerrorlog = FALSE; + if(janus_is_webrtc_encryption_enabled() && (!pc->dtls || !pc->dtls->srtp_valid || !pc->dtls->srtp_out)) { + if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT) && !pc->noerrorlog) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] %s stream (#%u) component has no valid SRTP session (yet?)\n", - handle->handle_id, video ? "video" : "audio", stream->stream_id); - component->noerrorlog = TRUE; /* Don't flood with the same error all over again */ + handle->handle_id, video ? "video" : "audio", pc->stream_id); + medium->noerrorlog = TRUE; /* Don't flood with the same error all over again */ } janus_ice_free_queued_packet(pkt); return G_SOURCE_CONTINUE; } - component->noerrorlog = FALSE; + medium->noerrorlog = FALSE; if(pkt->encrypted) { /* Already SRTCP */ - int sent = nice_agent_send(handle->agent, stream->stream_id, component->component_id, pkt->length, (const gchar *)pkt->data); + int sent = nice_agent_send(handle->agent, pc->stream_id, pc->component_id, pkt->length, (const gchar *)pkt->data); if(sent < pkt->length) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] ... only sent %d bytes? (was %d)\n", handle->handle_id, sent, pkt->length); } @@ -4286,16 +3987,15 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu rr->header.type = RTCP_RR; rr->header.rc = 0; rr->header.length = htons((rrlen/4)-1); - janus_ice_stream *stream = handle->stream; /* Append REMB */ memcpy(rtcpbuf+rrlen, pkt->data, pkt->length); /* If we're simulcasting, set the extra SSRCs (the first one will be set by janus_rtcp_fix_ssrc) */ - if(stream->video_ssrc_peer[1] && pkt->length >= 28) { + if(medium->ssrc_peer[1] && pkt->length >= 28) { rtcp_fb *rtcpfb = (rtcp_fb *)(rtcpbuf+rrlen); rtcp_remb *remb = (rtcp_remb *)rtcpfb->fci; - remb->ssrc[1] = htonl(stream->video_ssrc_peer[1]); - if(stream->video_ssrc_peer[2] && pkt->length >= 32) { - remb->ssrc[2] = htonl(stream->video_ssrc_peer[2]); + remb->ssrc[1] = htonl(medium->ssrc_peer[1]); + if(medium->ssrc_peer[2] && pkt->length >= 32) { + remb->ssrc[2] = htonl(medium->ssrc_peer[2]); } } /* Free old packet and update */ @@ -4311,7 +4011,7 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu /* Encrypt SRTCP */ int protected = pkt->length; int res = janus_is_webrtc_encryption_enabled() ? - srtp_protect_rtcp(component->dtls->srtp_out, pkt->data, &protected) : srtp_err_status_ok; + srtp_protect_rtcp(pc->dtls->srtp_out, pkt->data, &protected) : srtp_err_status_ok; if(res != srtp_err_status_ok) { /* We don't spam the logs for every SRTP error: just take note of this, and print a summary later */ handle->srtp_errors_count++; @@ -4320,7 +4020,7 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu JANUS_LOG(LOG_DBG, "[%"SCNu64"] ... SRTCP protect error... %s (len=%d-->%d)...\n", handle->handle_id, janus_srtp_error_str(res), pkt->length, protected); } else { /* Shoot! */ - int sent = nice_agent_send(handle->agent, stream->stream_id, component->component_id, protected, pkt->data); + int sent = nice_agent_send(handle->agent, pc->stream_id, pc->component_id, protected, pkt->data); if(sent < protected) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] ... only sent %d bytes? (was %d)\n", handle->handle_id, sent, protected); } @@ -4332,25 +4032,25 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu if(pkt->type == JANUS_ICE_PACKET_AUDIO || pkt->type == JANUS_ICE_PACKET_VIDEO) { /* RTP */ int video = (pkt->type == JANUS_ICE_PACKET_VIDEO); - if((!video && !stream->audio_send) || (video && !stream->video_send)) { + if(!medium->send) { janus_ice_free_queued_packet(pkt); return G_SOURCE_CONTINUE; } - if(janus_is_webrtc_encryption_enabled() && (!component->dtls || !component->dtls->srtp_valid || !component->dtls->srtp_out)) { - if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT) && !component->noerrorlog) { + if(janus_is_webrtc_encryption_enabled() && (!pc->dtls || !pc->dtls->srtp_valid || !pc->dtls->srtp_out)) { + if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT) && !medium->noerrorlog) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] %s stream component has no valid SRTP session (yet?)\n", handle->handle_id, video ? "video" : "audio"); - component->noerrorlog = TRUE; /* Don't flood with the same error all over again */ + medium->noerrorlog = TRUE; /* Don't flood with the same error all over again */ } janus_ice_free_queued_packet(pkt); return G_SOURCE_CONTINUE; } - component->noerrorlog = FALSE; + medium->noerrorlog = FALSE; if(pkt->encrypted) { /* Already RTP (probably a retransmission?) */ janus_rtp_header *header = (janus_rtp_header *)pkt->data; JANUS_LOG(LOG_HUGE, "[%"SCNu64"] ... Retransmitting seq.nr %"SCNu16"\n\n", handle->handle_id, ntohs(header->seq_number)); - int sent = nice_agent_send(handle->agent, stream->stream_id, component->component_id, pkt->length, (const gchar *)pkt->data); + int sent = nice_agent_send(handle->agent, pc->stream_id, pc->component_id, pkt->length, (const gchar *)pkt->data); if(sent < pkt->length) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] ... only sent %d bytes? (was %d)\n", handle->handle_id, sent, pkt->length); } @@ -4359,48 +4059,41 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu janus_rtp_header *header = (janus_rtp_header *)pkt->data; if(!pkt->retransmission) { /* ... but only if this isn't a retransmission (for those we already set it before) */ - header->ssrc = htonl(video ? stream->video_ssrc : stream->audio_ssrc); + header->ssrc = htonl(medium->ssrc); } /* Set the transport-wide sequence number, if needed */ - if(video && stream->transport_wide_cc_ext_id > 0) { - stream->transport_wide_cc_out_seq_num++; + if(video && pc->transport_wide_cc_ext_id > 0) { + pc->transport_wide_cc_out_seq_num++; if(janus_rtp_header_extension_set_transport_wide_cc(pkt->data, pkt->length, - stream->transport_wide_cc_ext_id, stream->transport_wide_cc_out_seq_num) < 0) { + pc->transport_wide_cc_ext_id, pc->transport_wide_cc_out_seq_num) < 0) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error setting transport wide CC sequence number...\n", handle->handle_id); } } /* Keep track of payload types too */ - if(!video && stream->audio_payload_type < 0) { - stream->audio_payload_type = header->type; - if(stream->audio_codec == NULL) { - const char *codec = janus_get_codec_from_pt(handle->local_sdp, stream->audio_payload_type); - if(codec != NULL) - stream->audio_codec = g_strdup(codec); - } - } else if(video && stream->video_payload_type < 0) { - stream->video_payload_type = header->type; + if(medium->payload_type < 0) { + medium->payload_type = header->type; if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX) && - stream->rtx_payload_types && g_hash_table_size(stream->rtx_payload_types) > 0) { - stream->video_rtx_payload_type = GPOINTER_TO_INT(g_hash_table_lookup(stream->rtx_payload_types, GINT_TO_POINTER(stream->video_payload_type))); + medium->rtx_payload_types && g_hash_table_size(medium->rtx_payload_types) > 0) { + medium->rtx_payload_type = GPOINTER_TO_INT(g_hash_table_lookup(medium->rtx_payload_types, GINT_TO_POINTER(medium->payload_type))); JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Retransmissions will have payload type %d\n", - handle->handle_id, stream->video_rtx_payload_type); + handle->handle_id, medium->rtx_payload_type); } - if(stream->video_codec == NULL) { - const char *codec = janus_get_codec_from_pt(handle->local_sdp, stream->video_payload_type); + if(medium->codec == NULL) { + const char *codec = janus_get_codec_from_pt(handle->local_sdp, medium->payload_type); if(codec != NULL) - stream->video_codec = g_strdup(codec); + medium->codec = g_strdup(codec); } - if(stream->video_is_keyframe == NULL && stream->video_codec != NULL) { - if(!strcasecmp(stream->video_codec, "vp8")) - stream->video_is_keyframe = &janus_vp8_is_keyframe; - else if(!strcasecmp(stream->video_codec, "vp9")) - stream->video_is_keyframe = &janus_vp9_is_keyframe; - else if(!strcasecmp(stream->video_codec, "h264")) - stream->video_is_keyframe = &janus_h264_is_keyframe; - else if(!strcasecmp(stream->video_codec, "av1")) - stream->video_is_keyframe = &janus_av1_is_keyframe; - else if(!strcasecmp(stream->video_codec, "h265")) - stream->video_is_keyframe = &janus_h265_is_keyframe; + if(video && medium->video_is_keyframe == NULL && medium->codec != NULL) { + if(!strcasecmp(medium->codec, "vp8")) + medium->video_is_keyframe = &janus_vp8_is_keyframe; + else if(!strcasecmp(medium->codec, "vp9")) + medium->video_is_keyframe = &janus_vp9_is_keyframe; + else if(!strcasecmp(medium->codec, "h264")) + medium->video_is_keyframe = &janus_h264_is_keyframe; + else if(!strcasecmp(medium->codec, "av1")) + medium->video_is_keyframe = &janus_av1_is_keyframe; + else if(!strcasecmp(medium->codec, "h265")) + medium->video_is_keyframe = &janus_h265_is_keyframe; } } /* Do we need to dump this packet for debugging? */ @@ -4409,17 +4102,17 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu "[session=%"SCNu64"][handle=%"SCNu64"]", session->session_id, handle->handle_id); /* If this is video and NACK optimizations are enabled, check if this is * a keyframe: if so, we empty our retransmit buffer for incoming NACKs */ - if(video && nack_optimizations && stream->video_is_keyframe) { + if(video && nack_optimizations && medium->video_is_keyframe) { int plen = 0; char *payload = janus_rtp_payload(pkt->data, pkt->length, &plen); - if(stream->video_is_keyframe(payload, plen)) { + if(medium->video_is_keyframe(payload, plen)) { JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Keyframe sent, cleaning retransmit buffer\n", handle->handle_id); - janus_cleanup_nack_buffer(0, stream, FALSE, TRUE); + janus_cleanup_nack_buffer(0, pc, FALSE, TRUE); } } /* Before encrypting, check if we need to copy the unencrypted payload (e.g., for rtx/90000) */ janus_rtp_packet *p = NULL; - if(stream->nack_queue_ms > 0 && !pkt->retransmission && pkt->type == JANUS_ICE_PACKET_VIDEO && component->do_video_nacks && + if(medium->nack_queue_ms > 0 && !pkt->retransmission && pkt->type == JANUS_ICE_PACKET_VIDEO && medium->do_nacks && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX)) { /* Save the packet for retransmissions that may be needed later: start by * making room for two more bytes to store the original sequence number */ @@ -4448,7 +4141,7 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu /* Encrypt SRTP */ int protected = pkt->length; int res = janus_is_webrtc_encryption_enabled() ? - srtp_protect(component->dtls->srtp_out, pkt->data, &protected) : srtp_err_status_ok; + srtp_protect(pc->dtls->srtp_out, pkt->data, &protected) : srtp_err_status_ok; if(res != srtp_err_status_ok) { /* We don't spam the logs for every SRTP error: just take note of this, and print a summary later */ handle->srtp_errors_count++; @@ -4462,7 +4155,7 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu janus_ice_free_rtp_packet(p); } else { /* Shoot! */ - int sent = nice_agent_send(handle->agent, stream->stream_id, component->component_id, protected, pkt->data); + int sent = nice_agent_send(handle->agent, pc->stream_id, pc->component_id, protected, pkt->data); if(sent < protected) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] ... only sent %d bytes? (was %d)\n", handle->handle_id, sent, protected); } @@ -4471,71 +4164,46 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu /* Update the RTCP context as well */ janus_rtp_header *header = (janus_rtp_header *)pkt->data; guint32 timestamp = ntohl(header->timestamp); - if(pkt->type == JANUS_ICE_PACKET_AUDIO) { - component->out_stats.audio.packets++; - component->out_stats.audio.bytes += pkt->length; - /* Last second outgoing audio */ - gint64 now = janus_get_monotonic_time(); - if(component->out_stats.audio.updated == 0) - component->out_stats.audio.updated = now; - if(now > component->out_stats.audio.updated && - now - component->out_stats.audio.updated >= G_USEC_PER_SEC) { - component->out_stats.audio.bytes_lastsec = component->out_stats.audio.bytes_lastsec_temp; - component->out_stats.audio.bytes_lastsec_temp = 0; - component->out_stats.audio.updated = now; - } - component->out_stats.audio.bytes_lastsec_temp += pkt->length; - struct timeval tv; - gettimeofday(&tv, NULL); - if(stream->audio_last_ntp_ts == 0 || (gint32)(timestamp - stream->audio_last_rtp_ts) > 0) { - stream->audio_last_ntp_ts = (gint64)tv.tv_sec*G_USEC_PER_SEC + tv.tv_usec; - stream->audio_last_rtp_ts = timestamp; - } - if(stream->audio_first_ntp_ts == 0) { - stream->audio_first_ntp_ts = (gint64)tv.tv_sec*G_USEC_PER_SEC + tv.tv_usec; - stream->audio_first_rtp_ts = timestamp; - } - /* Let's check if this is not Opus: in case we may need to change the timestamp base */ - rtcp_context *rtcp_ctx = stream->audio_rtcp_ctx; - int pt = header->type; - uint32_t clock_rate = stream->clock_rates ? - GPOINTER_TO_UINT(g_hash_table_lookup(stream->clock_rates, GINT_TO_POINTER(pt))) : 48000; - if(rtcp_ctx->tb != clock_rate) - rtcp_ctx->tb = clock_rate; - } else if(pkt->type == JANUS_ICE_PACKET_VIDEO) { - component->out_stats.video[0].packets++; - component->out_stats.video[0].bytes += pkt->length; - /* Last second outgoing video */ - gint64 now = janus_get_monotonic_time(); - if(component->out_stats.video[0].updated == 0) - component->out_stats.video[0].updated = now; - if(now > component->out_stats.video[0].updated && - now - component->out_stats.video[0].updated >= G_USEC_PER_SEC) { - component->out_stats.video[0].bytes_lastsec = component->out_stats.video[0].bytes_lastsec_temp; - component->out_stats.video[0].bytes_lastsec_temp = 0; - component->out_stats.video[0].updated = now; - } - component->out_stats.video[0].bytes_lastsec_temp += pkt->length; - struct timeval tv; - gettimeofday(&tv, NULL); - if(stream->video_last_ntp_ts == 0 || (gint32)(timestamp - stream->video_last_rtp_ts) > 0) { - stream->video_last_ntp_ts = (gint64)tv.tv_sec*G_USEC_PER_SEC + tv.tv_usec; - stream->video_last_rtp_ts = timestamp; - } - if(stream->video_first_ntp_ts[0] == 0) { - stream->video_first_ntp_ts[0] = (gint64)tv.tv_sec*G_USEC_PER_SEC + tv.tv_usec; - stream->video_first_rtp_ts[0] = timestamp; - } + medium->out_stats.info[0].packets++; + medium->out_stats.info[0].bytes += pkt->length; + /* Last second outgoing media */ + gint64 now = janus_get_monotonic_time(); + if(medium->out_stats.info[0].updated == 0) + medium->out_stats.info[0].updated = now; + if(now > medium->out_stats.info[0].updated && + now - medium->out_stats.info[0].updated >= G_USEC_PER_SEC) { + medium->out_stats.info[0].bytes_lastsec = medium->out_stats.info[0].bytes_lastsec_temp; + medium->out_stats.info[0].bytes_lastsec_temp = 0; + medium->out_stats.info[0].updated = now; + } + medium->out_stats.info[0].bytes_lastsec_temp += pkt->length; + struct timeval tv; + gettimeofday(&tv, NULL); + if(medium->last_ntp_ts == 0 || (gint32)(timestamp - medium->last_rtp_ts) > 0) { + medium->last_ntp_ts = (gint64)tv.tv_sec*G_USEC_PER_SEC + tv.tv_usec; + medium->last_rtp_ts = timestamp; + } + if(medium->first_ntp_ts == 0) { + medium->first_ntp_ts[0] = (gint64)tv.tv_sec*G_USEC_PER_SEC + tv.tv_usec; + medium->first_rtp_ts[0] = timestamp; } /* Update sent packets counter */ - rtcp_context *rtcp_ctx = video ? stream->video_rtcp_ctx[0] : stream->audio_rtcp_ctx; - if(rtcp_ctx) + rtcp_context *rtcp_ctx = medium->rtcp_ctx[0]; + if(rtcp_ctx) { g_atomic_int_inc(&rtcp_ctx->sent_packets_since_last_rr); + if(pkt->type == JANUS_ICE_PACKET_AUDIO) { + /* Let's check if this is not Opus: in case we may need to change the timestamp base */ + int pt = header->type; + uint32_t clock_rate = medium->clock_rates ? + GPOINTER_TO_UINT(g_hash_table_lookup(medium->clock_rates, GINT_TO_POINTER(pt))) : 48000; + if(rtcp_ctx->tb != clock_rate) + rtcp_ctx->tb = clock_rate; + } + } } - if(stream->nack_queue_ms > 0 && !pkt->retransmission) { + if(medium->nack_queue_ms > 0 && !pkt->retransmission) { /* Save the packet for retransmissions that may be needed later */ - if((pkt->type == JANUS_ICE_PACKET_AUDIO && !component->do_audio_nacks) || - (pkt->type == JANUS_ICE_PACKET_VIDEO && !component->do_video_nacks)) { + if(!medium->do_nacks) { /* ... unless NACKs are disabled for this medium */ janus_ice_free_queued_packet(pkt); return G_SOURCE_CONTINUE; @@ -4551,23 +4219,13 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu p->last_retransmit = 0; janus_rtp_header *header = (janus_rtp_header *)pkt->data; guint16 seq = ntohs(header->seq_number); - if(!video) { - if(component->audio_retransmit_buffer == NULL) { - component->audio_retransmit_buffer = g_queue_new(); - component->audio_retransmit_seqs = g_hash_table_new(NULL, NULL); - } - g_queue_push_tail(component->audio_retransmit_buffer, p); - /* Insert in the table too, for quick lookup */ - g_hash_table_insert(component->audio_retransmit_seqs, GUINT_TO_POINTER(seq), p); - } else { - if(component->video_retransmit_buffer == NULL) { - component->video_retransmit_buffer = g_queue_new(); - component->video_retransmit_seqs = g_hash_table_new(NULL, NULL); - } - g_queue_push_tail(component->video_retransmit_buffer, p); - /* Insert in the table too, for quick lookup */ - g_hash_table_insert(component->video_retransmit_seqs, GUINT_TO_POINTER(seq), p); + if(medium->retransmit_buffer == NULL) { + medium->retransmit_buffer = g_queue_new(); + medium->retransmit_seqs = g_hash_table_new(NULL, NULL); } + g_queue_push_tail(medium->retransmit_buffer, p); + /* Insert in the table too, for quick lookup */ + g_hash_table_insert(medium->retransmit_seqs, GUINT_TO_POINTER(seq), p); } else { janus_ice_free_rtp_packet(p); } @@ -4580,17 +4238,17 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu return G_SOURCE_CONTINUE; } #ifdef HAVE_SCTP - if(!component->dtls) { - if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT) && !component->noerrorlog) { + if(!pc->dtls) { + if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT) && !medium->noerrorlog) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] SCTP stream component has no valid DTLS session (yet?)\n", handle->handle_id); - component->noerrorlog = TRUE; /* Don't flood with the same error all over again */ + medium->noerrorlog = TRUE; /* Don't flood with the same error all over again */ } janus_ice_free_queued_packet(pkt); return G_SOURCE_CONTINUE; } - component->noerrorlog = FALSE; + medium->noerrorlog = FALSE; /* TODO Support binary data */ - janus_dtls_wrap_sctp_data(component->dtls, pkt->label, pkt->protocol, + janus_dtls_wrap_sctp_data(pc->dtls, pkt->label, pkt->protocol, pkt->type == JANUS_ICE_PACKET_TEXT, pkt->data, pkt->length); #endif } else if(pkt->type == JANUS_ICE_PACKET_SCTP) { @@ -4601,16 +4259,16 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu } #ifdef HAVE_SCTP /* Encapsulate this data in DTLS and send it */ - if(!component->dtls) { - if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT) && !component->noerrorlog) { + if(!pc->dtls) { + if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT) && !medium->noerrorlog) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] SCTP stream component has no valid DTLS session (yet?)\n", handle->handle_id); - component->noerrorlog = TRUE; /* Don't flood with the same error all over again */ + medium->noerrorlog = TRUE; /* Don't flood with the same error all over again */ } janus_ice_free_queued_packet(pkt); return G_SOURCE_CONTINUE; } - component->noerrorlog = FALSE; - janus_dtls_send_sctp_data(component->dtls, pkt->data, pkt->length); + medium->noerrorlog = FALSE; + janus_dtls_send_sctp_data(pc->dtls, pkt->data, pkt->length); #endif } else { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Unsupported packet type %d\n", handle->handle_id, pkt->type); @@ -4632,11 +4290,15 @@ static void janus_ice_queue_packet(janus_ice_handle *handle, janus_ice_queued_pa } void janus_ice_relay_rtp(janus_ice_handle *handle, janus_plugin_rtp *packet) { - if(!handle || !handle->stream || handle->queued_packets == NULL || packet == NULL || packet->buffer == NULL || + if(!handle || !handle->pc || handle->queued_packets == NULL || packet == NULL || packet->buffer == NULL || !janus_is_rtp(packet->buffer, packet->length)) return; - if((!packet->video && !janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AUDIO)) - || (packet->video && !janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO))) + /* Find the right medium instance */ + janus_ice_peerconnection_medium *medium = (packet->mindex != -1 ? + g_hash_table_lookup(handle->pc->media, GINT_TO_POINTER(packet->mindex)) : + g_hash_table_lookup(handle->pc->media_bytype, + GINT_TO_POINTER(packet->video ? JANUS_MEDIA_VIDEO : JANUS_MEDIA_AUDIO))); + if(!medium) return; uint16_t totlen = RTP_HEADER_SIZE; /* Check how large the payload is */ @@ -4651,9 +4313,9 @@ void janus_ice_relay_rtp(janus_ice_handle *handle, janus_plugin_rtp *packet) { int origext = header->extension; header->extension = 0; /* Add core and plugin extensions, if any */ - if((packet->video && handle->stream->transport_wide_cc_ext_id > 0) || handle->stream->mid_ext_id > 0 || - (!packet->video && packet->extensions.audio_level != -1 && handle->stream->audiolevel_ext_id > 0) || - (packet->video && packet->extensions.video_rotation != -1 && handle->stream->videoorientation_ext_id > 0)) { + if((packet->video && handle->pc->transport_wide_cc_ext_id > 0) || handle->pc->mid_ext_id > 0 || + (!packet->video && packet->extensions.audio_level != -1 && handle->pc->audiolevel_ext_id > 0) || + (packet->video && packet->extensions.video_rotation != -1 && handle->pc->videoorientation_ext_id > 0)) { header->extension = 1; memset(extensions, 0, sizeof(extensions)); janus_rtp_header_extension *extheader = (janus_rtp_header_extension *)extensions; @@ -4662,35 +4324,35 @@ void janus_ice_relay_rtp(janus_ice_handle *handle, janus_plugin_rtp *packet) { /* Iterate on all extensions we need */ char *index = extensions + 4; /* Check if we need to add the transport-wide CC extension */ - if(packet->video && handle->stream->transport_wide_cc_ext_id > 0) { - *index = (handle->stream->transport_wide_cc_ext_id << 4) + 1; + if(packet->video && handle->pc->transport_wide_cc_ext_id > 0) { + *index = (handle->pc->transport_wide_cc_ext_id << 4) + 1; /* We'll actually set the sequence number later, when sending the packet */ memset(index+1, 0, 2); index += 3; extlen += 3; } /* Check if we need to add the mid extension */ - if(handle->stream->mid_ext_id > 0) { - char *mid = packet->video ? handle->video_mid : handle->audio_mid; + if(handle->pc->mid_ext_id > 0) { + char *mid = medium->mid; if(mid != NULL) { size_t midlen = strlen(mid) & 0x0F; - *index = (handle->stream->mid_ext_id << 4) + (midlen ? midlen-1 : 0); + *index = (handle->pc->mid_ext_id << 4) + (midlen ? midlen-1 : 0); memcpy(index+1, mid, midlen); index += (midlen + 1); extlen += (midlen + 1); } } /* Check if the plugin (or source) included other extensions */ - if(!packet->video && packet->extensions.audio_level != -1 && handle->stream->audiolevel_ext_id > 0) { + if(!packet->video && packet->extensions.audio_level != -1 && handle->pc->audiolevel_ext_id > 0) { /* Add audio-level extension */ - *index = (handle->stream->audiolevel_ext_id << 4); + *index = (handle->pc->audiolevel_ext_id << 4); *(index+1) = (packet->extensions.audio_level_vad << 7) + (packet->extensions.audio_level & 0x7F); index += 2; extlen += 2; } - if(packet->video && packet->extensions.video_rotation != -1 && handle->stream->videoorientation_ext_id > 0) { + if(packet->video && packet->extensions.video_rotation != -1 && handle->pc->videoorientation_ext_id > 0) { /* Add video-orientation extension */ - *index = (handle->stream->videoorientation_ext_id << 4); + *index = (handle->pc->videoorientation_ext_id << 4); gboolean c = packet->extensions.video_back_camera, f = packet->extensions.video_flipped, r1 = FALSE, r0 = FALSE; switch(packet->extensions.video_rotation) { @@ -4727,6 +4389,7 @@ void janus_ice_relay_rtp(janus_ice_handle *handle, janus_plugin_rtp *packet) { } /* Queue this packet */ janus_ice_queued_packet *pkt = g_malloc(sizeof(janus_ice_queued_packet)); + pkt->mindex = medium->mindex; pkt->data = g_malloc(totlen + SRTP_MAX_TAG_LEN); /* RTP header first */ memcpy(pkt->data, packet->buffer, RTP_HEADER_SIZE); @@ -4750,9 +4413,16 @@ void janus_ice_relay_rtp(janus_ice_handle *handle, janus_plugin_rtp *packet) { } void janus_ice_relay_rtcp_internal(janus_ice_handle *handle, janus_plugin_rtcp *packet, gboolean filter_rtcp) { - if(!handle || handle->queued_packets == NULL || packet == NULL || packet->buffer == NULL || + if(!handle || !handle->pc || handle->queued_packets == NULL || packet == NULL || packet->buffer == NULL || !janus_is_rtcp(packet->buffer, packet->length)) return; + /* Find the right medium instance */ + janus_ice_peerconnection_medium *medium = (packet->mindex != -1 ? + g_hash_table_lookup(handle->pc->media, GINT_TO_POINTER(packet->mindex)) : + g_hash_table_lookup(handle->pc->media_bytype, + GINT_TO_POINTER(packet->video ? JANUS_MEDIA_VIDEO : JANUS_MEDIA_AUDIO))); + if(!medium) + return; /* We use this internal method to check whether we need to filter RTCP (e.g., to make * sure we don't just forward any SR/RR from peers/plugins, but use our own) or it has * already been done, and so this is actually a packet added by the ICE send thread */ @@ -4760,9 +4430,6 @@ void janus_ice_relay_rtcp_internal(janus_ice_handle *handle, janus_plugin_rtcp * int rtcp_len = packet->length; if(filter_rtcp) { /* FIXME Strip RR/SR/SDES/NACKs/etc. */ - janus_ice_stream *stream = handle->stream; - if(stream == NULL) - return; rtcp_buf = janus_rtcp_filter(packet->buffer, packet->length, &rtcp_len); if(rtcp_buf == NULL || rtcp_len < 1) return; @@ -4770,14 +4437,13 @@ void janus_ice_relay_rtcp_internal(janus_ice_handle *handle, janus_plugin_rtcp * * leg. Note that this is only needed for RTCP packets coming from plugins: the * ones created by the core already have the right SSRCs in the right place */ JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Fixing SSRCs (local %u, peer %u)\n", handle->handle_id, - packet->video ? stream->video_ssrc : stream->audio_ssrc, - packet->video ? stream->video_ssrc_peer[0] : stream->audio_ssrc_peer); + medium->ssrc, medium->ssrc_peer[0]); janus_rtcp_fix_ssrc(NULL, rtcp_buf, rtcp_len, 1, - packet->video ? stream->video_ssrc : stream->audio_ssrc, - packet->video ? stream->video_ssrc_peer[0] : stream->audio_ssrc_peer); + medium->ssrc, medium->ssrc_peer[0]); } /* Queue this packet */ janus_ice_queued_packet *pkt = g_malloc(sizeof(janus_ice_queued_packet)); + pkt->mindex = medium->mindex; pkt->data = g_malloc(rtcp_len+SRTP_MAX_TAG_LEN+4); memcpy(pkt->data, rtcp_buf, rtcp_len); pkt->length = rtcp_len; @@ -4798,26 +4464,30 @@ void janus_ice_relay_rtcp_internal(janus_ice_handle *handle, janus_plugin_rtcp * void janus_ice_relay_rtcp(janus_ice_handle *handle, janus_plugin_rtcp *packet) { janus_ice_relay_rtcp_internal(handle, packet, TRUE); /* If this is a PLI and we're simulcasting, send a PLI on other layers as well */ - if(janus_rtcp_has_pli(packet->buffer, packet->length)) { - janus_ice_stream *stream = handle->stream; - if(stream == NULL) + if(packet->video && janus_rtcp_has_pli(packet->buffer, packet->length)) { + /* Find the right medium instance */ + janus_ice_peerconnection_medium *medium = (packet->mindex != -1 ? + g_hash_table_lookup(handle->pc->media, GINT_TO_POINTER(packet->mindex)) : + g_hash_table_lookup(handle->pc->media_bytype, + GINT_TO_POINTER(packet->video ? JANUS_MEDIA_VIDEO : JANUS_MEDIA_AUDIO))); + if(!medium) return; - if(stream->video_ssrc_peer[1]) { + if(medium->ssrc_peer[1]) { char plibuf[12]; memset(plibuf, 0, 12); janus_rtcp_pli((char *)&plibuf, 12); janus_rtcp_fix_ssrc(NULL, plibuf, sizeof(plibuf), 1, - stream->video_ssrc, stream->video_ssrc_peer[1]); - janus_plugin_rtcp rtcp = { .video = TRUE, .buffer = plibuf, .length = sizeof(plibuf) }; + medium->ssrc, medium->ssrc_peer[1]); + janus_plugin_rtcp rtcp = { .mindex = medium->mindex, .video = TRUE, .buffer = plibuf, .length = sizeof(plibuf) }; janus_ice_relay_rtcp_internal(handle, &rtcp, FALSE); } - if(stream->video_ssrc_peer[2]) { + if(medium->ssrc_peer[2]) { char plibuf[12]; memset(plibuf, 0, 12); janus_rtcp_pli((char *)&plibuf, 12); janus_rtcp_fix_ssrc(NULL, plibuf, sizeof(plibuf), 1, - stream->video_ssrc, stream->video_ssrc_peer[2]); - janus_plugin_rtcp rtcp = { .video = TRUE, .buffer = plibuf, .length = sizeof(plibuf) }; + medium->ssrc, medium->ssrc_peer[2]); + janus_plugin_rtcp rtcp = { .mindex = medium->mindex, .video = TRUE, .buffer = plibuf, .length = sizeof(plibuf) }; janus_ice_relay_rtcp_internal(handle, &rtcp, FALSE); } } @@ -4827,24 +4497,31 @@ void janus_ice_send_pli(janus_ice_handle *handle) { char rtcpbuf[12]; memset(rtcpbuf, 0, 12); janus_rtcp_pli((char *)&rtcpbuf, 12); - janus_plugin_rtcp rtcp = { .video = TRUE, .buffer = rtcpbuf, .length = 12 }; + /* FIXME We send the PLI on the first video m-line we have */ + janus_plugin_rtcp rtcp = { .mindex = -1, .video = TRUE, .buffer = rtcpbuf, .length = 12 }; janus_ice_relay_rtcp(handle, &rtcp); } void janus_ice_send_remb(janus_ice_handle *handle, uint32_t bitrate) { char rtcpbuf[24]; janus_rtcp_remb((char *)&rtcpbuf, 24, bitrate); - janus_plugin_rtcp rtcp = { .video = TRUE, .buffer = rtcpbuf, .length = 24 }; + /* FIXME We send the PLI on the first video m-line we have */ + janus_plugin_rtcp rtcp = { .mindex = -1, .video = TRUE, .buffer = rtcpbuf, .length = 24 }; janus_ice_relay_rtcp(handle, &rtcp); } #ifdef HAVE_SCTP void janus_ice_relay_data(janus_ice_handle *handle, janus_plugin_data *packet) { - if(!handle || handle->queued_packets == NULL || packet == NULL || packet->buffer == NULL || packet->length < 1) + if(!handle || !handle->pc || handle->queued_packets == NULL || packet == NULL || packet->buffer == NULL || packet->length < 1) + return; + /* Find the right medium instance */ + janus_ice_peerconnection_medium *medium = g_hash_table_lookup(handle->pc->media_bytype, + GINT_TO_POINTER(JANUS_MEDIA_DATA)); + if(!medium) /* Queue this packet */ return; - /* Queue this packet */ janus_ice_queued_packet *pkt = g_malloc(sizeof(janus_ice_queued_packet)); pkt->data = g_malloc(packet->length); + pkt->mindex = medium->mindex; memcpy(pkt->data, packet->buffer, packet->length); pkt->length = packet->length; pkt->type = packet->binary ? JANUS_ICE_PACKET_BINARY : JANUS_ICE_PACKET_TEXT; @@ -4860,11 +4537,17 @@ void janus_ice_relay_data(janus_ice_handle *handle, janus_plugin_data *packet) { void janus_ice_relay_sctp(janus_ice_handle *handle, char *buffer, int length) { #ifdef HAVE_SCTP - if(!handle || handle->queued_packets == NULL || buffer == NULL || length < 1) + if(!handle || !handle->pc || handle->queued_packets == NULL || buffer == NULL || length < 1) + return; + /* Find the right medium instance */ + janus_ice_peerconnection_medium *medium = g_hash_table_lookup(handle->pc->media_bytype, + GINT_TO_POINTER(JANUS_MEDIA_DATA)); + if(!medium) /* Queue this packet */ return; /* Queue this packet */ janus_ice_queued_packet *pkt = g_malloc(sizeof(janus_ice_queued_packet)); pkt->data = g_malloc(length); + pkt->mindex = medium->mindex; memcpy(pkt->data, buffer, length); pkt->length = length; pkt->type = JANUS_ICE_PACKET_SCTP; @@ -4892,21 +4575,13 @@ void janus_ice_notify_data_ready(janus_ice_handle *handle) { #endif } -void janus_ice_dtls_handshake_done(janus_ice_handle *handle, janus_ice_component *component) { - if(!handle || !component) +void janus_ice_dtls_handshake_done(janus_ice_handle *handle) { + if(!handle || !handle->pc) return; JANUS_LOG(LOG_VERB, "[%"SCNu64"] The DTLS handshake for the component %d in stream %d has been completed\n", - handle->handle_id, component->component_id, component->stream_id); + handle->handle_id, handle->pc->component_id, handle->pc->stream_id); /* Check if all components are ready */ janus_mutex_lock(&handle->mutex); - if(handle->stream && janus_is_webrtc_encryption_enabled()) { - if(handle->stream->component && (!handle->stream->component->dtls || - !handle->stream->component->dtls->srtp_valid)) { - /* Still waiting for this component to become ready */ - janus_mutex_unlock(&handle->mutex); - return; - } - } if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_READY)) { /* Already notified */ janus_mutex_unlock(&handle->mutex); diff --git a/ice.h b/ice.h index 8fe22c5c33..5ce8bbe68f 100644 --- a/ice.h +++ b/ice.h @@ -1,9 +1,13 @@ /*! \file ice.h * \author Lorenzo Miniero * \copyright GNU General Public License v3 - * \brief ICE/STUN/TURN processing (headers) - * \details Implementation (based on libnice) of the ICE process. The - * code handles the whole ICE process, from the gathering of candidates + * \brief Janus handles and ICE/STUN/TURN processing (headers) + * \details A Janus handle represents an abstraction of the communication + * between a user and a specific plugin, within a Janus session. This is + * particularly important in terms of media connectivity, as each handle + * can be associated with a single WebRTC PeerConnection. This code also + * contains the implementation (based on libnice) of a WebRTC PeerConnection. + * The code handles the whole ICE process, from the gathering of candidates * to the final setup of a virtual channel RTP and RTCP can be transported * on. Incoming RTP and RTCP packets from peers are relayed to the associated * plugins by means of the incoming_rtp and incoming_rtcp callbacks. Packets @@ -195,12 +199,12 @@ gboolean janus_is_opaqueid_in_api_enabled(void); const gchar *janus_get_ice_state_name(gint state); -/*! \brief Janus ICE handle/session */ +/*! \brief Janus ICE handle in a session */ typedef struct janus_ice_handle janus_ice_handle; -/*! \brief Janus ICE stream */ -typedef struct janus_ice_stream janus_ice_stream; -/*! \brief Janus ICE component */ -typedef struct janus_ice_component janus_ice_component; +/*! \brief WebRTC PeerConnection associated to a specific handle */ +typedef struct janus_ice_peerconnection janus_ice_peerconnection; +/*! \brief A single medium (i.e., m-line) in a Janus handle PeerConnection: can be bidirectional */ +typedef struct janus_ice_peerconnection_medium janus_ice_peerconnection_medium; /*! \brief Helper to handle pending trickle candidates (e.g., when we're still waiting for an offer) */ typedef struct janus_ice_trickle janus_ice_trickle; @@ -227,6 +231,14 @@ typedef struct janus_ice_trickle janus_ice_trickle; #define JANUS_ICE_HANDLE_WEBRTC_E2EE (1 << 21) +/*! \brief Janus media types */ +typedef enum janus_media_type { + JANUS_MEDIA_UNKNOWN = 0, + JANUS_MEDIA_AUDIO, + JANUS_MEDIA_VIDEO, + JANUS_MEDIA_DATA +} janus_media_type; + /*! \brief Janus media statistics * \note To improve with more stuff */ typedef struct janus_ice_stats_info { @@ -247,16 +259,10 @@ typedef struct janus_ice_stats_info { /*! \brief Janus media statistics container * \note To improve with more stuff */ typedef struct janus_ice_stats { - /*! \brief Audio info */ - janus_ice_stats_info audio; - /*! \brief Video info (considering we may be simulcasting) */ - janus_ice_stats_info video[3]; - /*! \brief Data info */ - janus_ice_stats_info data; - /*! \brief Last known count of lost audio packets (for slow_link) */ - guint sl_lost_count_audio; - /*! \brief Last known count of lost video packets (for slow_link) */ - guint sl_lost_count_video; + /*! \brief Media stats info (considering we may be simulcasting) */ + janus_ice_stats_info info[3]; + /*! \brief Last known count of lost packets (for slow_link) */ + guint sl_lost_count; } janus_ice_stats; /*! \brief Quick helper method to notify a WebRTC hangup through the Janus API @@ -322,18 +328,12 @@ struct janus_ice_handle { gint64 agent_created; /*! \brief ICE role (controlling or controlled) */ gboolean controlling; - /*! \brief Audio mid (media ID) */ - gchar *audio_mid; - /*! \brief Video mid (media ID) */ - gchar *video_mid; - /*! \brief Data channel mid (media ID) */ - gchar *data_mid; - /*! \brief Main mid (will be a pointer to one of the above) */ - gchar *stream_mid; + /*! \brief Main mid */ + gchar *pc_mid; /*! \brief ICE Stream ID */ guint stream_id; - /*! \brief ICE stream */ - janus_ice_stream *stream; + /*! \brief WebRTC PeerConnection, if any */ + janus_ice_peerconnection *pc; /*! \brief RTP profile set by caller (so that we can match it) */ gchar *rtp_profile; /*! \brief SDP generated locally (just for debugging purposes) */ @@ -368,76 +368,38 @@ struct janus_ice_handle { janus_refcount ref; }; -/*! \brief Janus ICE stream */ -struct janus_ice_stream { +/*! \brief Janus handle WebRTC PeerConnection */ +struct janus_ice_peerconnection { /*! \brief Janus ICE handle this stream belongs to */ janus_ice_handle *handle; /*! \brief libnice ICE stream ID */ guint stream_id; + /*! \brief libnice ICE component ID */ + guint component_id; /*! \brief Whether this stream is ready to be used */ gint cdone:1; - /*! \brief Audio SSRC of the server for this stream */ - guint32 audio_ssrc; - /*! \brief Video SSRC of the server for this stream */ - guint32 video_ssrc; - /*! \brief Video retransmission SSRC of the peer for this stream */ - guint32 video_ssrc_rtx; - /*! \brief Audio SSRC of the peer for this stream */ - guint32 audio_ssrc_peer, audio_ssrc_peer_new, audio_ssrc_peer_orig; - /*! \brief Video SSRC(s) of the peer for this stream (may be simulcasting) */ - guint32 video_ssrc_peer[3], video_ssrc_peer_new[3], video_ssrc_peer_orig[3], video_ssrc_peer_temp; - /*! \brief Video retransmissions SSRC(s) of the peer for this stream */ - guint32 video_ssrc_peer_rtx[3], video_ssrc_peer_rtx_new[3], video_ssrc_peer_rtx_orig[3]; - /*! \brief Array of RTP Stream IDs (for Firefox simulcasting, if enabled) */ - char *rid[3]; - /*! \brief Whether the order of the rids in the SDP will be h-m-l (TRUE) or l-m-h (FALSE) */ - gboolean rids_hml; - /*! \brief Whether we should use the legacy simulcast syntax (a=simulcast:recv rid=..) or the proper one (a=simulcast:recv ..) */ - gboolean legacy_rid; - /*! \brief RTP switching context(s) in case of renegotiations (audio+video and/or simulcast) */ - janus_rtp_switching_context rtp_ctx[3]; - /*! \brief List of payload types we can expect for audio */ - GList *audio_payload_types; - /*! \brief List of payload types we can expect for video */ - GList *video_payload_types; - /*! \brief Mapping of rtx payload types to actual media-related packet types */ - GHashTable *rtx_payload_types; - /*! \brief Mapping of payload types to their clock rates, as advertised in the SDP */ - GHashTable *clock_rates; - /*! \brief RTP payload types of this stream */ - gint audio_payload_type, video_payload_type, video_rtx_payload_type; - /*! \brief Codecs used by this stream */ - char *audio_codec, *video_codec; - /*! \brief Pointer to function to check if a packet is a keyframe (depends on negotiated codec) */ - gboolean (* video_is_keyframe)(const char* buffer, int len); - /*! \brief Media direction */ - gboolean audio_send, audio_recv, video_send, video_recv; - /*! \brief RTCP context for the audio stream */ - janus_rtcp_context *audio_rtcp_ctx; - /*! \brief RTCP context(s) for the video stream (may be simulcasting) */ - janus_rtcp_context *video_rtcp_ctx[3]; - /*! \brief Size of the NACK queue (in ms), dynamically updated per the RTT */ - uint16_t nack_queue_ms; - /*! \brief Map(s) of the NACKed packets (to track retransmissions and avoid duplicates) */ - GHashTable *rtx_nacked[3]; - /*! \brief Map of the pending NACKed cleanup callback */ - GHashTable *pending_nacked_cleanup; - /*! \brief First received audio NTP timestamp */ - gint64 audio_first_ntp_ts; - /*! \brief First received audio RTP timestamp */ - guint32 audio_first_rtp_ts; - /*! \brief First received video NTP timestamp (for all simulcast video streams) */ - gint64 video_first_ntp_ts[3]; - /*! \brief First received video NTP RTP timestamp (for all simulcast video streams) */ - guint32 video_first_rtp_ts[3]; - /*! \brief Last sent audio NTP timestamp */ - gint64 audio_last_ntp_ts; - /*! \brief Last sent audio RTP timestamp */ - guint32 audio_last_rtp_ts; - /*! \brief Last sent video NTP timestamp */ - gint64 video_last_ntp_ts; - /*! \brief Last sent video RTP timestamp */ - guint32 video_last_rtp_ts; + /*! \brief libnice ICE component state */ + guint state; + /*! \brief Monotonic time of when ICE has successfully connected */ + gint64 connected; + /*! \brief GLib list of libnice remote candidates for this component */ + GSList *candidates; + /*! \brief GLib list of local candidates for this component (summary) */ + GSList *local_candidates; + /*! \brief GLib list of remote candidates for this component (summary) */ + GSList *remote_candidates; + /*! \brief String representation of the selected pair as notified by libnice (foundations) */ + gchar *selected_pair; + /*! \brief Whether the setup of remote candidates for this component has started or not */ + gboolean process_started; + /*! \brief Timer to check when we should consider ICE as failed */ + GSource *icestate_source; + /*! \brief Time of when we first detected an ICE failed (we'll need this for the timer above) */ + gint64 icefailed_detected; + /*! \brief Re-transmission timer for DTLS */ + GSource *dtlsrt_source; + /*! \brief DTLS-SRTP stack */ + janus_dtls_srtp *dtls; /*! \brief SDES mid RTP extension ID */ gint mid_ext_id; /*! \brief RTP Stream extension ID, and the related rtx one */ @@ -448,7 +410,7 @@ struct janus_ice_stream { gint videoorientation_ext_id; /*! \brief Frame marking extension ID */ gint framemarking_ext_id; - /*! \brief Whether we do transport wide cc for video */ + /*! \brief Whether we do transport wide cc */ gboolean do_transport_wide_cc; /*! \brief Transport wide cc rtp ext ID */ gint transport_wide_cc_ext_id; @@ -466,6 +428,8 @@ struct janus_ice_stream { GSList *transport_wide_received_seq_nums; /*! \brief DTLS role of the server for this stream */ janus_dtls_role dtls_role; + /*! \brief Data exchanged for DTLS handshakes and messages */ + janus_ice_stats dtls_in_stats, dtls_out_stats; /*! \brief Hashing algorhitm used by the peer for the DTLS certificate (e.g., "SHA-256") */ gchar *remote_hashing; /*! \brief Hashed fingerprint of the peer's certificate, as parsed in SDP */ @@ -474,10 +438,17 @@ struct janus_ice_stream { gchar *ruser; /*! \brief The ICE password for this stream */ gchar *rpass; - /*! \brief GLib hash table of components (IDs are the keys) */ - GHashTable *components; - /*! \brief ICE component */ - janus_ice_component *component; + /*! \brief GLib hash table of media (m-line indexes are the keys) */ + GHashTable *media; + /*! \brief GLib hash table of media (SSRCs are the keys) */ + GHashTable *media_byssrc; + /*! \brief GLib hash table of media (mids are the keys) */ + GHashTable *media_bymid; + /*! \brief GLib hash table of media (media types are the keys) + * @note This is just a convenience hash table to track the very first audio + * or video m-line, in order to make it easier for plugins that don't do + * multistream. That said, we don't plan to keep it forever */ + GHashTable *media_bytype; /*! \brief Helper flag to avoid flooding the console with the same error all over again */ gboolean noerrorlog; /*! \brief Mutex to lock/unlock this stream */ @@ -489,44 +460,68 @@ struct janus_ice_stream { }; #define LAST_SEQS_MAX_LEN 160 -/*! \brief Janus ICE component */ -struct janus_ice_component { - /*! \brief Janus ICE stream this component belongs to */ - janus_ice_stream *stream; - /*! \brief libnice ICE stream ID */ - guint stream_id; - /*! \brief libnice ICE component ID */ - guint component_id; - /*! \brief libnice ICE component state */ - guint state; - /*! \brief Monotonic time of when this component has successfully connected */ - gint64 component_connected; - /*! \brief GLib list of libnice remote candidates for this component */ - GSList *candidates; - /*! \brief GLib list of local candidates for this component (summary) */ - GSList *local_candidates; - /*! \brief GLib list of remote candidates for this component (summary) */ - GSList *remote_candidates; - /*! \brief String representation of the selected pair as notified by libnice (foundations) */ - gchar *selected_pair; - /*! \brief Whether the setup of remote candidates for this component has started or not */ - gboolean process_started; - /*! \brief Timer to check when we should consider ICE as failed */ - GSource *icestate_source; - /*! \brief Time of when we first detected an ICE failed (we'll need this for the timer above) */ - gint64 icefailed_detected; - /*! \brief Re-transmission timer for DTLS */ - GSource *dtlsrt_source; - /*! \brief DTLS-SRTP stack */ - janus_dtls_srtp *dtls; - /*! \brief Whether we should do NACKs (in or out) for audio */ - gboolean do_audio_nacks; - /*! \brief Whether we should do NACKs (in or out) for video */ - gboolean do_video_nacks; +/*! \brief A single media in a PeerConnection */ +struct janus_ice_peerconnection_medium { + /*! \brief WebRTC PeerConnection this m-line belongs to */ + janus_ice_peerconnection *pc; + /*! \brief Type of this medium */ + janus_media_type type; + /*! \brief Index of this medium in the media list */ + int mindex; + /*! \brief Media ID */ + char *mid; + /*! \brief SSRC of the server for this medium */ + guint32 ssrc; + /*! \brief Retransmission SSRC of the server for this medium */ + guint32 ssrc_rtx; + /*! \brief SSRC(s) of the peer for this medium (may be simulcasting) */ + guint32 ssrc_peer[3], ssrc_peer_new[3], ssrc_peer_orig[3], ssrc_peer_temp; + /*! \brief Retransmissions SSRC(s) of the peer for this medium (may be simulcasting) */ + guint32 ssrc_peer_rtx[3], ssrc_peer_rtx_new[3], ssrc_peer_rtx_orig[3]; + /*! \brief Array of RTP Stream IDs (for Firefox simulcasting, if enabled) */ + char *rid[3]; + /*! \brief Whether the order of the rids in the SDP will be h-m-l (TRUE) or l-m-h (FALSE) */ + gboolean rids_hml; + /*! \brief Whether we should use the legacy simulcast syntax (a=simulcast:recv rid=..) or the proper one (a=simulcast:recv ..) */ + gboolean legacy_rid; + /*! \brief RTP switching context(s) in case of renegotiations (audio+video and/or simulcast) */ + janus_rtp_switching_context rtp_ctx[3]; + /*! \brief List of payload types we can expect */ + GList *payload_types; + /*! \brief Mapping of rtx payload types to actual media-related packet types */ + GHashTable *rtx_payload_types; + /*! \brief Mapping of payload types to their clock rates, as advertised in the SDP */ + GHashTable *clock_rates; + /*! \brief RTP payload types for this medium */ + gint payload_type, rtx_payload_type; + /*! \brief Codec used in this medium */ + char *codec; + /*! \brief Pointer to function to check if a packet is a keyframe (depends on negotiated codec; video only) */ + gboolean (* video_is_keyframe)(const char* buffer, int len); + /*! \brief Media direction */ + gboolean send, recv; + /*! \brief RTCP context(s) for the medium (may be simulcasting) */ + janus_rtcp_context *rtcp_ctx[3]; + /*! \brief Size of the NACK queue (in ms), dynamically updated per the RTT */ + uint16_t nack_queue_ms; + /*! \brief Map(s) of the NACKed packets (to track retransmissions and avoid duplicates) */ + GHashTable *rtx_nacked[3]; + /*! \brief Map of the pending NACKed cleanup callback */ + GHashTable *pending_nacked_cleanup; + /*! \brief First received NTP timestamp */ + gint64 first_ntp_ts[3]; + /*! \brief First received RTP timestamp */ + guint32 first_rtp_ts[3]; + /*! \brief Last sent NTP timestamp */ + gint64 last_ntp_ts; + /*! \brief Last sent RTP timestamp */ + guint32 last_rtp_ts; + /*! \brief Whether we should do NACKs (in or out) for this medium */ + gboolean do_nacks; /*! \brief List of previously sent janus_rtp_packet RTP packets, in case we receive NACKs */ - GQueue *audio_retransmit_buffer, *video_retransmit_buffer; + GQueue *retransmit_buffer; /*! \brief HashTable of retransmittable sequence numbers, in case we receive NACKs */ - GHashTable *audio_retransmit_seqs, *video_retransmit_seqs; + GHashTable *retransmit_seqs; /*! \brief Current sequence number for the RFC4588 rtx SSRC session */ guint16 rtx_seq_number; /*! \brief Last time a log message about sending retransmits was printed */ @@ -537,23 +532,27 @@ struct janus_ice_component { gint64 nack_sent_log_ts; /*! \brief Number of NACKs sent since last log message */ guint nack_sent_recent_cnt; - /*! \brief List of recently received audio sequence numbers (as a support to NACK generation) */ - janus_seq_info *last_seqs_audio; - /*! \brief List of recently received video sequence numbers (as a support to NACK generation, for each simulcast SSRC) */ - janus_seq_info *last_seqs_video[3]; + /*! \brief List of recently received sequence numbers (as a support to NACK generation, for each simulcast SSRC) */ + janus_seq_info *last_seqs[3]; /*! \brief Stats for incoming data (audio/video/data) */ janus_ice_stats in_stats; /*! \brief Stats for outgoing data (audio/video/data) */ janus_ice_stats out_stats; /*! \brief Helper flag to avoid flooding the console with the same error all over again */ gboolean noerrorlog; - /*! \brief Mutex to lock/unlock this component */ + /*! \brief Mutex to lock/unlock this medium */ janus_mutex mutex; /*! \brief Atomic flag to check if this instance has been destroyed */ volatile gint destroyed; /*! \brief Reference counter for this instance */ janus_refcount ref; }; +/*! \brief Method to quickly create a medium to be added to a handle PeerConnection + * @note This will autogenerate SSRCs, if needed + * @param[in] handle The Janus handle instance to add the medium to + * @param[in] type The medium type + * @returns A pointer to the new medium, if successful, or NULL otherwise */ +janus_ice_peerconnection_medium *janus_ice_peerconnection_medium_create(janus_ice_handle *handle, janus_media_type type); /*! \brief Helper to handle pending trickle candidates (e.g., when we're still waiting for an offer) */ struct janus_ice_trickle { @@ -612,12 +611,9 @@ gint janus_ice_handle_destroy(void *core_session, janus_ice_handle *handle); * @param[in] handle The Janus ICE handle instance managing the WebRTC PeerConnection to hangup * @param[in] reason A description of why this happened */ void janus_ice_webrtc_hangup(janus_ice_handle *handle, const char *reason); -/*! \brief Method to only free resources related to a specific ICE stream allocated by a Janus ICE handle - * @param[in] stream The Janus ICE stream instance to free */ -void janus_ice_stream_destroy(janus_ice_stream *stream); -/*! \brief Method to only free resources related to a specific ICE component allocated by a Janus ICE handle +/*! \brief Method to only free resources related to a specific Webrtc PeerConnection allocated by a Janus ICE handle * @param[in] component The Janus ICE component instance to free */ -void janus_ice_component_destroy(janus_ice_component *component); +void janus_ice_peerconnection_destroy(janus_ice_peerconnection *pc); ///@} @@ -668,12 +664,9 @@ void janus_ice_notify_data_ready(janus_ice_handle *handle); /*! \brief Method to locally set up the ICE candidates (initialization and gathering) * @param[in] handle The Janus ICE handle this method refers to * @param[in] offer Whether this is for an OFFER or an ANSWER - * @param[in] audio Whether audio is enabled - * @param[in] video Whether video is enabled - * @param[in] data Whether SCTP data channels are enabled * @param[in] trickle Whether ICE trickling is supported or not * @returns 0 in case of success, a negative integer otherwise */ -int janus_ice_setup_local(janus_ice_handle *handle, int offer, int audio, int video, int data, int trickle); +int janus_ice_setup_local(janus_ice_handle *handle, gboolean offer, gboolean trickle); /*! \brief Method to add local candidates to a janus_sdp SDP object representation * @param[in] handle The Janus ICE handle this method refers to * @param[in] mline The Janus SDP m-line object to add candidates to @@ -691,9 +684,8 @@ void janus_ice_add_remote_candidate(janus_ice_handle *handle, NiceCandidate *c); void janus_ice_setup_remote_candidates(janus_ice_handle *handle, guint stream_id, guint component_id); /*! \brief Callback to be notified when the DTLS handshake for a specific component has been completed * \details This method also decides when to notify attached plugins about the availability of a reliable PeerConnection - * @param[in] handle The Janus ICE handle this callback refers to - * @param[in] component The Janus ICE component that is now ready to be used */ -void janus_ice_dtls_handshake_done(janus_ice_handle *handle, janus_ice_component *component); + * @param[in] handle The Janus ICE handle this callback refers to */ +void janus_ice_dtls_handshake_done(janus_ice_handle *handle); /*! \brief Method to restart ICE and the connectivity checks * @param[in] handle The Janus ICE handle this method refers to */ void janus_ice_restart(janus_ice_handle *handle); diff --git a/janus.c b/janus.c index bdfe68b230..a6749484b4 100644 --- a/janus.c +++ b/janus.c @@ -190,8 +190,8 @@ static struct janus_json_parameter teststun_parameters[] = { }; /* Admin/Monitor helpers */ -json_t *janus_admin_stream_summary(janus_ice_stream *stream); -json_t *janus_admin_component_summary(janus_ice_component *component); +json_t *janus_admin_peerconnection_summary(janus_ice_peerconnection *pc); +json_t *janus_admin_peerconnection_medium_summary(janus_ice_peerconnection_medium *medium); /* IP addresses */ @@ -920,7 +920,7 @@ static int janus_request_check_secret(janus_request *request, guint64 session_id return 0; } -static void janus_request_ice_handle_answer(janus_ice_handle *handle, int audio, int video, int data, char *jsep_sdp) { +static void janus_request_ice_handle_answer(janus_ice_handle *handle, char *jsep_sdp) { /* We got our answer */ janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_PROCESSING_OFFER); /* Any pending trickles? */ @@ -971,7 +971,7 @@ static void janus_request_ice_handle_answer(janus_ice_handle *handle, int audio, } } - gboolean candidates_found = (handle->stream && handle->stream->component && g_slist_length(handle->stream->component->candidates) > 0); + gboolean candidates_found = (handle->pc && g_slist_length(handle->pc->candidates) > 0); /* This was an answer, check if it's time to start ICE */ if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_TRICKLE) && !candidates_found) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- ICE Trickling is supported by the browser, waiting for remote candidates...\n", handle->handle_id); @@ -1368,17 +1368,17 @@ int janus_process_incoming_request(janus_request *request) { renegotiation = janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_NEGOTIATED); /* Check the JSEP type */ janus_mutex_lock(&handle->mutex); - int offer = 0; + gboolean offer = FALSE; if(!strcasecmp(jsep_type, "offer")) { - offer = 1; + offer = TRUE; janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_PROCESSING_OFFER); janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_GOT_OFFER); janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_GOT_ANSWER); } else if(!strcasecmp(jsep_type, "answer")) { + offer = FALSE; janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_GOT_ANSWER); if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_GOT_OFFER)) janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_NEGOTIATED); - offer = 0; } else { /* TODO Handle other message types as well */ ret = janus_process_error(request, session_id, transaction_text, JANUS_ERROR_JSEP_UNKNOWN_TYPE, "JSEP error: unknown message type '%s'", jsep_type); @@ -1408,32 +1408,14 @@ int janus_process_incoming_request(janus_request *request) { janus_events_notify_handlers(JANUS_EVENT_TYPE_JSEP, JANUS_EVENT_SUBTYPE_NONE, session_id, handle_id, handle->opaque_id, "remote", jsep_type, jsep_sdp); } - /* FIXME We're only handling single audio/video lines for now... */ - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Audio %s been negotiated, Video %s been negotiated, SCTP/DataChannels %s been negotiated\n", - handle->handle_id, - audio ? "has" : "has NOT", - video ? "has" : "has NOT", - data ? "have" : "have NOT"); - if(audio > 1) { - JANUS_LOG(LOG_WARN, "[%"SCNu64"] More than one audio line? only going to negotiate one...\n", handle->handle_id); - } - if(video > 1) { - JANUS_LOG(LOG_WARN, "[%"SCNu64"] More than one video line? only going to negotiate one...\n", handle->handle_id); - } - if(data > 1) { - JANUS_LOG(LOG_WARN, "[%"SCNu64"] More than one data line? only going to negotiate one...\n", handle->handle_id); - } -#ifndef HAVE_SCTP - if(data) { - JANUS_LOG(LOG_WARN, "[%"SCNu64"] -- DataChannels have been negotiated, but support for them has not been compiled...\n", handle->handle_id); - } -#endif + JANUS_LOG(LOG_VERB, "[%"SCNu64"] There are %d audio, %d video and %d data m-lines\n", + handle->handle_id, audio, video, data); /* We behave differently if it's a new session or an update... */ if(!renegotiation) { /* New session */ if(offer) { /* Setup ICE locally (we received an offer) */ - if(janus_ice_setup_local(handle, offer, audio, video, data, do_trickle) < 0) { + if(janus_ice_setup_local(handle, offer, do_trickle) < 0) { JANUS_LOG(LOG_ERR, "Error setting ICE locally\n"); janus_sdp_destroy(parsed_sdp); g_free(jsep_type); @@ -1454,7 +1436,7 @@ int janus_process_incoming_request(janus_request *request) { goto jsondone; } } - if(janus_sdp_process(handle, parsed_sdp, rids_hml, FALSE) < 0) { + if(janus_sdp_process_remote(handle, parsed_sdp, rids_hml, FALSE) < 0) { JANUS_LOG(LOG_ERR, "Error processing SDP\n"); janus_sdp_destroy(parsed_sdp); g_free(jsep_type); @@ -1470,29 +1452,29 @@ int janus_process_incoming_request(janus_request *request) { } else { janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_TRICKLE); } - janus_request_ice_handle_answer(handle, audio, video, data, jsep_sdp); + janus_request_ice_handle_answer(handle, jsep_sdp); } else { /* Check if the mid RTP extension is being negotiated */ - handle->stream->mid_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_MID); + handle->pc->mid_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_MID); /* Check if the RTP Stream ID extension is being negotiated */ - handle->stream->rid_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_RID); - handle->stream->ridrtx_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_REPAIRED_RID); + handle->pc->rid_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_RID); + handle->pc->ridrtx_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_REPAIRED_RID); /* Check if the audio level ID extension is being negotiated */ - handle->stream->audiolevel_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_AUDIO_LEVEL); + handle->pc->audiolevel_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_AUDIO_LEVEL); /* Check if the video orientation ID extension is being negotiated */ - handle->stream->videoorientation_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION); + handle->pc->videoorientation_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION); /* Check if the frame marking ID extension is being negotiated */ - handle->stream->framemarking_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_FRAME_MARKING); + handle->pc->framemarking_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_FRAME_MARKING); /* Check if transport wide CC is supported */ int transport_wide_cc_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC); - handle->stream->do_transport_wide_cc = transport_wide_cc_ext_id > 0 ? TRUE : FALSE; - handle->stream->transport_wide_cc_ext_id = transport_wide_cc_ext_id; + handle->pc->do_transport_wide_cc = transport_wide_cc_ext_id > 0 ? TRUE : FALSE; + handle->pc->transport_wide_cc_ext_id = transport_wide_cc_ext_id; } } else { /* FIXME This is a renegotiation: we can currently only handle simple changes in media * direction and ICE restarts: anything more complex than that will result in an error */ JANUS_LOG(LOG_INFO, "[%"SCNu64"] Negotiation update, checking what changed...\n", handle->handle_id); - if(janus_sdp_process(handle, parsed_sdp, rids_hml, TRUE) < 0) { + if(janus_sdp_process_remote(handle, parsed_sdp, rids_hml, TRUE) < 0) { JANUS_LOG(LOG_ERR, "Error processing SDP\n"); janus_sdp_destroy(parsed_sdp); g_free(jsep_type); @@ -1504,9 +1486,9 @@ int janus_process_incoming_request(janus_request *request) { if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ICE_RESTART)) { JANUS_LOG(LOG_INFO, "[%"SCNu64"] Restarting ICE...\n", handle->handle_id); /* Update remote credentials for ICE */ - if(handle->stream) { - nice_agent_set_remote_credentials(handle->agent, handle->stream->stream_id, - handle->stream->ruser, handle->stream->rpass); + if(handle->pc) { + nice_agent_set_remote_credentials(handle->agent, handle->pc->stream_id, + handle->pc->ruser, handle->pc->rpass); } /* FIXME We only need to do that for offers: if it's an answer, we did that already */ if(offer) { @@ -1523,12 +1505,11 @@ int janus_process_incoming_request(janus_request *request) { if(!offer) { /* Were datachannels just added? */ if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_DATA_CHANNELS)) { - janus_ice_stream *stream = handle->stream; - if(stream != NULL && stream->component != NULL - && stream->component->dtls != NULL && stream->component->dtls->sctp == NULL) { + janus_ice_peerconnection *pc = handle->pc; + if(pc != NULL && pc->dtls != NULL && pc->dtls->sctp == NULL) { /* Create SCTP association as well */ JANUS_LOG(LOG_WARN, "[%"SCNu64"] Creating datachannels...\n", handle->handle_id); - janus_dtls_srtp_create_sctp(stream->component->dtls); + janus_dtls_srtp_create_sctp(pc->dtls); } } } @@ -1536,20 +1517,20 @@ int janus_process_incoming_request(janus_request *request) { /* Check if renegotiating has added new RTP extensions */ if(offer) { /* Check if the mid RTP extension is being negotiated */ - handle->stream->mid_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_MID); + handle->pc->mid_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_MID); /* Check if the RTP Stream ID extension is being negotiated */ - handle->stream->rid_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_RID); - handle->stream->ridrtx_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_REPAIRED_RID); + handle->pc->rid_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_RID); + handle->pc->ridrtx_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_REPAIRED_RID); /* Check if the audio level ID extension is being negotiated */ - handle->stream->audiolevel_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_AUDIO_LEVEL); + handle->pc->audiolevel_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_AUDIO_LEVEL); /* Check if the video orientation ID extension is being negotiated */ - handle->stream->videoorientation_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION); + handle->pc->videoorientation_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION); /* Check if the frame marking ID extension is being negotiated */ - handle->stream->framemarking_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_FRAME_MARKING); + handle->pc->framemarking_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_FRAME_MARKING); /* Check if transport wide CC is supported */ int transport_wide_cc_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC); - handle->stream->do_transport_wide_cc = transport_wide_cc_ext_id > 0 ? TRUE : FALSE; - handle->stream->transport_wide_cc_ext_id = transport_wide_cc_ext_id; + handle->pc->do_transport_wide_cc = transport_wide_cc_ext_id > 0 ? TRUE : FALSE; + handle->pc->transport_wide_cc_ext_id = transport_wide_cc_ext_id; } } char *tmp = handle->remote_sdp; @@ -1587,34 +1568,45 @@ int janus_process_incoming_request(janus_request *request) { json_t *body_jsep = NULL; if(jsep_sdp_stripped) { body_jsep = json_pack("{ssss}", "type", jsep_type, "sdp", jsep_sdp_stripped); - /* Check if simulcasting is enabled */ - if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO)) { - if(handle->stream && (handle->stream->rid[0] || handle->stream->video_ssrc_peer[1])) { - json_t *simulcast = json_object(); + /* Check if simulcasting is enabled in one of the media streams */ + json_t *simulcast = NULL; + janus_ice_peerconnection_medium *medium = NULL; + uint mi=0; + for(mi=0; mipc->media); mi++) { + medium = g_hash_table_lookup(handle->pc->media, GUINT_TO_POINTER(mi)); + if(medium && (medium->ssrc_peer[1] || medium->rid[0])) { + if(simulcast == NULL) + simulcast = json_array(); + json_t *msc = json_object(); + json_object_set_new(msc, "mindex", json_integer(medium->mindex)); + if(medium->mid != NULL) + json_object_set_new(msc, "mid", json_string(medium->mid)); /* If we have rids, pass those, otherwise pass the SSRCs */ - if(handle->stream->rid[0]) { + if(medium->rid[0]) { json_t *rids = json_array(); - if(handle->stream->rid[2]) - json_array_append_new(rids, json_string(handle->stream->rid[2])); - if(handle->stream->rid[1]) - json_array_append_new(rids, json_string(handle->stream->rid[1])); - json_array_append_new(rids, json_string(handle->stream->rid[0])); + if(medium->rid[2]) + json_array_append_new(rids, json_string(medium->rid[2])); + if(medium->rid[1]) + json_array_append_new(rids, json_string(medium->rid[1])); + json_array_append_new(rids, json_string(medium->rid[0])); json_object_set_new(simulcast, "rids", rids); - json_object_set_new(simulcast, "rid-ext", json_integer(handle->stream->rid_ext_id)); + json_object_set_new(simulcast, "rid-ext", json_integer(medium->pc->rid_ext_id)); } else { json_t *ssrcs = json_array(); - json_array_append_new(ssrcs, json_integer(handle->stream->video_ssrc_peer[0])); - if(handle->stream->video_ssrc_peer[1]) - json_array_append_new(ssrcs, json_integer(handle->stream->video_ssrc_peer[1])); - if(handle->stream->video_ssrc_peer[2]) - json_array_append_new(ssrcs, json_integer(handle->stream->video_ssrc_peer[2])); - json_object_set_new(simulcast, "ssrcs", ssrcs); + json_array_append_new(ssrcs, json_integer(medium->ssrc_peer[0])); + if(medium->ssrc_peer[1]) + json_array_append_new(ssrcs, json_integer(medium->ssrc_peer[1])); + if(medium->ssrc_peer[2]) + json_array_append_new(ssrcs, json_integer(medium->ssrc_peer[2])); + json_object_set_new(msc, "ssrcs", ssrcs); } - if(handle->stream->framemarking_ext_id > 0) - json_object_set_new(simulcast, "framemarking-ext", json_integer(handle->stream->framemarking_ext_id)); - json_object_set_new(body_jsep, "simulcast", simulcast); + if(medium->pc && medium->pc->framemarking_ext_id > 0) + json_object_set_new(msc, "framemarking-ext", json_integer(medium->pc->framemarking_ext_id)); + json_array_append_new(simulcast, msc); } } + if(simulcast) + json_object_set_new(body_jsep, "simulcast", simulcast); /* Check if this is a renegotiation or update */ if(renegotiation) json_object_set_new(body_jsep, "update", json_true()); @@ -1702,7 +1694,7 @@ int janus_process_incoming_request(janus_request *request) { janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_TRICKLE); } /* Is there any stream ready? this trickle may get here before the SDP it relates to */ - if(handle->stream == NULL) { + if(handle->pc == NULL) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] No stream, queueing this trickle as it got here before the SDP...\n", handle->handle_id); /* Enqueue this trickle candidate(s), we'll process this later */ janus_ice_trickle *early_trickle = janus_ice_trickle_new(transaction_text, candidate ? candidate : candidates); @@ -2854,13 +2846,11 @@ int janus_process_incoming_admin_request(janus_request *request) { json_object_set_new(info, "pcap-file", json_string(handle->text2pcap->filename)); } } - json_t *streams = json_array(); - if(handle->stream) { - json_t *s = janus_admin_stream_summary(handle->stream); - if(s) - json_array_append_new(streams, s); + if(handle->pc) { + json_t *p = janus_admin_peerconnection_summary(handle->pc); + if(p) + json_object_set_new(info, "webrtc", p); } - json_object_set_new(info, "streams", streams); info_done: janus_mutex_unlock(&handle->mutex); /* Prepare JSON reply */ @@ -2927,193 +2917,54 @@ int janus_process_error(janus_request *request, uint64_t session_id, const char } /* Admin/monitor helpers */ -json_t *janus_admin_stream_summary(janus_ice_stream *stream) { - if(stream == NULL) - return NULL; - json_t *s = json_object(); - json_object_set_new(s, "id", json_integer(stream->stream_id)); - json_object_set_new(s, "ready", json_integer(stream->cdone)); - json_t *ss = json_object(); - if(stream->audio_ssrc) - json_object_set_new(ss, "audio", json_integer(stream->audio_ssrc)); - if(stream->video_ssrc) - json_object_set_new(ss, "video", json_integer(stream->video_ssrc)); - if(stream->video_ssrc_rtx) - json_object_set_new(ss, "video-rtx", json_integer(stream->video_ssrc_rtx)); - if(stream->audio_ssrc_peer) - json_object_set_new(ss, "audio-peer", json_integer(stream->audio_ssrc_peer)); - if(stream->video_ssrc_peer[0]) - json_object_set_new(ss, "video-peer", json_integer(stream->video_ssrc_peer[0])); - if(stream->video_ssrc_peer[1]) - json_object_set_new(ss, "video-peer-sim-1", json_integer(stream->video_ssrc_peer[1])); - if(stream->video_ssrc_peer[2]) - json_object_set_new(ss, "video-peer-sim-2", json_integer(stream->video_ssrc_peer[2])); - if(stream->video_ssrc_peer_rtx[0]) - json_object_set_new(ss, "video-peer-rtx", json_integer(stream->video_ssrc_peer_rtx[0])); - if(stream->video_ssrc_peer_rtx[1]) - json_object_set_new(ss, "video-peer-sim-1-rtx", json_integer(stream->video_ssrc_peer_rtx[1])); - if(stream->video_ssrc_peer_rtx[2]) - json_object_set_new(ss, "video-peer-sim-2-rtx", json_integer(stream->video_ssrc_peer_rtx[2])); - json_object_set_new(s, "ssrc", ss); - if(stream->rid[0] && stream->rid_ext_id > 0) { - json_t *sr = json_object(); - json_t *rid = json_array(); - if(stream->rid[2]) - json_array_append_new(rid, json_string(stream->rid[2])); - if(stream->rid[1]) - json_array_append_new(rid, json_string(stream->rid[1])); - json_array_append_new(rid, json_string(stream->rid[0])); - json_object_set_new(sr, "rid", rid); - json_object_set_new(sr, "rid-ext-id", json_integer(stream->rid_ext_id)); - if(stream->ridrtx_ext_id > 0) - json_object_set_new(sr, "ridrtx-ext-id", json_integer(stream->ridrtx_ext_id)); - json_object_set_new(sr, "rid-order", json_string(stream->rids_hml ? "hml" : "lmh")); - if(stream->legacy_rid) - json_object_set_new(sr, "rid-syntax", json_string("legacy")); - json_object_set_new(s, "rid-simulcast", sr); - } - json_t *sd = json_object(); - json_object_set_new(sd, "audio-send", stream->audio_send ? json_true() : json_false()); - json_object_set_new(sd, "audio-recv", stream->audio_recv ? json_true() : json_false()); - json_object_set_new(sd, "video-send", stream->video_send ? json_true() : json_false()); - json_object_set_new(sd, "video-recv", stream->video_recv ? json_true() : json_false()); - json_object_set_new(s, "direction", sd); - if(stream->audio_payload_type > -1 || stream->video_payload_type > -1) { - json_t *sc = json_object(); - if(stream->audio_payload_type > -1) - json_object_set_new(sc, "audio-pt", json_integer(stream->audio_payload_type)); - if(stream->audio_codec != NULL) - json_object_set_new(sc, "audio-codec", json_string(stream->audio_codec)); - if(stream->video_payload_type > -1) - json_object_set_new(sc, "video-pt", json_integer(stream->video_payload_type)); - if(stream->video_rtx_payload_type > -1) - json_object_set_new(sc, "video-rtx-pt", json_integer(stream->video_rtx_payload_type)); - if(stream->video_codec != NULL) - json_object_set_new(sc, "video-codec", json_string(stream->video_codec)); - json_object_set_new(s, "codecs", sc); - } - json_t *se = json_object(); - if(stream->mid_ext_id > 0) - json_object_set_new(se, JANUS_RTP_EXTMAP_MID, json_integer(stream->mid_ext_id)); - if(stream->rid_ext_id > 0) - json_object_set_new(se, JANUS_RTP_EXTMAP_RID, json_integer(stream->rid_ext_id)); - if(stream->ridrtx_ext_id > 0) - json_object_set_new(se, JANUS_RTP_EXTMAP_REPAIRED_RID, json_integer(stream->ridrtx_ext_id)); - if(stream->transport_wide_cc_ext_id > 0) - json_object_set_new(se, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC, json_integer(stream->transport_wide_cc_ext_id)); - if(stream->audiolevel_ext_id > 0) - json_object_set_new(se, JANUS_RTP_EXTMAP_AUDIO_LEVEL, json_integer(stream->audiolevel_ext_id)); - if(stream->videoorientation_ext_id > 0) - json_object_set_new(se, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION, json_integer(stream->videoorientation_ext_id)); - json_object_set_new(s, "extensions", se); - json_t *bwe = json_object(); - json_object_set_new(bwe, "twcc", stream->do_transport_wide_cc ? json_true() : json_false()); - if(stream->transport_wide_cc_ext_id > 0) - json_object_set_new(bwe, "twcc-ext-id", json_integer(stream->transport_wide_cc_ext_id)); - json_object_set_new(s, "bwe", bwe); - json_object_set_new(s, "nack-queue-ms", json_integer(stream->nack_queue_ms)); - json_t *components = json_array(); - if(stream->component) { - json_t *c = janus_admin_component_summary(stream->component); - if(c) - json_array_append_new(components, c); - } - json_t *rtcp_stats = NULL; - if(stream->audio_rtcp_ctx != NULL) { - rtcp_stats = json_object(); - json_t *audio_rtcp_stats = json_object(); - json_object_set_new(audio_rtcp_stats, "base", json_integer(stream->audio_rtcp_ctx->tb)); - json_object_set_new(audio_rtcp_stats, "rtt", json_integer(janus_rtcp_context_get_rtt(stream->audio_rtcp_ctx))); - json_object_set_new(audio_rtcp_stats, "lost", json_integer(janus_rtcp_context_get_lost_all(stream->audio_rtcp_ctx, FALSE))); - json_object_set_new(audio_rtcp_stats, "lost-by-remote", json_integer(janus_rtcp_context_get_lost_all(stream->audio_rtcp_ctx, TRUE))); - json_object_set_new(audio_rtcp_stats, "jitter-local", json_integer(janus_rtcp_context_get_jitter(stream->audio_rtcp_ctx, FALSE))); - json_object_set_new(audio_rtcp_stats, "jitter-remote", json_integer(janus_rtcp_context_get_jitter(stream->audio_rtcp_ctx, TRUE))); - json_object_set_new(audio_rtcp_stats, "in-link-quality", json_integer(janus_rtcp_context_get_in_link_quality(stream->audio_rtcp_ctx))); - json_object_set_new(audio_rtcp_stats, "in-media-link-quality", json_integer(janus_rtcp_context_get_in_media_link_quality(stream->audio_rtcp_ctx))); - json_object_set_new(audio_rtcp_stats, "out-link-quality", json_integer(janus_rtcp_context_get_out_link_quality(stream->audio_rtcp_ctx))); - json_object_set_new(audio_rtcp_stats, "out-media-link-quality", json_integer(janus_rtcp_context_get_out_media_link_quality(stream->audio_rtcp_ctx))); - json_object_set_new(rtcp_stats, "audio", audio_rtcp_stats); - } - int vindex=0; - for(vindex=0; vindex<3; vindex++) { - if(stream->video_rtcp_ctx[vindex] != NULL) { - if(rtcp_stats == NULL) - rtcp_stats = json_object(); - json_t *video_rtcp_stats = json_object(); - json_object_set_new(video_rtcp_stats, "base", json_integer(stream->video_rtcp_ctx[vindex]->tb)); - if(vindex == 0) - json_object_set_new(video_rtcp_stats, "rtt", json_integer(janus_rtcp_context_get_rtt(stream->video_rtcp_ctx[vindex]))); - json_object_set_new(video_rtcp_stats, "lost", json_integer(janus_rtcp_context_get_lost_all(stream->video_rtcp_ctx[vindex], FALSE))); - json_object_set_new(video_rtcp_stats, "lost-by-remote", json_integer(janus_rtcp_context_get_lost_all(stream->video_rtcp_ctx[vindex], TRUE))); - json_object_set_new(video_rtcp_stats, "jitter-local", json_integer(janus_rtcp_context_get_jitter(stream->video_rtcp_ctx[vindex], FALSE))); - json_object_set_new(video_rtcp_stats, "jitter-remote", json_integer(janus_rtcp_context_get_jitter(stream->video_rtcp_ctx[vindex], TRUE))); - json_object_set_new(video_rtcp_stats, "in-link-quality", json_integer(janus_rtcp_context_get_in_link_quality(stream->video_rtcp_ctx[vindex]))); - json_object_set_new(video_rtcp_stats, "in-media-link-quality", json_integer(janus_rtcp_context_get_in_media_link_quality(stream->video_rtcp_ctx[vindex]))); - json_object_set_new(video_rtcp_stats, "out-link-quality", json_integer(janus_rtcp_context_get_out_link_quality(stream->video_rtcp_ctx[vindex]))); - json_object_set_new(video_rtcp_stats, "out-media-link-quality", json_integer(janus_rtcp_context_get_out_media_link_quality(stream->video_rtcp_ctx[vindex]))); - if(vindex == 0) - json_object_set_new(rtcp_stats, "video", video_rtcp_stats); - else if(vindex == 1) - json_object_set_new(rtcp_stats, "video-sim1", video_rtcp_stats); - else - json_object_set_new(rtcp_stats, "video-sim2", video_rtcp_stats); - } - } - if(rtcp_stats != NULL) - json_object_set_new(s, "rtcp_stats", rtcp_stats); - json_object_set_new(s, "components", components); - return s; -} - -json_t *janus_admin_component_summary(janus_ice_component *component) { - if(component == NULL) +json_t *janus_admin_peerconnection_summary(janus_ice_peerconnection *pc) { + if(pc == NULL) return NULL; - janus_ice_handle *handle = component->stream ? component->stream->handle : NULL; - json_t *c = json_object(); - json_object_set_new(c, "id", json_integer(component->component_id)); - json_object_set_new(c, "state", json_string(janus_get_ice_state_name(component->state))); - if(component->icefailed_detected) { - json_object_set_new(c, "failed-detected", json_integer(component->icefailed_detected)); - json_object_set_new(c, "icetimer-started", component->icestate_source ? json_true() : json_false()); - } - if(component->component_connected > 0) - json_object_set_new(c, "connected", json_integer(component->component_connected)); - if(component->local_candidates) { + json_t *w = json_object(); + json_t *i = json_object(); + json_object_set_new(i, "stream_id", json_integer(pc->stream_id)); + json_object_set_new(i, "component_id", json_integer(pc->component_id)); + json_object_set_new(i, "state", json_string(janus_get_ice_state_name(pc->state))); + if(pc->icefailed_detected) { + json_object_set_new(i, "failed-detected", json_integer(pc->icefailed_detected)); + json_object_set_new(i, "icetimer-started", pc->icestate_source ? json_true() : json_false()); + } + if(pc->connected > 0) + json_object_set_new(i, "connected", json_integer(pc->connected)); + if(pc->local_candidates) { json_t *cs = json_array(); - GSList *candidates = component->local_candidates, *i = NULL; - for (i = candidates; i; i = i->next) { - gchar *lc = (gchar *) i->data; + GSList *candidates = pc->local_candidates, *c = NULL; + for (c = candidates; c; c = c->next) { + gchar *lc = (gchar *) c->data; if(lc) json_array_append_new(cs, json_string(lc)); } - json_object_set_new(c, "local-candidates", cs); + json_object_set_new(i, "local-candidates", cs); } - if(component->remote_candidates) { + if(pc->remote_candidates) { json_t *cs = json_array(); - GSList *candidates = component->remote_candidates, *i = NULL; - for (i = candidates; i; i = i->next) { - gchar *rc = (gchar *) i->data; + GSList *candidates = pc->remote_candidates, *c = NULL; + for (c = candidates; c; c = c->next) { + gchar *rc = (gchar *) c->data; if(rc) json_array_append_new(cs, json_string(rc)); } - json_object_set_new(c, "remote-candidates", cs); + json_object_set_new(i, "remote-candidates", cs); } - if(component->selected_pair) { - json_object_set_new(c, "selected-pair", json_string(component->selected_pair)); + if(pc->selected_pair) { + json_object_set_new(i, "selected-pair", json_string(pc->selected_pair)); } + json_object_set_new(i, "ready", json_integer(pc->cdone)); + json_object_set_new(w, "ice", i); json_t *d = json_object(); - json_t *in_stats = json_object(); - json_t *out_stats = json_object(); - if(component->dtls) { - janus_dtls_srtp *dtls = component->dtls; + if(pc->dtls) { + janus_dtls_srtp *dtls = pc->dtls; json_object_set_new(d, "fingerprint", json_string(janus_dtls_get_local_fingerprint())); - if(component->stream) { - if(component->stream->remote_fingerprint) - json_object_set_new(d, "remote-fingerprint", json_string(component->stream->remote_fingerprint)); - if(component->stream->remote_hashing) - json_object_set_new(d, "remote-fingerprint-hash", json_string(component->stream->remote_hashing)); - json_object_set_new(d, "dtls-role", json_string(janus_get_dtls_srtp_role(component->stream->dtls_role))); - } + if(pc->remote_fingerprint) + json_object_set_new(d, "remote-fingerprint", json_string(pc->remote_fingerprint)); + if(pc->remote_hashing) + json_object_set_new(d, "remote-fingerprint-hash", json_string(pc->remote_hashing)); + json_object_set_new(d, "dtls-role", json_string(janus_get_dtls_srtp_role(pc->dtls_role))); json_object_set_new(d, "dtls-state", json_string(janus_get_dtls_srtp_state(dtls->dtls_state))); json_object_set_new(d, "retransmissions", json_integer(dtls->retransmissions)); json_object_set_new(d, "valid", dtls->srtp_valid ? json_true() : json_false()); @@ -3124,64 +2975,193 @@ json_t *janus_admin_component_summary(janus_ice_component *component) { json_object_set_new(d, "handshake-started", json_integer(dtls->dtls_started)); if(dtls->dtls_connected > 0) json_object_set_new(d, "connected", json_integer(dtls->dtls_connected)); - if(handle && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AUDIO)) { - json_object_set_new(in_stats, "audio_packets", json_integer(component->in_stats.audio.packets)); - json_object_set_new(in_stats, "audio_bytes", json_integer(component->in_stats.audio.bytes)); - json_object_set_new(in_stats, "audio_bytes_lastsec", json_integer(component->in_stats.audio.bytes_lastsec)); - json_object_set_new(in_stats, "do_audio_nacks", component->do_audio_nacks ? json_true() : json_false()); - if(component->do_audio_nacks) { - json_object_set_new(in_stats, "audio_nacks", json_integer(component->in_stats.audio.nacks)); - if(component->stream && component->stream->audio_rtcp_ctx) - json_object_set_new(in_stats, "audio_retransmissions", json_integer(component->stream->audio_rtcp_ctx->retransmitted)); - } - } - if(handle && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO)) { - int vindex=0; - for(vindex=0; vindex<3; vindex++) { - if(vindex > 0 && component->stream->video_ssrc_peer[vindex] == 0) - continue; - json_t *container = (vindex == 0 ? in_stats : json_object()); - json_object_set_new(container, "video_packets", json_integer(component->in_stats.video[vindex].packets)); - json_object_set_new(container, "video_bytes", json_integer(component->in_stats.video[vindex].bytes)); - json_object_set_new(container, "video_bytes_lastsec", json_integer(component->in_stats.video[vindex].bytes_lastsec)); - if(vindex == 0) - json_object_set_new(container, "do_video_nacks", component->do_video_nacks ? json_true() : json_false()); - if(component->do_video_nacks) { - json_object_set_new(container, "video_nacks", json_integer(component->in_stats.video[vindex].nacks)); - if(component->stream && component->stream->video_rtcp_ctx[vindex]) - json_object_set_new(in_stats, "video_retransmissions", json_integer(component->stream->video_rtcp_ctx[vindex]->retransmitted)); - } - if(vindex == 1) - json_object_set_new(in_stats, "video-simulcast-1", container); - else if(vindex == 2) - json_object_set_new(in_stats, "video-simulcast-2", container); - } - } - json_object_set_new(in_stats, "data_packets", json_integer(component->in_stats.data.packets)); - json_object_set_new(in_stats, "data_bytes", json_integer(component->in_stats.data.bytes)); - if(handle && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AUDIO)) { - json_object_set_new(out_stats, "audio_packets", json_integer(component->out_stats.audio.packets)); - json_object_set_new(out_stats, "audio_bytes", json_integer(component->out_stats.audio.bytes)); - json_object_set_new(out_stats, "audio_bytes_lastsec", json_integer(component->out_stats.audio.bytes_lastsec)); - json_object_set_new(out_stats, "audio_nacks", json_integer(component->out_stats.audio.nacks)); - } - if(handle && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO)) { - json_object_set_new(out_stats, "video_packets", json_integer(component->out_stats.video[0].packets)); - json_object_set_new(out_stats, "video_bytes", json_integer(component->out_stats.video[0].bytes)); - json_object_set_new(out_stats, "video_bytes_lastsec", json_integer(component->out_stats.video[0].bytes_lastsec)); - json_object_set_new(out_stats, "video_nacks", json_integer(component->out_stats.video[0].nacks)); - } - json_object_set_new(out_stats, "data_packets", json_integer(component->out_stats.data.packets)); - json_object_set_new(out_stats, "data_bytes", json_integer(component->out_stats.data.bytes)); #ifdef HAVE_SCTP /* FIXME Actually check if this succeeded? */ json_object_set_new(d, "sctp-association", dtls->sctp ? json_true() : json_false()); #endif + json_t *stats = json_object(); + json_t *in_stats = json_object(); + json_object_set_new(in_stats, "packets", json_integer(pc->dtls_in_stats.info[0].packets)); + json_object_set_new(in_stats, "bytes", json_integer(pc->dtls_in_stats.info[0].bytes)); + json_t *out_stats = json_object(); + json_object_set_new(out_stats, "packets", json_integer(pc->dtls_out_stats.info[0].packets)); + json_object_set_new(out_stats, "bytes", json_integer(pc->dtls_out_stats.info[0].bytes)); + json_object_set_new(stats, "in", in_stats); + json_object_set_new(stats, "out", out_stats); + json_object_set_new(d, "stats", stats); + } + json_object_set_new(w, "dtls", d); + json_t *se = json_object(); + if(pc->mid_ext_id > 0) + json_object_set_new(se, JANUS_RTP_EXTMAP_MID, json_integer(pc->mid_ext_id)); + if(pc->rid_ext_id > 0) + json_object_set_new(se, JANUS_RTP_EXTMAP_RID, json_integer(pc->rid_ext_id)); + if(pc->ridrtx_ext_id > 0) + json_object_set_new(se, JANUS_RTP_EXTMAP_REPAIRED_RID, json_integer(pc->ridrtx_ext_id)); + if(pc->transport_wide_cc_ext_id > 0) + json_object_set_new(se, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC, json_integer(pc->transport_wide_cc_ext_id)); + if(pc->framemarking_ext_id > 0) + json_object_set_new(se, JANUS_RTP_EXTMAP_FRAME_MARKING, json_integer(pc->framemarking_ext_id)); + if(pc->audiolevel_ext_id > 0) + json_object_set_new(se, JANUS_RTP_EXTMAP_AUDIO_LEVEL, json_integer(pc->audiolevel_ext_id)); + if(pc->videoorientation_ext_id > 0) + json_object_set_new(se, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION, json_integer(pc->videoorientation_ext_id)); + json_object_set_new(w, "extensions", se); + json_t *bwe = json_object(); + json_object_set_new(bwe, "twcc", pc->do_transport_wide_cc ? json_true() : json_false()); + if(pc->transport_wide_cc_ext_id >= 0) + json_object_set_new(bwe, "twcc-ext-id", json_integer(pc->transport_wide_cc_ext_id)); + json_object_set_new(w, "bwe", bwe); + json_t *media = json_object(); + /* Iterate on all media */ + janus_ice_peerconnection_medium *medium = NULL; + uint mi=0; + for(mi=0; mimedia); mi++) { + medium = g_hash_table_lookup(pc->media, GUINT_TO_POINTER(mi)); + json_t *m = janus_admin_peerconnection_medium_summary(medium); + if(m) + json_object_set_new(media, medium->mid, m); + } + json_object_set_new(w, "media", media); + return w; +} + +json_t *janus_admin_peerconnection_medium_summary(janus_ice_peerconnection_medium *medium) { + if(medium == NULL) + return NULL; + /* SSRCs */ + json_t *m = json_object(); + if(medium->type == JANUS_MEDIA_AUDIO) + json_object_set_new(m, "type", json_string("audio")); + else if(medium->type == JANUS_MEDIA_VIDEO) + json_object_set_new(m, "type", json_string("video")); + else if(medium->type == JANUS_MEDIA_DATA) + json_object_set_new(m, "type", json_string("data")); + json_object_set_new(m, "mindex", json_integer(medium->mindex)); + json_object_set_new(m, "mid", json_string(medium->mid)); + if(medium->type != JANUS_MEDIA_DATA) { + json_object_set_new(m, "do_nacks", medium->do_nacks ? json_true() : json_false()); + json_object_set_new(m, "nack-queue-ms", json_integer(medium->nack_queue_ms)); + } + if(medium->type != JANUS_MEDIA_DATA) { + json_t *ms = json_object(); + if(medium->ssrc) + json_object_set_new(ms, "ssrc", json_integer(medium->ssrc)); + if(medium->ssrc_rtx) + json_object_set_new(ms, "ssrc-rtx", json_integer(medium->ssrc_rtx)); + if(medium->ssrc_peer[0]) + json_object_set_new(ms, "ssrc-peer", json_integer(medium->ssrc_peer[0])); + if(medium->ssrc_peer[1]) + json_object_set_new(ms, "ssrc-peer-sim-1", json_integer(medium->ssrc_peer[1])); + if(medium->ssrc_peer[2]) + json_object_set_new(ms, "ssrc-peer-sim-2", json_integer(medium->ssrc_peer[2])); + if(medium->ssrc_peer_rtx[0]) + json_object_set_new(ms, "ssrc-peer-rtx", json_integer(medium->ssrc_peer_rtx[0])); + if(medium->ssrc_peer_rtx[1]) + json_object_set_new(ms, "ssrc-peer-sim-1-rtx", json_integer(medium->ssrc_peer_rtx[1])); + if(medium->ssrc_peer_rtx[2]) + json_object_set_new(ms, "ssrc-peer-sim-2-rtx", json_integer(medium->ssrc_peer_rtx[2])); + json_object_set_new(m, "ssrc", ms); + if(medium->rid[0] && medium->pc->rid_ext_id > 0) { + json_t *mr = json_object(); + json_t *rid = json_array(); + if(medium->rid[2]) + json_array_append_new(rid, json_string(medium->rid[2])); + if(medium->rid[1]) + json_array_append_new(rid, json_string(medium->rid[1])); + json_array_append_new(rid, json_string(medium->rid[0])); + json_object_set_new(mr, "rid", rid); + json_object_set_new(mr, "rid-ext-id", json_integer(medium->pc->rid_ext_id)); + if(medium->pc->ridrtx_ext_id > 0) + json_object_set_new(mr, "ridrtx-ext-id", json_integer(medium->pc->ridrtx_ext_id)); + json_object_set_new(mr, "rid-order", json_string(medium->rids_hml ? "hml" : "lmh")); + if(medium->legacy_rid) + json_object_set_new(mr, "rid-syntax", json_string("legacy")); + json_object_set_new(m, "rid-simulcast", mr); + } + } + /* Media direction */ + if(medium->type != JANUS_MEDIA_DATA) { + json_t *md = json_object(); + json_object_set_new(md, "send", medium->send ? json_true() : json_false()); + json_object_set_new(md, "recv", medium->recv ? json_true() : json_false()); + json_object_set_new(m, "direction", md); + } + /* Payload type and codec */ + if(medium->type != JANUS_MEDIA_DATA && (medium->payload_type > -1 || medium->payload_type > -1)) { + json_t *sc = json_object(); + if(medium->payload_type > -1) + json_object_set_new(sc, "pt", json_integer(medium->payload_type)); + if(medium->rtx_payload_type > -1) + json_object_set_new(sc, "rtx-pt", json_integer(medium->rtx_payload_type)); + if(medium->codec != NULL) + json_object_set_new(sc, "codec", json_string(medium->codec)); + json_object_set_new(m, "codecs", sc); + } + /* RTCP stats */ + int vindex=0; + if(medium->type != JANUS_MEDIA_DATA) { + json_t *rtcp_stats = NULL; + for(vindex=0; vindex<3; vindex++) { + if(medium->rtcp_ctx[vindex] != NULL) { + if(rtcp_stats == NULL) + rtcp_stats = json_object(); + json_t *medium_rtcp_stats = json_object(); + json_object_set_new(medium_rtcp_stats, "base", json_integer(medium->rtcp_ctx[vindex]->tb)); + if(vindex == 0) + json_object_set_new(medium_rtcp_stats, "rtt", json_integer(janus_rtcp_context_get_rtt(medium->rtcp_ctx[vindex]))); + json_object_set_new(medium_rtcp_stats, "lost", json_integer(janus_rtcp_context_get_lost_all(medium->rtcp_ctx[vindex], FALSE))); + json_object_set_new(medium_rtcp_stats, "lost-by-remote", json_integer(janus_rtcp_context_get_lost_all(medium->rtcp_ctx[vindex], TRUE))); + json_object_set_new(medium_rtcp_stats, "jitter-local", json_integer(janus_rtcp_context_get_jitter(medium->rtcp_ctx[vindex], FALSE))); + json_object_set_new(medium_rtcp_stats, "jitter-remote", json_integer(janus_rtcp_context_get_jitter(medium->rtcp_ctx[vindex], TRUE))); + json_object_set_new(medium_rtcp_stats, "in-link-quality", json_integer(janus_rtcp_context_get_in_link_quality(medium->rtcp_ctx[vindex]))); + json_object_set_new(medium_rtcp_stats, "in-media-link-quality", json_integer(janus_rtcp_context_get_in_media_link_quality(medium->rtcp_ctx[vindex]))); + json_object_set_new(medium_rtcp_stats, "out-link-quality", json_integer(janus_rtcp_context_get_out_link_quality(medium->rtcp_ctx[vindex]))); + json_object_set_new(medium_rtcp_stats, "out-media-link-quality", json_integer(janus_rtcp_context_get_out_media_link_quality(medium->rtcp_ctx[vindex]))); + if(vindex == 0) + json_object_set_new(rtcp_stats, "main", medium_rtcp_stats); + else if(vindex == 1) + json_object_set_new(rtcp_stats, "sim1", medium_rtcp_stats); + else + json_object_set_new(rtcp_stats, "sim2", medium_rtcp_stats); + } + } + if(rtcp_stats != NULL) + json_object_set_new(m, "rtcp", rtcp_stats); } - json_object_set_new(c, "dtls", d); - json_object_set_new(c, "in_stats", in_stats); - json_object_set_new(c, "out_stats", out_stats); - return c; + /* Media stats */ + json_t *stats = json_object(); + json_t *in_stats = json_object(); + json_t *out_stats = json_object(); + for(vindex=0; vindex<3; vindex++) { + if(vindex > 0 && medium->ssrc_peer[vindex] == 0) + continue; + json_t *container = (vindex == 0 ? in_stats : json_object()); + json_object_set_new(container, "packets", json_integer(medium->in_stats.info[vindex].packets)); + json_object_set_new(container, "bytes", json_integer(medium->in_stats.info[vindex].bytes)); + if(medium->type != JANUS_MEDIA_DATA) { + json_object_set_new(container, "bytes_lastsec", json_integer(medium->in_stats.info[vindex].bytes_lastsec)); + if(medium->do_nacks) { + json_object_set_new(container, "nacks", json_integer(medium->in_stats.info[vindex].nacks)); + if(medium->rtcp_ctx[vindex]) + json_object_set_new(in_stats, "retransmissions", json_integer(medium->rtcp_ctx[vindex]->retransmitted)); + } + if(vindex == 1) + json_object_set_new(in_stats, "video-simulcast-1", container); + else if(vindex == 2) + json_object_set_new(in_stats, "video-simulcast-2", container); + } + } + json_object_set_new(out_stats, "packets", json_integer(medium->out_stats.info[0].packets)); + json_object_set_new(out_stats, "bytes", json_integer(medium->out_stats.info[0].bytes)); + if(medium->type != JANUS_MEDIA_DATA) { + json_object_set_new(out_stats, "bytes_lastsec", json_integer(medium->out_stats.info[0].bytes_lastsec)); + json_object_set_new(out_stats, "nacks", json_integer(medium->out_stats.info[0].nacks)); + } + json_object_set_new(stats, "in", in_stats); + json_object_set_new(stats, "out", out_stats); + json_object_set_new(m, "stats", stats); + return m; } @@ -3515,26 +3495,10 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug JANUS_LOG(LOG_ERR, "[%"SCNu64"] Couldn't parse SDP... %s\n", ice_handle->handle_id, error_str); return NULL; } + JANUS_LOG(LOG_VERB, "[%"SCNu64"] There are %d audio, %d video and %d data m-lines\n", + ice_handle->handle_id, audio, video, data); gboolean updating = FALSE; if(offer) { - /* We may still not have a local ICE setup */ - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Audio %s been negotiated\n", ice_handle->handle_id, audio ? "has" : "has NOT"); - if(audio > 1) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] More than one audio line? only going to negotiate one...\n", ice_handle->handle_id); - } - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Video %s been negotiated\n", ice_handle->handle_id, video ? "has" : "has NOT"); - if(video > 1) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] More than one video line? only going to negotiate one...\n", ice_handle->handle_id); - } - JANUS_LOG(LOG_VERB, "[%"SCNu64"] SCTP/DataChannels %s been negotiated\n", ice_handle->handle_id, data ? "have" : "have NOT"); - if(data > 1) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] More than one data line? only going to negotiate one...\n", ice_handle->handle_id); - } -#ifndef HAVE_SCTP - if(data) { - JANUS_LOG(LOG_WARN, "[%"SCNu64"] -- DataChannels have been negotiated, but support for them has not been compiled...\n", ice_handle->handle_id); - } -#endif /* Are we still cleaning up from a previous media session? */ if(janus_flags_is_set(&ice_handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_CLEANING)) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Still cleaning up from a previous media session, let's wait a bit...\n", ice_handle->handle_id); @@ -3556,46 +3520,29 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug janus_flags_set(&ice_handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX); /* Process SDP in order to setup ICE locally (this is going to result in an answer from the browser) */ janus_mutex_lock(&ice_handle->mutex); - if(janus_ice_setup_local(ice_handle, 0, audio, video, data, 1) < 0) { + if(janus_ice_setup_local(ice_handle, FALSE, TRUE) < 0) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error setting ICE locally\n", ice_handle->handle_id); janus_sdp_destroy(parsed_sdp); janus_mutex_unlock(&ice_handle->mutex); return NULL; } + /* Create medium instances */ + if(janus_sdp_process_local(ice_handle, parsed_sdp, FALSE) < 0) { + JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error processing SDP\n", ice_handle->handle_id); + janus_sdp_destroy(parsed_sdp); + janus_mutex_unlock(&ice_handle->mutex); + return NULL; + } janus_mutex_unlock(&ice_handle->mutex); } else { updating = TRUE; JANUS_LOG(LOG_INFO, "[%"SCNu64"] Updating existing session\n", ice_handle->handle_id); - if(offer && ice_handle->stream) { - /* We might need some new properties set as well */ - janus_ice_stream *stream = ice_handle->stream; - if(audio) { - if(!janus_flags_is_set(&ice_handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AUDIO)) { - janus_flags_set(&ice_handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AUDIO); - stream->audio_ssrc = janus_random_uint32(); /* FIXME Should we look for conflicts? */ - if(stream->audio_rtcp_ctx == NULL) { - stream->audio_rtcp_ctx = g_malloc0(sizeof(rtcp_context)); - stream->audio_rtcp_ctx->tb = 48000; /* May change later */ - } - } - if(ice_handle->audio_mid == NULL) - ice_handle->audio_mid = g_strdup("audio"); - } - if(video) { - if(!janus_flags_is_set(&ice_handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO)) { - janus_flags_set(&ice_handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO); - stream->video_ssrc = janus_random_uint32(); /* FIXME Should we look for conflicts? */ - if(stream->video_rtcp_ctx[0] == NULL) { - stream->video_rtcp_ctx[0] = g_malloc0(sizeof(rtcp_context)); - stream->video_rtcp_ctx[0]->tb = 90000; /* May change later */ - } - } - if(ice_handle->video_mid == NULL) - ice_handle->video_mid = g_strdup("video"); - } - if(data) { - if(ice_handle->data_mid == NULL) - ice_handle->data_mid = g_strdup("data"); + if(offer && ice_handle->pc) { + /* Check what changed */ + if(janus_sdp_process_local(ice_handle, parsed_sdp, TRUE) < 0) { + JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error processing SDP\n", ice_handle->handle_id); + janus_sdp_destroy(parsed_sdp); + return NULL; } } } @@ -3628,55 +3575,66 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug } temp = temp->next; } - if(ice_handle->stream && ice_handle->stream->mid_ext_id != mid_ext_id) - ice_handle->stream->mid_ext_id = mid_ext_id; - if(ice_handle->stream && ice_handle->stream->transport_wide_cc_ext_id != transport_wide_cc_ext_id) { - ice_handle->stream->do_transport_wide_cc = transport_wide_cc_ext_id > 0 ? TRUE : FALSE; - ice_handle->stream->transport_wide_cc_ext_id = transport_wide_cc_ext_id; + if(ice_handle->pc && ice_handle->pc->mid_ext_id != mid_ext_id) + ice_handle->pc->mid_ext_id = mid_ext_id; + if(ice_handle->pc && ice_handle->pc->transport_wide_cc_ext_id != transport_wide_cc_ext_id) { + ice_handle->pc->do_transport_wide_cc = transport_wide_cc_ext_id > 0 ? TRUE : FALSE; + ice_handle->pc->transport_wide_cc_ext_id = transport_wide_cc_ext_id; } - if(ice_handle->stream && ice_handle->stream->audiolevel_ext_id != audiolevel_ext_id) - ice_handle->stream->audiolevel_ext_id = audiolevel_ext_id; - if(ice_handle->stream && ice_handle->stream->videoorientation_ext_id != videoorientation_ext_id) - ice_handle->stream->videoorientation_ext_id = videoorientation_ext_id; + if(ice_handle->pc && ice_handle->pc->audiolevel_ext_id != audiolevel_ext_id) + ice_handle->pc->audiolevel_ext_id = audiolevel_ext_id; + if(ice_handle->pc && ice_handle->pc->videoorientation_ext_id != videoorientation_ext_id) + ice_handle->pc->videoorientation_ext_id = videoorientation_ext_id; } else { /* Check if the answer does contain the mid/rid/repaired-rid attributes */ + int mindex = 0; gboolean do_mid = FALSE, do_rid = FALSE, do_repaired_rid = FALSE; GList *temp = parsed_sdp->m_lines; while(temp) { janus_sdp_mline *m = (janus_sdp_mline *)temp->data; + gboolean have_mid = FALSE, have_rid = FALSE, have_repaired_rid = FALSE; GList *tempA = m->attributes; while(tempA) { janus_sdp_attribute *a = (janus_sdp_attribute *)tempA->data; if(a->name && a->value) { if(strstr(a->value, JANUS_RTP_EXTMAP_MID)) - do_mid = TRUE; + have_mid = TRUE; else if(strstr(a->value, JANUS_RTP_EXTMAP_RID)) - do_rid = TRUE; + have_rid = TRUE; else if(strstr(a->value, JANUS_RTP_EXTMAP_REPAIRED_RID)) - do_repaired_rid = TRUE; + have_repaired_rid = TRUE; } tempA = tempA->next; } + if(!have_rid) { + janus_ice_peerconnection_medium *medium = g_hash_table_lookup(ice_handle->pc->media, GUINT_TO_POINTER(mindex)); + if(medium != NULL) { + g_free(medium->rid[0]); + medium->rid[0] = NULL; + g_free(medium->rid[1]); + medium->rid[1] = NULL; + g_free(medium->rid[2]); + medium->rid[2] = NULL; + if(medium->ssrc_peer_temp > 0) { + medium->ssrc_peer[0] = medium->ssrc_peer_temp; + medium->ssrc_peer_temp = 0; + } + } + } + do_mid = do_mid || have_mid; + do_rid = do_rid || have_rid; + do_repaired_rid = do_repaired_rid || have_repaired_rid; + mindex++; temp = temp->next; } - if(!do_mid && ice_handle->stream) - ice_handle->stream->mid_ext_id = 0; - if(!do_rid && ice_handle->stream) { - ice_handle->stream->rid_ext_id = 0; - ice_handle->stream->ridrtx_ext_id = 0; - g_free(ice_handle->stream->rid[0]); - ice_handle->stream->rid[0] = NULL; - g_free(ice_handle->stream->rid[1]); - ice_handle->stream->rid[1] = NULL; - g_free(ice_handle->stream->rid[2]); - ice_handle->stream->rid[2] = NULL; - if(ice_handle->stream->video_ssrc_peer_temp > 0) { - ice_handle->stream->video_ssrc_peer[0] = ice_handle->stream->video_ssrc_peer_temp; - ice_handle->stream->video_ssrc_peer_temp = 0; - } - } - if(!do_repaired_rid && ice_handle->stream) - ice_handle->stream->ridrtx_ext_id = 0; + if(!do_mid && ice_handle->pc) + ice_handle->pc->mid_ext_id = 0; + if(!do_rid && ice_handle->pc) { + ice_handle->pc->rid_ext_id = 0; + ice_handle->pc->ridrtx_ext_id = 0; + } + if(!do_repaired_rid && ice_handle->pc) + ice_handle->pc->ridrtx_ext_id = 0; } if(!updating && !janus_ice_is_full_trickle_enabled()) { /* Wait for candidates-done callback */ @@ -3718,44 +3676,51 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug janus_ice_restart(ice_handle); /* Add our details */ janus_mutex_lock(&ice_handle->mutex); - janus_ice_stream *stream = ice_handle->stream; - if (stream == NULL) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error stream not found\n", ice_handle->handle_id); + janus_ice_peerconnection *pc = ice_handle->pc; + if(pc == NULL) { + JANUS_LOG(LOG_ERR, "[%"SCNu64"] No WebRTC PeerConnection\n", ice_handle->handle_id); janus_mutex_unlock(&ice_handle->mutex); return NULL; } - if(janus_flags_is_set(&ice_handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX) && - stream->rtx_payload_types == NULL) { - /* Make sure we have a list of rtx payload types to generate, if needed */ - janus_sdp_mline *m = janus_sdp_mline_find(parsed_sdp, JANUS_SDP_VIDEO); - if(m && m->ptypes) { - stream->rtx_payload_types = g_hash_table_new(NULL, NULL); - GList *ptypes = g_list_copy(m->ptypes), *tempP = ptypes; - GList *rtx_ptypes = g_hash_table_get_values(stream->rtx_payload_types); - while(tempP) { - int ptype = GPOINTER_TO_INT(tempP->data); - int rtx_ptype = ptype+1; - if(rtx_ptype > 127) - rtx_ptype = 96; - while(g_list_find(m->ptypes, GINT_TO_POINTER(rtx_ptype)) - || g_list_find(rtx_ptypes, GINT_TO_POINTER(rtx_ptype))) { - rtx_ptype++; + /* Iterate on all media */ + janus_ice_peerconnection_medium *medium = NULL; + uint mi=0; + for(mi=0; mimedia); mi++) { + medium = g_hash_table_lookup(pc->media, GUINT_TO_POINTER(mi)); + if(medium && medium->type == JANUS_MEDIA_VIDEO && + janus_flags_is_set(&ice_handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX) && + medium->rtx_payload_types == NULL) { + /* Make sure we have a list of rtx payload types to generate, if needed */ + janus_sdp_mline *m = janus_sdp_mline_find(parsed_sdp, JANUS_SDP_VIDEO); + if(m && m->ptypes) { + medium->rtx_payload_types = g_hash_table_new(NULL, NULL); + GList *ptypes = g_list_copy(m->ptypes), *tempP = ptypes; + GList *rtx_ptypes = g_hash_table_get_values(medium->rtx_payload_types); + while(tempP) { + int ptype = GPOINTER_TO_INT(tempP->data); + int rtx_ptype = ptype+1; if(rtx_ptype > 127) rtx_ptype = 96; - if(rtx_ptype == ptype) { - /* We did a whole round? should never happen... */ - rtx_ptype = -1; - break; + while(g_list_find(m->ptypes, GINT_TO_POINTER(rtx_ptype)) + || g_list_find(rtx_ptypes, GINT_TO_POINTER(rtx_ptype))) { + rtx_ptype++; + if(rtx_ptype > 127) + rtx_ptype = 96; + if(rtx_ptype == ptype) { + /* We did a whole round? should never happen... */ + rtx_ptype = -1; + break; + } } + if(rtx_ptype > 0) + g_hash_table_insert(medium->rtx_payload_types, GINT_TO_POINTER(ptype), GINT_TO_POINTER(rtx_ptype)); + g_list_free(rtx_ptypes); + rtx_ptypes = g_hash_table_get_values(medium->rtx_payload_types); + tempP = tempP->next; } - if(rtx_ptype > 0) - g_hash_table_insert(stream->rtx_payload_types, GINT_TO_POINTER(ptype), GINT_TO_POINTER(rtx_ptype)); + g_list_free(ptypes); g_list_free(rtx_ptypes); - rtx_ptypes = g_hash_table_get_values(stream->rtx_payload_types); - tempP = tempP->next; } - g_list_free(ptypes); - g_list_free(rtx_ptypes); } } /* Enrich the SDP the plugin gave us with all the WebRTC related stuff */ @@ -3775,19 +3740,18 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug janus_flags_set(&ice_handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_PROCESSING_OFFER); } else { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Sending answer, ready to setup remote candidates and send connectivity checks...\n", ice_handle->handle_id); - janus_request_ice_handle_answer(ice_handle, audio, video, data, NULL); + janus_request_ice_handle_answer(ice_handle, NULL); } } #ifdef HAVE_SCTP if(!offer && janus_flags_is_set(&ice_handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_READY)) { /* Renegotiation: check if datachannels were just added on an existing PeerConnection */ if(janus_flags_is_set(&ice_handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_DATA_CHANNELS)) { - janus_ice_stream *stream = ice_handle->stream; - if(stream != NULL && stream->component != NULL && - stream->component->dtls != NULL && stream->component->dtls->sctp == NULL) { + janus_ice_peerconnection *pc = ice_handle->pc; + if(pc != NULL && pc->dtls != NULL && pc->dtls->sctp == NULL) { /* Create SCTP association as well */ JANUS_LOG(LOG_WARN, "[%"SCNu64"] Creating datachannels...\n", ice_handle->handle_id); - janus_dtls_srtp_create_sctp(stream->component->dtls); + janus_dtls_srtp_create_sctp(pc->dtls); } } } diff --git a/plugins/janus_audiobridge.c b/plugins/janus_audiobridge.c index 2ef028321c..483a1ae75f 100644 --- a/plugins/janus_audiobridge.c +++ b/plugins/janus_audiobridge.c @@ -6252,7 +6252,7 @@ static void *janus_audiobridge_handler(void *data) { /* What is the Opus payload type? */ janus_audiobridge_participant *participant = (janus_audiobridge_participant *)session->participant; if(sdp != NULL) { - participant->opus_pt = janus_sdp_get_codec_pt(sdp, "opus"); + participant->opus_pt = janus_sdp_get_codec_pt(sdp, -1, "opus"); if(participant->opus_pt > 0 && strstr(msg_sdp, "useinbandfec=1")){ /* Opus codec, inband FEC setted */ participant->fec = TRUE; @@ -6314,14 +6314,23 @@ static void *janus_audiobridge_handler(void *data) { /* If we got an offer, we need to answer */ janus_sdp *offer = NULL, *answer = NULL; if(got_offer) { - answer = janus_sdp_generate_answer(sdp, - /* Reject video and data channels, if offered */ - JANUS_SDP_OA_AUDIO_CODEC, janus_audiocodec_name(participant->codec), - JANUS_SDP_OA_VIDEO, FALSE, - JANUS_SDP_OA_DATA, FALSE, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_AUDIO_LEVEL, - JANUS_SDP_OA_DONE); + janus_sdp *answer = janus_sdp_generate_answer(offer); + /* Only accept the first audio line, and reject everything else if offered */ + GList *temp = offer->m_lines; + gboolean accepted = FALSE; + while(temp) { + janus_sdp_mline *m = (janus_sdp_mline *)temp->data; + if(m->type == JANUS_SDP_AUDIO && !accepted) { + accepted = TRUE; + janus_sdp_generate_answer_mline(offer, answer, m, + JANUS_SDP_OA_MLINE, JANUS_SDP_AUDIO, + JANUS_SDP_OA_CODEC, janus_audiocodec_name(participant->codec), + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_AUDIO_LEVEL, + JANUS_SDP_OA_DONE); + } + temp = temp->next; + } /* Replace the session name */ g_free(answer->s_name); answer->s_name = g_strdup(s_name); @@ -6342,15 +6351,13 @@ static void *janus_audiobridge_handler(void *data) { pt = 8; offer = janus_sdp_generate_offer( s_name, "1.1.1.1", - JANUS_SDP_OA_AUDIO, TRUE, - JANUS_SDP_OA_AUDIO_CODEC, janus_audiocodec_name(participant->codec), - JANUS_SDP_OA_AUDIO_PT, pt, - JANUS_SDP_OA_AUDIO_FMTP, (participant->codec == JANUS_AUDIOCODEC_OPUS ? fmtp : NULL), - JANUS_SDP_OA_AUDIO_DIRECTION, JANUS_SDP_SENDRECV, - JANUS_SDP_OA_AUDIO_EXTENSION, JANUS_RTP_EXTMAP_MID, 1, - JANUS_SDP_OA_AUDIO_EXTENSION, JANUS_RTP_EXTMAP_AUDIO_LEVEL, extmap_id, - JANUS_SDP_OA_VIDEO, FALSE, - JANUS_SDP_OA_DATA, FALSE, + JANUS_SDP_OA_MLINE, JANUS_SDP_AUDIO, + JANUS_SDP_OA_CODEC, janus_audiocodec_name(participant->codec), + JANUS_SDP_OA_PT, pt, + JANUS_SDP_OA_FMTP, (participant->codec == JANUS_AUDIOCODEC_OPUS ? fmtp : NULL), + JANUS_SDP_OA_DIRECTION, JANUS_SDP_SENDRECV, + JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_MID, 1, + JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_AUDIO_LEVEL, extmap_id, JANUS_SDP_OA_DONE); /* Let's overwrite a couple o= fields, in case this is a renegotiation */ if(session->sdp_version == 1) { @@ -7021,7 +7028,7 @@ static void janus_audiobridge_relay_rtp_packet(gpointer data, gpointer user_data /* Fix sequence number and timestamp (room switching may be involved) */ janus_rtp_header_update(packet->data, &participant->context, FALSE, 0); if(gateway != NULL) { - janus_plugin_rtp rtp = { .video = FALSE, .buffer = (char *)packet->data, .length = packet->length }; + janus_plugin_rtp rtp = { .mindex = -1, .video = FALSE, .buffer = (char *)packet->data, .length = packet->length }; janus_plugin_rtp_extensions_reset(&rtp.extensions); /* FIXME Should we add our own audio level extension? */ gateway->relay_rtp(session->handle, &rtp); diff --git a/plugins/janus_duktape.c b/plugins/janus_duktape.c index 9a5ba7813e..0b66172376 100644 --- a/plugins/janus_duktape.c +++ b/plugins/janus_duktape.c @@ -225,7 +225,7 @@ void janus_duktape_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp * void janus_duktape_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet); void janus_duktape_incoming_data(janus_plugin_session *handle, janus_plugin_data *packet); void janus_duktape_data_ready(janus_plugin_session *handle); -void janus_duktape_slow_link(janus_plugin_session *handle, int uplink, int video); +void janus_duktape_slow_link(janus_plugin_session *handle, int mindex, gboolean video, gboolean uplink); void janus_duktape_hangup_media(janus_plugin_session *handle); void janus_duktape_destroy_session(janus_plugin_session *handle, int *error); json_t *janus_duktape_query_session(janus_plugin_session *handle); @@ -610,7 +610,7 @@ static duk_ret_t janus_duktape_method_pushevent(duk_context *ctx) { if(sdp_type && !strcasecmp(sdp_type, "answer")) { /* Take note of which video codec were negotiated */ const char *vcodec = NULL; - janus_sdp_find_first_codecs(parsed_sdp, NULL, &vcodec); + janus_sdp_find_first_codec(parsed_sdp, JANUS_SDP_VIDEO, -1, &vcodec); if(vcodec) session->vcodec = janus_videocodec_from_name(vcodec); if(session->vcodec != JANUS_VIDEOCODEC_VP8 && session->vcodec != JANUS_VIDEOCODEC_H264) { @@ -2018,7 +2018,8 @@ void janus_duktape_create_session(janus_plugin_session *handle, int *error) { janus_duktape_session *session = (janus_duktape_session *)g_malloc0(sizeof(janus_duktape_session)); session->handle = handle; session->id = id; - janus_rtp_switching_context_reset(&session->rtpctx); + janus_rtp_switching_context_reset(&session->artpctx); + janus_rtp_switching_context_reset(&session->vrtpctx); janus_rtp_simulcasting_context_reset(&session->sim_context); session->sim_context.substream_target = 2; session->sim_context.templayer_target = 2; @@ -2187,7 +2188,7 @@ struct janus_plugin_result *janus_duktape_handle_message(janus_plugin_session *h const char *sdp = json_string_value(json_object_get(jsep, "sdp")); janus_sdp *parsed_sdp = janus_sdp_parse(sdp, error_str, sizeof(error_str)); const char *vcodec = NULL; - janus_sdp_find_first_codecs(parsed_sdp, NULL, &vcodec); + janus_sdp_find_first_codec(parsed_sdp, JANUS_SDP_VIDEO, -1, &vcodec); if(vcodec) session->vcodec = janus_videocodec_from_name(vcodec); if(session->vcodec != JANUS_VIDEOCODEC_VP8 && session->vcodec != JANUS_VIDEOCODEC_H264) { @@ -2591,7 +2592,7 @@ void janus_duktape_data_ready(janus_plugin_session *handle) { } } -void janus_duktape_slow_link(janus_plugin_session *handle, int uplink, int video) { +void janus_duktape_slow_link(janus_plugin_session *handle, int mindex, gboolean video, gboolean uplink) { if(handle == NULL || handle->stopped || g_atomic_int_get(&duktape_stopping) || !g_atomic_int_get(&duktape_initialized)) return; janus_mutex_lock(&duktape_sessions_mutex); @@ -2662,7 +2663,8 @@ void janus_duktape_hangup_media(janus_plugin_session *handle) { session->pli_freq = 0; session->pli_latest = 0; session->e2ee = FALSE; - janus_rtp_switching_context_reset(&session->rtpctx); + janus_rtp_switching_context_reset(&session->artpctx); + janus_rtp_switching_context_reset(&session->vrtpctx); janus_rtp_simulcasting_context_reset(&session->sim_context); session->sim_context.substream_target = 2; session->sim_context.templayer_target = 2; @@ -2729,7 +2731,7 @@ static void janus_duktape_relay_rtp_packet(gpointer data, gpointer user_data) { return; /* Process this packet: don't relay if it's not the SSRC/layer we wanted to handle */ gboolean relay = janus_rtp_simulcasting_context_process_rtp(&session->sim_context, - (char *)packet->data, packet->length, packet->ssrc, NULL, sender->vcodec, &session->rtpctx); + (char *)packet->data, packet->length, packet->ssrc, NULL, sender->vcodec, &session->vrtpctx); if(session->sim_context.need_pli && sender->handle) { /* Send a PLI */ JANUS_LOG(LOG_VERB, "We need a PLI for the simulcast context\n"); @@ -2778,7 +2780,7 @@ static void janus_duktape_relay_rtp_packet(gpointer data, gpointer user_data) { } } /* If we got here, update the RTP header and send the packet */ - janus_rtp_header_update(packet->data, &session->rtpctx, TRUE, 0); + janus_rtp_header_update(packet->data, &session->vrtpctx, TRUE, 0); char vp8pd[6]; if(sender->vcodec == JANUS_VIDEOCODEC_VP8) { /* For VP8, we save the original payload descriptor, to restore it after */ @@ -2801,7 +2803,7 @@ static void janus_duktape_relay_rtp_packet(gpointer data, gpointer user_data) { } } else { /* Fix sequence number and timestamp (publisher switching may be involved) */ - janus_rtp_header_update(packet->data, &session->rtpctx, packet->is_video, 0); + janus_rtp_header_update(packet->data, packet->is_video ? &session->vrtpctx : &session->artpctx, packet->is_video, 0); /* Send the packet */ if(janus_core != NULL) { janus_plugin_rtp rtp = { .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; diff --git a/plugins/janus_duktape_data.h b/plugins/janus_duktape_data.h index e705df33ba..b67907a02a 100644 --- a/plugins/janus_duktape_data.h +++ b/plugins/janus_duktape_data.h @@ -58,6 +58,7 @@ typedef struct janus_duktape_session { gboolean send_audio; /* Whether outgoing audio can be sent or must be dropped */ gboolean send_video; /* Whether outgoing video can be sent or must be dropped */ gboolean send_data; /* Whether outgoing data can be sent or must be dropped */ + janus_rtp_switching_context artpctx, vrtpctx; janus_rtp_switching_context rtpctx; /* RTP switching context */ janus_videocodec vcodec; /* Video codec this session is using */ uint32_t ssrc[3]; /* Only needed in case VP8 (or H.264) simulcasting is involved */ diff --git a/plugins/janus_echotest.c b/plugins/janus_echotest.c index d9ffdf4cba..d4d773fc52 100644 --- a/plugins/janus_echotest.c +++ b/plugins/janus_echotest.c @@ -149,7 +149,7 @@ void janus_echotest_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp void janus_echotest_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet); void janus_echotest_incoming_data(janus_plugin_session *handle, janus_plugin_data *packet); void janus_echotest_data_ready(janus_plugin_session *handle); -void janus_echotest_slow_link(janus_plugin_session *handle, int uplink, int video); +void janus_echotest_slow_link(janus_plugin_session *handle, int mindex, gboolean video, gboolean uplink); void janus_echotest_hangup_media(janus_plugin_session *handle); void janus_echotest_destroy_session(janus_plugin_session *handle, int *error); json_t *janus_echotest_query_session(janus_plugin_session *handle); @@ -702,7 +702,7 @@ void janus_echotest_data_ready(janus_plugin_session *handle) { /* Data channels are writable */ } -void janus_echotest_slow_link(janus_plugin_session *handle, int uplink, int video) { +void janus_echotest_slow_link(janus_plugin_session *handle, int mindex, gboolean video, gboolean uplink) { /* The core is informing us that our peer got or sent too many NACKs, are we pushing media too hard? */ if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) return; @@ -874,14 +874,21 @@ static void *janus_echotest_handler(void *data) { const char *msg_sdp_type = json_string_value(json_object_get(msg->jsep, "type")); const char *msg_sdp = json_string_value(json_object_get(msg->jsep, "sdp")); json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast"); - if(msg_simulcast) { - JANUS_LOG(LOG_VERB, "EchoTest client is going to do simulcasting\n"); - int rid_ext_id = -1, framemarking_ext_id = -1; - janus_rtp_simulcasting_prepare(msg_simulcast, &rid_ext_id, &framemarking_ext_id, session->ssrc, session->rid); - session->sim_context.rid_ext_id = rid_ext_id; - session->sim_context.framemarking_ext_id = framemarking_ext_id; - session->sim_context.substream_target = 2; /* Let's aim for the highest quality */ - session->sim_context.templayer_target = 2; /* Let's aim for all temporal layers */ + if(msg_simulcast && json_array_size(msg_simulcast) > 0) { + size_t i = 0; + for(i=0; issrc, session->rid); + session->sim_context.rid_ext_id = rid_ext_id; + session->sim_context.framemarking_ext_id = framemarking_ext_id; + session->sim_context.substream_target = 2; /* Let's aim for the highest quality */ + session->sim_context.templayer_target = 2; /* Let's aim for all temporal layers */ + /* FIXME We're stopping at the first item, there may be more */ + break; + } } json_t *msg_e2ee = json_object_get(msg->jsep, "e2ee"); if(json_is_true(msg_e2ee)) @@ -1082,20 +1089,27 @@ static void *janus_echotest_handler(void *data) { } temp = temp->next; } - janus_sdp *answer = janus_sdp_generate_answer(offer, - JANUS_SDP_OA_AUDIO_CODEC, json_string_value(audiocodec), - JANUS_SDP_OA_AUDIO_FMTP, opus_fec ? "useinbandfec=1" : NULL, - JANUS_SDP_OA_VIDEO_CODEC, json_string_value(videocodec), - JANUS_SDP_OA_VP9_PROFILE, json_string_value(videoprofile), - JANUS_SDP_OA_H264_PROFILE, json_string_value(videoprofile), - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RID, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_REPAIRED_RID, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_AUDIO_LEVEL, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_FRAME_MARKING, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC, - JANUS_SDP_OA_DONE); + janus_sdp *answer = janus_sdp_generate_answer(offer); + temp = offer->m_lines; + while(temp) { + janus_sdp_mline *m = (janus_sdp_mline *)temp->data; + janus_sdp_generate_answer_mline(offer, answer, m, + JANUS_SDP_OA_MLINE, m->type, + JANUS_SDP_OA_CODEC, (m->type == JANUS_SDP_AUDIO ? json_string_value(audiocodec) : + (m->type == JANUS_SDP_VIDEO ? json_string_value(videocodec) : NULL)), + JANUS_SDP_OA_FMTP, (m->type == JANUS_SDP_AUDIO && opus_fec ? "useinbandfec=1" : NULL), + JANUS_SDP_OA_VP9_PROFILE, json_string_value(videoprofile), + JANUS_SDP_OA_H264_PROFILE, json_string_value(videoprofile), + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_REPAIRED_RID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_AUDIO_LEVEL, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_FRAME_MARKING, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC, + JANUS_SDP_OA_DONE); + temp = temp->next; + } /* If we ended up sendonly, switch to inactive (as we don't really send anything ourselves) */ janus_sdp_mline *m = janus_sdp_mline_find(answer, JANUS_SDP_AUDIO); if(m && m->direction == JANUS_SDP_SENDONLY) @@ -1105,9 +1119,10 @@ static void *janus_echotest_handler(void *data) { m->direction = JANUS_SDP_INACTIVE; /* Check which codecs we ended up with */ const char *acodec = NULL, *vcodec = NULL; - janus_sdp_find_first_codecs(answer, &acodec, &vcodec); + janus_sdp_find_first_codec(answer, JANUS_SDP_AUDIO, -1, &acodec); if(acodec) session->acodec = janus_audiocodec_from_name(acodec); + janus_sdp_find_first_codec(answer, JANUS_SDP_VIDEO, -1, &vcodec); if(vcodec) session->vcodec = janus_videocodec_from_name(vcodec); session->has_audio = session->acodec != JANUS_AUDIOCODEC_NONE; @@ -1124,7 +1139,7 @@ static void *janus_echotest_handler(void *data) { g_free(session->vfmtp); session->vfmtp = NULL; if(session->has_video) { - const char *vfmtp = janus_sdp_get_fmtp(answer, janus_sdp_get_codec_pt(answer, vcodec)); + const char *vfmtp = janus_sdp_get_fmtp(answer, -1, janus_sdp_get_codec_pt(answer, -1, vcodec)); if(vfmtp != NULL) session->vfmtp = g_strdup(vfmtp); } diff --git a/plugins/janus_lua.c b/plugins/janus_lua.c index 452d271673..caa7bb4a61 100644 --- a/plugins/janus_lua.c +++ b/plugins/janus_lua.c @@ -226,7 +226,7 @@ void janus_lua_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp *pack void janus_lua_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet); void janus_lua_incoming_data(janus_plugin_session *handle, janus_plugin_data *packet); void janus_lua_data_ready(janus_plugin_session *handle); -void janus_lua_slow_link(janus_plugin_session *handle, int uplink, int video); +void janus_lua_slow_link(janus_plugin_session *handle, int mindex, gboolean video, gboolean uplink); void janus_lua_hangup_media(janus_plugin_session *handle); void janus_lua_destroy_session(janus_plugin_session *handle, int *error); json_t *janus_lua_query_session(janus_plugin_session *handle); @@ -539,7 +539,7 @@ static int janus_lua_method_pushevent(lua_State *s) { if(sdp_type && !strcasecmp(sdp_type, "answer")) { /* Take note of which video codec were negotiated */ const char *vcodec = NULL; - janus_sdp_find_first_codecs(parsed_sdp, NULL, &vcodec); + janus_sdp_find_first_codec(parsed_sdp, JANUS_SDP_VIDEO, -1, &vcodec); if(vcodec) session->vcodec = janus_videocodec_from_name(vcodec); if(session->vcodec != JANUS_VIDEOCODEC_VP8 && session->vcodec != JANUS_VIDEOCODEC_H264) { @@ -1761,7 +1761,8 @@ void janus_lua_create_session(janus_plugin_session *handle, int *error) { janus_lua_session *session = (janus_lua_session *)g_malloc0(sizeof(janus_lua_session)); session->handle = handle; session->id = id; - janus_rtp_switching_context_reset(&session->rtpctx); + janus_rtp_switching_context_reset(&session->artpctx); + janus_rtp_switching_context_reset(&session->vrtpctx); janus_rtp_simulcasting_context_reset(&session->sim_context); session->sim_context.substream_target = 2; session->sim_context.templayer_target = 2; @@ -1908,7 +1909,7 @@ struct janus_plugin_result *janus_lua_handle_message(janus_plugin_session *handl const char *sdp = json_string_value(json_object_get(jsep, "sdp")); janus_sdp *parsed_sdp = janus_sdp_parse(sdp, error_str, sizeof(error_str)); const char *vcodec = NULL; - janus_sdp_find_first_codecs(parsed_sdp, NULL, &vcodec); + janus_sdp_find_first_codec(parsed_sdp, JANUS_SDP_VIDEO, -1, &vcodec); if(vcodec) session->vcodec = janus_videocodec_from_name(vcodec); if(session->vcodec != JANUS_VIDEOCODEC_VP8 && session->vcodec != JANUS_VIDEOCODEC_H264) { @@ -2268,7 +2269,7 @@ void janus_lua_data_ready(janus_plugin_session *handle) { } } -void janus_lua_slow_link(janus_plugin_session *handle, int uplink, int video) { +void janus_lua_slow_link(janus_plugin_session *handle, int mindex, gboolean video, gboolean uplink) { if(handle == NULL || handle->stopped || g_atomic_int_get(&lua_stopping) || !g_atomic_int_get(&lua_initialized)) return; janus_mutex_lock(&lua_sessions_mutex); @@ -2333,7 +2334,8 @@ void janus_lua_hangup_media(janus_plugin_session *handle) { session->pli_freq = 0; session->pli_latest = 0; session->e2ee = FALSE; - janus_rtp_switching_context_reset(&session->rtpctx); + janus_rtp_switching_context_reset(&session->artpctx); + janus_rtp_switching_context_reset(&session->vrtpctx); janus_rtp_simulcasting_context_reset(&session->sim_context); session->sim_context.substream_target = 2; session->sim_context.templayer_target = 2; @@ -2394,7 +2396,7 @@ static void janus_lua_relay_rtp_packet(gpointer data, gpointer user_data) { return; /* Process this packet: don't relay if it's not the SSRC/layer we wanted to handle */ gboolean relay = janus_rtp_simulcasting_context_process_rtp(&session->sim_context, - (char *)packet->data, packet->length, packet->ssrc, NULL, sender->vcodec, &session->rtpctx); + (char *)packet->data, packet->length, packet->ssrc, NULL, sender->vcodec, &session->vrtpctx); if(session->sim_context.need_pli && sender->handle) { /* Send a PLI */ JANUS_LOG(LOG_VERB, "We need a PLI for the simulcast context\n"); @@ -2431,7 +2433,7 @@ static void janus_lua_relay_rtp_packet(gpointer data, gpointer user_data) { } } /* If we got here, update the RTP header and send the packet */ - janus_rtp_header_update(packet->data, &session->rtpctx, TRUE, 0); + janus_rtp_header_update(packet->data, &session->vrtpctx, TRUE, 0); char vp8pd[6]; if(sender->vcodec == JANUS_VIDEOCODEC_VP8) { /* For VP8, we save the original payload descriptor, to restore it after */ @@ -2454,7 +2456,7 @@ static void janus_lua_relay_rtp_packet(gpointer data, gpointer user_data) { } } else { /* Fix sequence number and timestamp (publisher switching may be involved) */ - janus_rtp_header_update(packet->data, &session->rtpctx, packet->is_video, 0); + janus_rtp_header_update(packet->data, packet->is_video ? &session->vrtpctx : &session->artpctx, packet->is_video, 0); /* Send the packet */ if(janus_core != NULL) { janus_plugin_rtp rtp = { .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; diff --git a/plugins/janus_lua_data.h b/plugins/janus_lua_data.h index 2b49954546..98ae081f5e 100644 --- a/plugins/janus_lua_data.h +++ b/plugins/janus_lua_data.h @@ -58,7 +58,7 @@ typedef struct janus_lua_session { gboolean send_audio; /* Whether outgoing audio can be sent or must be dropped */ gboolean send_video; /* Whether outgoing video can be sent or must be dropped */ gboolean send_data; /* Whether outgoing data can be sent or must be dropped */ - janus_rtp_switching_context rtpctx; /* RTP switching context */ + janus_rtp_switching_context artpctx, vrtpctx; janus_videocodec vcodec; /* Video codec this session is using */ uint32_t ssrc[3]; /* Only needed in case VP8 (or H.264) simulcasting is involved */ char *rid[3]; /* Only needed if simulcasting is rid-based */ diff --git a/plugins/janus_nosip.c b/plugins/janus_nosip.c index 556904c09f..73074b13cf 100644 --- a/plugins/janus_nosip.c +++ b/plugins/janus_nosip.c @@ -314,7 +314,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; - janus_rtp_switching_context context; + janus_rtp_switching_context acontext, vcontext; int pipefd[2]; gboolean updated; int video_orientation_extension_id; @@ -626,7 +626,8 @@ void janus_nosip_media_reset(janus_nosip_session *session) { session->media.video_send = TRUE; session->media.video_orientation_extension_id = -1; session->media.audio_level_extension_id = -1; - janus_rtp_switching_context_reset(&session->media.context); + janus_rtp_switching_context_reset(&session->media.acontext); + janus_rtp_switching_context_reset(&session->media.vcontext); } @@ -918,7 +919,8 @@ void janus_nosip_create_session(janus_plugin_session *handle, int *error) { session->media.video_orientation_extension_id = -1; session->media.audio_level_extension_id = -1; /* Initialize the RTP context */ - janus_rtp_switching_context_reset(&session->media.context); + janus_rtp_switching_context_reset(&session->media.acontext); + janus_rtp_switching_context_reset(&session->media.vcontext); session->media.pipefd[0] = -1; session->media.pipefd[1] = -1; session->media.updated = FALSE; @@ -2424,12 +2426,12 @@ static void *janus_nosip_relay_thread(void *data) { bytes = buflen; } /* Check if the SSRC changed (e.g., after a re-INVITE or UPDATE) */ - janus_rtp_header_update(header, &session->media.context, video, 0); + janus_rtp_header_update(header, video ? &session->media.vcontext : &session->media.acontext, video, 0); /* Save the frame if we're recording */ header->ssrc = htonl(video ? session->media.video_ssrc_peer : session->media.audio_ssrc_peer); janus_recorder_save_frame(video ? session->vrc_peer : session->arc_peer, buffer, bytes); /* Relay to browser */ - janus_plugin_rtp rtp = { .video = video, .buffer = buffer, .length = bytes }; + janus_plugin_rtp rtp = { .mindex = -1, .video = video, .buffer = buffer, .length = bytes }; /* Add audio-level extension, if present */ janus_plugin_rtp_extensions_reset(&rtp.extensions); if(!video && session->media.audio_level_extension_id != -1) { @@ -2476,7 +2478,7 @@ static void *janus_nosip_relay_thread(void *data) { bytes = buflen; } /* Relay to browser */ - janus_plugin_rtcp rtcp = { .video = video, .buffer = buffer, bytes }; + janus_plugin_rtcp rtcp = { .mindex = -1, .video = video, .buffer = buffer, bytes }; gateway->relay_rtcp(session->handle, &rtcp); continue; } diff --git a/plugins/janus_recordplay.c b/plugins/janus_recordplay.c index 3d7e76a66d..38faacc2e9 100644 --- a/plugins/janus_recordplay.c +++ b/plugins/janus_recordplay.c @@ -301,7 +301,7 @@ json_t *janus_recordplay_handle_admin_message(json_t *message); void janus_recordplay_setup_media(janus_plugin_session *handle); void janus_recordplay_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp *packet); void janus_recordplay_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet); -void janus_recordplay_slow_link(janus_plugin_session *handle, int uplink, int video); +void janus_recordplay_slow_link(janus_plugin_session *handle, int mindex, gboolean video, gboolean uplink); void janus_recordplay_hangup_media(janus_plugin_session *handle); void janus_recordplay_destroy_session(janus_plugin_session *handle, int *error); json_t *janus_recordplay_query_session(janus_plugin_session *handle); @@ -627,7 +627,7 @@ static const char *janus_recordplay_parse_codec(const char *dir, const char *fil c = "text"; json_decref(info); fclose(file); - return c; + return c; } const char *mcodec = janus_sdp_match_preferred_codec(video ? JANUS_SDP_VIDEO : JANUS_SDP_AUDIO, (char *)c); if(mcodec != NULL) { @@ -663,19 +663,22 @@ static int janus_recordplay_generate_offer(janus_recordplay_recording *rec) { g_snprintf(s_name, sizeof(s_name), "Recording %"SCNu64, rec->id); janus_sdp *offer = janus_sdp_generate_offer( s_name, "1.1.1.1", - JANUS_SDP_OA_AUDIO, offer_audio, - JANUS_SDP_OA_AUDIO_CODEC, janus_audiocodec_name(rec->acodec), - JANUS_SDP_OA_AUDIO_PT, rec->audio_pt, - JANUS_SDP_OA_AUDIO_FMTP, rec->afmtp, - JANUS_SDP_OA_AUDIO_DIRECTION, JANUS_SDP_SENDONLY, - JANUS_SDP_OA_AUDIO_EXTENSION, JANUS_RTP_EXTMAP_MID, 1, - JANUS_SDP_OA_VIDEO, offer_video, - JANUS_SDP_OA_VIDEO_CODEC, janus_videocodec_name(rec->vcodec), - JANUS_SDP_OA_VIDEO_FMTP, rec->vfmtp, - JANUS_SDP_OA_VIDEO_PT, rec->video_pt, - JANUS_SDP_OA_VIDEO_DIRECTION, JANUS_SDP_SENDONLY, - JANUS_SDP_OA_VIDEO_EXTENSION, JANUS_RTP_EXTMAP_MID, 1, - JANUS_SDP_OA_DATA, offer_data, + JANUS_SDP_OA_MLINE, JANUS_SDP_AUDIO, + JANUS_SDP_OA_ENABLED, offer_audio, + JANUS_SDP_OA_CODEC, janus_audiocodec_name(rec->acodec), + JANUS_SDP_OA_PT, rec->audio_pt, + JANUS_SDP_OA_FMTP, rec->afmtp, + JANUS_SDP_OA_DIRECTION, JANUS_SDP_SENDONLY, + JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_MID, 1, + JANUS_SDP_OA_MLINE, JANUS_SDP_VIDEO, + JANUS_SDP_OA_ENABLED, offer_video, + JANUS_SDP_OA_CODEC, janus_videocodec_name(rec->vcodec), + JANUS_SDP_OA_PT, rec->video_pt, + JANUS_SDP_OA_FMTP, rec->vfmtp, + JANUS_SDP_OA_DIRECTION, JANUS_SDP_SENDONLY, + JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_MID, 1, + JANUS_SDP_OA_MLINE, JANUS_SDP_APPLICATION, + JANUS_SDP_OA_ENABLED, offer_data, JANUS_SDP_OA_DONE); g_free(rec->offer); rec->offer = janus_sdp_write(offer); @@ -1277,7 +1280,7 @@ void janus_recordplay_incoming_rtcp(janus_plugin_session *handle, janus_plugin_r return; } -void janus_recordplay_slow_link(janus_plugin_session *handle, int uplink, int video) { +void janus_recordplay_slow_link(janus_plugin_session *handle, int mindex, gboolean video, gboolean uplink) { if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) return; @@ -1575,12 +1578,15 @@ static void *janus_recordplay_handler(void *data) { janus_mutex_init(&rec->mutex); /* Check which codec we should record for audio and/or video */ const char *acodec = NULL, *vcodec = NULL; - janus_sdp_find_preferred_codecs(offer, &acodec, &vcodec); if(audiocodec != NULL) acodec = json_string_value(audiocodec); + else + janus_sdp_find_preferred_codec(offer, JANUS_SDP_AUDIO, -1, &acodec); rec->acodec = janus_audiocodec_from_name(acodec); if(videocodec != NULL) vcodec = json_string_value(videocodec); + else + janus_sdp_find_preferred_codec(offer, JANUS_SDP_VIDEO, -1, &vcodec); rec->vcodec = janus_videocodec_from_name(vcodec); /* We found preferred codecs: let's just make sure the direction is what we need */ janus_sdp_mline *m = janus_sdp_mline_find(offer, JANUS_SDP_AUDIO); @@ -1602,7 +1608,7 @@ static void *janus_recordplay_handler(void *data) { int video_pt = -1; if(video_profile != NULL) { /* Check if the provided profile is supported supported */ - video_pt = janus_sdp_get_codec_pt_full(offer, janus_videocodec_name(rec->vcodec), video_profile); + video_pt = janus_sdp_get_codec_pt_full(offer, -1, janus_videocodec_name(rec->vcodec), video_profile); if(video_pt == -1) { JANUS_LOG(LOG_WARN, "No such video codec with profile %s, falling back to plain %s\n", video_profile, janus_videocodec_name(rec->vcodec)); @@ -1610,13 +1616,13 @@ static void *janus_recordplay_handler(void *data) { } } if(video && video_pt == -1) - video_pt = janus_sdp_get_codec_pt(offer, janus_videocodec_name(rec->vcodec)); + video_pt = janus_sdp_get_codec_pt(offer, -1, janus_videocodec_name(rec->vcodec)); g_free(session->video_profile); session->video_profile = NULL; if(video_profile != NULL) session->video_profile = g_strdup(video_profile); if(video && video_pt != -1) { - const char *video_fmtp = janus_sdp_get_fmtp(offer, video_pt); + const char *video_fmtp = janus_sdp_get_fmtp(offer, -1, video_pt); if(video_fmtp != NULL) rec->vfmtp = g_strdup(video_fmtp); } @@ -1676,22 +1682,39 @@ static void *janus_recordplay_handler(void *data) { janus_mutex_unlock(&recordings_mutex); /* We need to prepare an answer */ recdone: - answer = janus_sdp_generate_answer(offer, - JANUS_SDP_OA_AUDIO, audio, - JANUS_SDP_OA_AUDIO_CODEC, janus_audiocodec_name(rec->acodec), - JANUS_SDP_OA_AUDIO_DIRECTION, JANUS_SDP_RECVONLY, - JANUS_SDP_OA_VIDEO, video, - JANUS_SDP_OA_VIDEO_CODEC, janus_videocodec_name(rec->vcodec), - JANUS_SDP_OA_VP9_PROFILE, session->video_profile, - JANUS_SDP_OA_H264_PROFILE, session->video_profile, - JANUS_SDP_OA_VIDEO_DIRECTION, JANUS_SDP_RECVONLY, - JANUS_SDP_OA_DATA, FALSE, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RID, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_REPAIRED_RID, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_FRAME_MARKING, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC, - JANUS_SDP_OA_DONE); + answer = janus_sdp_generate_answer(offer); + gboolean audio_accepted = FALSE, video_accepted = FALSE; + GList *temp = offer->m_lines; + while(temp) { + janus_sdp_mline *m = (janus_sdp_mline *)temp->data; + if(m->type == JANUS_SDP_AUDIO && audio && !audio_accepted) { + audio_accepted = TRUE; + janus_sdp_generate_answer_mline(offer, answer, m, + JANUS_SDP_OA_MLINE, JANUS_SDP_AUDIO, + JANUS_SDP_OA_DIRECTION, JANUS_SDP_RECVONLY, + JANUS_SDP_OA_CODEC, janus_audiocodec_name(rec->acodec), + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_REPAIRED_RID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC, + JANUS_SDP_OA_DONE); + } else if(m->type == JANUS_SDP_VIDEO && video && !video_accepted) { + video_accepted = TRUE; + janus_sdp_generate_answer_mline(offer, answer, m, + JANUS_SDP_OA_MLINE, JANUS_SDP_VIDEO, + JANUS_SDP_OA_DIRECTION, JANUS_SDP_RECVONLY, + JANUS_SDP_OA_CODEC, janus_videocodec_name(rec->vcodec), + JANUS_SDP_OA_VP9_PROFILE, session->video_profile, + JANUS_SDP_OA_H264_PROFILE, session->video_profile, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_REPAIRED_RID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_FRAME_MARKING, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC, + JANUS_SDP_OA_DONE); + } + temp = temp->next; + } g_free(answer->s_name); char s_name[100]; g_snprintf(s_name, sizeof(s_name), "Recording %"SCNu64, rec->id); @@ -2594,7 +2617,7 @@ static void *janus_recordplay_playout_thread(void *sessiondata) { /* Update payload type */ janus_rtp_header *rtp = (janus_rtp_header *)buffer; rtp->type = audio_pt; - janus_plugin_rtp prtp = { .video = FALSE, .buffer = (char *)buffer, .length = bytes }; + janus_plugin_rtp prtp = { .mindex = -1, .video = FALSE, .buffer = (char *)buffer, .length = bytes }; janus_plugin_rtp_extensions_reset(&prtp.extensions); gateway->relay_rtp(session->handle, &prtp); gettimeofday(&now, NULL); @@ -2636,7 +2659,7 @@ static void *janus_recordplay_playout_thread(void *sessiondata) { /* Update payload type */ janus_rtp_header *rtp = (janus_rtp_header *)buffer; rtp->type = audio_pt; - janus_plugin_rtp prtp = { .video = FALSE, .buffer = (char *)buffer, .length = bytes }; + janus_plugin_rtp prtp = { .mindex = -1, .video = FALSE, .buffer = (char *)buffer, .length = bytes }; janus_plugin_rtp_extensions_reset(&prtp.extensions); gateway->relay_rtp(session->handle, &prtp); asent = TRUE; @@ -2656,7 +2679,7 @@ static void *janus_recordplay_playout_thread(void *sessiondata) { /* Update payload type */ janus_rtp_header *rtp = (janus_rtp_header *)buffer; rtp->type = video_pt; - janus_plugin_rtp prtp = { .video = TRUE, .buffer = (char *)buffer, .length = bytes }; + janus_plugin_rtp prtp = { .mindex = -1, .video = TRUE, .buffer = (char *)buffer, .length = bytes }; janus_plugin_rtp_extensions_reset(&prtp.extensions); gateway->relay_rtp(session->handle, &prtp); video = video->next; @@ -2702,7 +2725,7 @@ static void *janus_recordplay_playout_thread(void *sessiondata) { /* Update payload type */ janus_rtp_header *rtp = (janus_rtp_header *)buffer; rtp->type = video_pt; - janus_plugin_rtp prtp = { .video = TRUE, .buffer = (char *)buffer, .length = bytes }; + janus_plugin_rtp prtp = { .mindex = -1, .video = TRUE, .buffer = (char *)buffer, .length = bytes }; janus_plugin_rtp_extensions_reset(&prtp.extensions); gateway->relay_rtp(session->handle, &prtp); video = video->next; @@ -2713,7 +2736,7 @@ static void *janus_recordplay_playout_thread(void *sessiondata) { } if(data) { u_int64_t prev_ts = 0; /* All timestamps for data are indexed to 0, since when parsing ts = when - c_time */ - if(data->prev) + if(data->prev) prev_ts = data->prev->ts; ts_diff = data->ts - prev_ts; @@ -2748,7 +2771,7 @@ static void *janus_recordplay_playout_thread(void *sessiondata) { janus_plugin_data datapacket = { .label = NULL, .protocol = NULL, - .binary = rec->textdata ? FALSE : TRUE, + .binary = rec->textdata ? FALSE : TRUE, .buffer = (char *)buffer, .length = bytes }; diff --git a/plugins/janus_sip.c b/plugins/janus_sip.c index 03d9be6807..93014194e5 100644 --- a/plugins/janus_sip.c +++ b/plugins/janus_sip.c @@ -951,7 +951,7 @@ typedef struct janus_sip_media { char *video_srtp_local_profile, *video_srtp_local_crypto; gboolean video_send; janus_sdp_mdirection pre_hold_video_dir; - janus_rtp_switching_context context; + janus_rtp_switching_context acontext, vcontext; int pipefd[2]; gboolean updated; int video_orientation_extension_id; @@ -1382,7 +1382,8 @@ static void janus_sip_media_reset(janus_sip_session *session) { session->media.pre_hold_video_dir = JANUS_SDP_DEFAULT; session->media.video_orientation_extension_id = -1; session->media.audio_level_extension_id = -1; - janus_rtp_switching_context_reset(&session->media.context); + janus_rtp_switching_context_reset(&session->media.acontext); + janus_rtp_switching_context_reset(&session->media.vcontext); } @@ -2042,7 +2043,8 @@ void janus_sip_create_session(janus_plugin_session *handle, int *error) { session->media.video_orientation_extension_id = -1; session->media.audio_level_extension_id = -1; /* Initialize the RTP context */ - janus_rtp_switching_context_reset(&session->media.context); + janus_rtp_switching_context_reset(&session->media.acontext); + janus_rtp_switching_context_reset(&session->media.vcontext); session->media.pipefd[0] = -1; session->media.pipefd[1] = -1; session->media.updated = FALSE; @@ -5936,19 +5938,20 @@ char *janus_sip_sdp_manipulate(janus_sip_session *session, janus_sdp *sdp, gbool } ma = ma->next; } + /* If we need to remove some payload types from this m-line, do it now */ + if(pts_to_remove != NULL) { + GList *temp = pts_to_remove; + while(temp) { + int pt = GPOINTER_TO_INT(temp->data); + janus_sdp_remove_payload_type(sdp, m->index, pt); + temp = temp->next; + } + g_list_free(pts_to_remove); + pts_to_remove = NULL; + } } temp = temp->next; } - /* If we need to remove some payload types from the SDP, do it now */ - if(pts_to_remove != NULL) { - GList *temp = pts_to_remove; - while(temp) { - int pt = GPOINTER_TO_INT(temp->data); - janus_sdp_remove_payload_type(sdp, pt); - temp = temp->next; - } - g_list_free(pts_to_remove); - } /* Generate a SDP string out of our changes */ return janus_sdp_write(sdp); } @@ -6420,12 +6423,12 @@ static void *janus_sip_relay_thread(void *data) { bytes = buflen; } /* Check if the SSRC changed (e.g., after a re-INVITE or UPDATE) */ - janus_rtp_header_update(header, &session->media.context, FALSE, 0); + janus_rtp_header_update(header, &session->media.acontext, FALSE, 0); /* Save the frame if we're recording */ header->ssrc = htonl(session->media.audio_ssrc_peer); janus_recorder_save_frame(session->arc_peer, buffer, bytes); /* Relay to application */ - janus_plugin_rtp rtp = { .video = FALSE, .buffer = buffer, .length = bytes }; + janus_plugin_rtp rtp = { .mindex = -1, .video = FALSE, .buffer = buffer, .length = bytes }; janus_plugin_rtp_extensions_reset(&rtp.extensions); /* Add audio-level extension, if present */ if(session->media.audio_level_extension_id != -1) { @@ -6460,7 +6463,7 @@ static void *janus_sip_relay_thread(void *data) { bytes = buflen; } /* Relay to application */ - janus_plugin_rtcp rtcp = { .video = FALSE, .buffer = buffer, bytes }; + janus_plugin_rtcp rtcp = { .mindex = -1, .video = FALSE, .buffer = buffer, bytes }; gateway->relay_rtcp(session->handle, &rtcp); continue; } else if(session->media.video_rtp_fd != -1 && fds[i].fd == session->media.video_rtp_fd) { @@ -6491,12 +6494,12 @@ static void *janus_sip_relay_thread(void *data) { bytes = buflen; } /* Check if the SSRC changed (e.g., after a re-INVITE or UPDATE) */ - janus_rtp_header_update(header, &session->media.context, TRUE, 0); + janus_rtp_header_update(header, &session->media.vcontext, TRUE, 0); /* Save the frame if we're recording */ header->ssrc = htonl(session->media.video_ssrc_peer); janus_recorder_save_frame(session->vrc_peer, buffer, bytes); /* Relay to application */ - janus_plugin_rtp rtp = { .video = TRUE, .buffer = buffer, .length = bytes }; + janus_plugin_rtp rtp = { .mindex = -1, .video = TRUE, .buffer = buffer, .length = bytes }; janus_plugin_rtp_extensions_reset(&rtp.extensions); /* Add video-orientation extension, if present */ if(session->media.video_orientation_extension_id > 0) { @@ -6537,7 +6540,7 @@ static void *janus_sip_relay_thread(void *data) { bytes = buflen; } /* Relay to application */ - janus_plugin_rtcp rtcp = { .video = TRUE, .buffer = buffer, bytes }; + janus_plugin_rtcp rtcp = { .mindex = -1, .video = TRUE, .buffer = buffer, bytes }; gateway->relay_rtcp(session->handle, &rtcp); continue; } diff --git a/plugins/janus_streaming.c b/plugins/janus_streaming.c index f5482e3c7a..2102b74f7a 100644 --- a/plugins/janus_streaming.c +++ b/plugins/janus_streaming.c @@ -1039,7 +1039,7 @@ typedef struct janus_streaming_rtp_source { janus_recorder *vrc; /* The Janus recorder instance for this streams's video, if enabled */ janus_recorder *drc; /* The Janus recorder instance for this streams's data, if enabled */ janus_mutex rec_mutex; /* Mutex to protect the recorders from race conditions */ - janus_rtp_switching_context context[3]; + janus_rtp_switching_context acontext, vcontext[3]; int audio_fd; int video_fd[3]; int data_fd; @@ -1208,7 +1208,7 @@ typedef struct janus_streaming_session { volatile gint started; volatile gint paused; gboolean audio, video, data; /* Whether audio, video and/or data must be sent to this listener */ - janus_rtp_switching_context context; + janus_rtp_switching_context acontext, vcontext; janus_rtp_simulcasting_context sim_context; janus_vp8_simulcast_context vp8_context; /* The following are only relevant the mountpoint is VP9-SVC, and are not to be confused with VP8 @@ -4364,7 +4364,8 @@ void janus_streaming_setup_media(janus_plugin_session *handle) { janus_mutex_unlock(&sessions_mutex); g_atomic_int_set(&session->hangingup, 0); /* We only start streaming towards this user when we get this event */ - janus_rtp_switching_context_reset(&session->context); + janus_rtp_switching_context_reset(&session->acontext); + janus_rtp_switching_context_reset(&session->vcontext); /* If this is related to a live RTP mountpoint, any keyframe we can shoot already? */ janus_streaming_mountpoint *mountpoint = session->mountpoint; if (!mountpoint) { @@ -4491,7 +4492,8 @@ static void janus_streaming_hangup_media_internal(janus_plugin_session *handle) g_atomic_int_set(&session->stopping, 1); g_atomic_int_set(&session->started, 0); g_atomic_int_set(&session->paused, 0); - janus_rtp_switching_context_reset(&session->context); + janus_rtp_switching_context_reset(&session->acontext); + janus_rtp_switching_context_reset(&session->vcontext); janus_rtp_simulcasting_context_reset(&session->sim_context); janus_vp8_simulcast_context_reset(&session->vp8_context); session->spatial_layer = -1; @@ -4775,7 +4777,8 @@ static void *janus_streaming_handler(void *data) { goto error; } /* In case this mountpoint is simulcasting, let's aim high by default */ - janus_rtp_switching_context_reset(&session->context); + janus_rtp_switching_context_reset(&session->acontext); + janus_rtp_switching_context_reset(&session->vcontext); janus_rtp_simulcasting_context_reset(&session->sim_context); session->sim_context.substream_target = 2; session->sim_context.templayer_target = 2; @@ -5993,9 +5996,10 @@ janus_streaming_mountpoint *janus_streaming_create_rtp_source( live_rtp_source->arc = NULL; live_rtp_source->vrc = NULL; live_rtp_source->drc = NULL; - janus_rtp_switching_context_reset(&live_rtp_source->context[0]); - janus_rtp_switching_context_reset(&live_rtp_source->context[1]); - janus_rtp_switching_context_reset(&live_rtp_source->context[2]); + janus_rtp_switching_context_reset(&live_rtp_source->acontext); + janus_rtp_switching_context_reset(&live_rtp_source->vcontext[0]); + janus_rtp_switching_context_reset(&live_rtp_source->vcontext[1]); + janus_rtp_switching_context_reset(&live_rtp_source->vcontext[2]); janus_mutex_init(&live_rtp_source->rec_mutex); live_rtp_source->audio_fd = audio_fd; live_rtp_source->audio_rtcp_fd = audio_rtcp_fd; @@ -7803,9 +7807,9 @@ static void *janus_streaming_relay_thread(void *data) { packet.is_keyframe = FALSE; packet.data->type = mountpoint->codecs.audio_pt; /* Is there a recorder? */ - janus_rtp_header_update(packet.data, &source->context[0], FALSE, 0); + janus_rtp_header_update(packet.data, &source->acontext, FALSE, 0); if(source->askew) { - int ret = janus_rtp_skew_compensate_audio(packet.data, &source->context[0], now); + int ret = janus_rtp_skew_compensate_audio(packet.data, &source->acontext, now); if(ret < 0) { JANUS_LOG(LOG_WARN, "[%s] Dropping %d packets, audio source clock is too fast (ssrc=%"SCNu32")\n", name, -ret, a_last_ssrc); @@ -7999,9 +8003,9 @@ static void *janus_streaming_relay_thread(void *data) { } packet.data->type = mountpoint->codecs.video_pt; /* Is there a recorder? (FIXME notice we only record the first substream, if simulcasting) */ - janus_rtp_header_update(packet.data, &source->context[index], TRUE, 0); + janus_rtp_header_update(packet.data, &source->vcontext[index], TRUE, 0); if(source->vskew) { - int ret = janus_rtp_skew_compensate_video(packet.data, &source->context[index], now); + int ret = janus_rtp_skew_compensate_video(packet.data, &source->vcontext[index], now); if(ret < 0) { JANUS_LOG(LOG_WARN, "[%s] Dropping %d packets, video source clock is too fast (ssrc=%"SCNu32", index %d)\n", name, -ret, v_last_ssrc[index], index); @@ -8295,7 +8299,7 @@ static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data) if(spatial_layer < packet->svc_info.spatial_layer) { /* Drop the packet: update the context to make sure sequence number is increased normally later */ JANUS_LOG(LOG_HUGE, "Dropping packet (spatial layer %d < %d)\n", spatial_layer, packet->svc_info.spatial_layer); - session->context.v_base_seq++; + session->vcontext.base_seq++; return; } else if(packet->svc_info.ebit && spatial_layer == packet->svc_info.spatial_layer) { /* If we stop at layer 0, we need a marker bit now, as the one from layer 1 will not be received */ @@ -8343,7 +8347,7 @@ static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data) if(temporal_layer < packet->svc_info.temporal_layer) { /* Drop the packet: update the context to make sure sequence number is increased normally later */ JANUS_LOG(LOG_HUGE, "Dropping packet (temporal layer %d < %d)\n", temporal_layer, packet->svc_info.temporal_layer); - session->context.v_base_seq++; + session->vcontext.base_seq++; return; } /* If we got here, we can send the frame: this doesn't necessarily mean it's @@ -8351,11 +8355,11 @@ static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data) JANUS_LOG(LOG_HUGE, "Sending packet (spatial=%d, temporal=%d)\n", packet->svc_info.spatial_layer, packet->svc_info.temporal_layer); /* Fix sequence number and timestamp (publisher switching may be involved) */ - janus_rtp_header_update(packet->data, &session->context, TRUE, 0); + janus_rtp_header_update(packet->data, &session->vcontext, TRUE, 0); if(override_mark_bit && !has_marker_bit) { packet->data->markerbit = 1; } - janus_plugin_rtp rtp = { .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; + janus_plugin_rtp rtp = { .mindex = -1, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; janus_plugin_rtp_extensions_reset(&rtp.extensions); if(gateway != NULL) gateway->relay_rtp(session->handle, &rtp); @@ -8373,7 +8377,7 @@ static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data) return; /* Process this packet: don't relay if it's not the SSRC/layer we wanted to handle */ gboolean relay = janus_rtp_simulcasting_context_process_rtp(&session->sim_context, - (char *)packet->data, packet->length, packet->ssrc, NULL, packet->codec, &session->context); + (char *)packet->data, packet->length, packet->ssrc, NULL, packet->codec, &session->vcontext); if(!relay) { /* Did a lot of time pass before we could relay a packet? */ gint64 now = janus_get_monotonic_time(); @@ -8415,7 +8419,7 @@ static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data) json_decref(event); } /* If we got here, update the RTP header and send the packet */ - janus_rtp_header_update(packet->data, &session->context, TRUE, 0); + janus_rtp_header_update(packet->data, &session->vcontext, TRUE, 0); char vp8pd[6]; if(packet->codec == JANUS_VIDEOCODEC_VP8) { /* For VP8, we save the original payload descriptor, to restore it after */ @@ -8424,7 +8428,7 @@ static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data) session->sim_context.changed_substream); } /* Send the packet */ - janus_plugin_rtp rtp = { .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; + janus_plugin_rtp rtp = { .mindex = -1, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; janus_plugin_rtp_extensions_reset(&rtp.extensions); if(gateway != NULL) gateway->relay_rtp(session->handle, &rtp); @@ -8437,8 +8441,8 @@ static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data) } } else { /* Fix sequence number and timestamp (switching may be involved) */ - janus_rtp_header_update(packet->data, &session->context, TRUE, 0); - janus_plugin_rtp rtp = { .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; + janus_rtp_header_update(packet->data, &session->vcontext, TRUE, 0); + janus_plugin_rtp rtp = { .mindex = -1, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; janus_plugin_rtp_extensions_reset(&rtp.extensions); if(gateway != NULL) gateway->relay_rtp(session->handle, &rtp); @@ -8450,8 +8454,8 @@ static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data) if(!session->audio) return; /* Fix sequence number and timestamp (switching may be involved) */ - janus_rtp_header_update(packet->data, &session->context, FALSE, 0); - janus_plugin_rtp rtp = { .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; + janus_rtp_header_update(packet->data, &session->acontext, FALSE, 0); + janus_plugin_rtp rtp = { .mindex = -1, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; janus_plugin_rtp_extensions_reset(&rtp.extensions); if(gateway != NULL) gateway->relay_rtp(session->handle, &rtp); @@ -8495,7 +8499,7 @@ static void janus_streaming_relay_rtcp_packet(gpointer data, gpointer user_data) return; } - janus_plugin_rtcp rtcp = { .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; + janus_plugin_rtcp rtcp = { .mindex = -1, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; if(gateway != NULL) gateway->relay_rtcp(session->handle, &rtcp); diff --git a/plugins/janus_textroom.c b/plugins/janus_textroom.c index 6d61721d3c..3b2422300c 100644 --- a/plugins/janus_textroom.c +++ b/plugins/janus_textroom.c @@ -536,7 +536,7 @@ void janus_textroom_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp void janus_textroom_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet); void janus_textroom_incoming_data(janus_plugin_session *handle, janus_plugin_data *packet); void janus_textroom_data_ready(janus_plugin_session *handle); -void janus_textroom_slow_link(janus_plugin_session *handle, int uplink, int video); +void janus_textroom_slow_link(janus_plugin_session *handle, int mindex, int uplink, int video); void janus_textroom_hangup_media(janus_plugin_session *handle); void janus_textroom_destroy_session(janus_plugin_session *handle, int *error); json_t *janus_textroom_query_session(janus_plugin_session *handle); @@ -2835,7 +2835,7 @@ janus_plugin_result *janus_textroom_handle_incoming_request(janus_plugin_session return NULL; } -void janus_textroom_slow_link(janus_plugin_session *handle, int uplink, int video) { +void janus_textroom_slow_link(janus_plugin_session *handle, int mindex, int uplink, int video) { /* We don't do audio/video */ } diff --git a/plugins/janus_videocall.c b/plugins/janus_videocall.c index 8df581b188..9ddd0c308a 100644 --- a/plugins/janus_videocall.c +++ b/plugins/janus_videocall.c @@ -293,7 +293,7 @@ void janus_videocall_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp void janus_videocall_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet); void janus_videocall_incoming_data(janus_plugin_session *handle, janus_plugin_data *packet); void janus_videocall_data_ready(janus_plugin_session *handle); -void janus_videocall_slow_link(janus_plugin_session *handle, int uplink, int video); +void janus_videocall_slow_link(janus_plugin_session *handle, int mindex, gboolean video, gboolean uplink); void janus_videocall_hangup_media(janus_plugin_session *handle); void janus_videocall_destroy_session(janus_plugin_session *handle, int *error); json_t *janus_videocall_query_session(janus_plugin_session *handle); @@ -909,7 +909,7 @@ void janus_videocall_data_ready(janus_plugin_session *handle) { } } -void janus_videocall_slow_link(janus_plugin_session *handle, int uplink, int video) { +void janus_videocall_slow_link(janus_plugin_session *handle, int mindex, gboolean video, gboolean uplink) { /* The core is informing us that our peer got or sent too many NACKs, are we pushing media too hard? */ if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) return; @@ -1377,8 +1377,9 @@ static void *janus_videocall_handler(void *data) { } /* Check which codecs we ended up using */ const char *acodec = NULL, *vcodec = NULL; - janus_sdp_find_first_codecs(answer, &acodec, &vcodec); + janus_sdp_find_first_codec(answer, JANUS_SDP_AUDIO, -1, &acodec); session->acodec = janus_audiocodec_from_name(acodec); + janus_sdp_find_first_codec(answer, JANUS_SDP_VIDEO, -1, &vcodec); session->vcodec = janus_videocodec_from_name(vcodec); if(session->acodec == JANUS_AUDIOCODEC_NONE) { session->has_audio = FALSE; diff --git a/plugins/janus_videoroom.c b/plugins/janus_videoroom.c index 9620051d12..c432d4d45d 100644 --- a/plugins/janus_videoroom.c +++ b/plugins/janus_videoroom.c @@ -1156,7 +1156,7 @@ void janus_videoroom_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp void janus_videoroom_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet); void janus_videoroom_incoming_data(janus_plugin_session *handle, janus_plugin_data *packet); void janus_videoroom_data_ready(janus_plugin_session *handle); -void janus_videoroom_slow_link(janus_plugin_session *handle, int uplink, int video); +void janus_videoroom_slow_link(janus_plugin_session *handle, int mindex, gboolean video, gboolean uplink); void janus_videoroom_hangup_media(janus_plugin_session *handle); void janus_videoroom_destroy_session(janus_plugin_session *handle, int *error); json_t *janus_videoroom_query_session(janus_plugin_session *handle); @@ -1494,7 +1494,7 @@ typedef struct janus_videoroom_rtp_forwarder { GSource *rtcp_recv; /* Only needed when forwarding simulcasted streams to a single endpoint */ gboolean simulcast; - janus_rtp_switching_context context; + janus_rtp_switching_context acontext, vcontext; janus_rtp_simulcasting_context sim_context; /* Only needed for SRTP forwarders */ gboolean is_srtp; @@ -1629,7 +1629,7 @@ typedef struct janus_videoroom_subscriber { gboolean close_pc; /* Whether we should automatically close the PeerConnection when the publisher goes away */ guint32 pvt_id; /* Private ID of the participant that is subscribing (if available/provided) */ janus_sdp *sdp; /* Offer we sent this listener (may be updated within renegotiations) */ - janus_rtp_switching_context context; /* Needed in case there are publisher switches on this subscriber */ + janus_rtp_switching_context acontext, vcontext; /* Needed in case there are publisher switches on this subscriber */ janus_rtp_simulcasting_context sim_context; janus_vp8_simulcast_context vp8_context; gboolean audio, video, data; /* Whether audio, video and/or data must be sent to this subscriber */ @@ -1981,7 +1981,8 @@ static guint32 janus_videoroom_rtp_forwarder_add_helper(janus_videoroom_publishe } if(is_video && simulcast) { forward->simulcast = TRUE; - janus_rtp_switching_context_reset(&forward->context); + janus_rtp_switching_context_reset(&forward->acontext); + janus_rtp_switching_context_reset(&forward->vcontext); janus_rtp_simulcasting_context_reset(&forward->sim_context); forward->sim_context.rid_ext_id = p->rid_extmap_id; forward->sim_context.substream_target = 2; @@ -4999,9 +5000,9 @@ void janus_videoroom_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp } else if(video && rtp_forward->simulcast) { /* This is video and we're simulcasting, check if we need to forward this frame */ if(!janus_rtp_simulcasting_context_process_rtp(&rtp_forward->sim_context, - buf, len, participant->ssrc, participant->rid, participant->vcodec, &rtp_forward->context)) + buf, len, participant->ssrc, participant->rid, participant->vcodec, &rtp_forward->vcontext)) continue; - janus_rtp_header_update(rtp, &rtp_forward->context, TRUE, 0); + janus_rtp_header_update(rtp, &rtp_forward->vcontext, TRUE, 0); /* By default we use a fixed SSRC (it may be overwritten later) */ rtp->ssrc = htonl(participant->user_id & 0xffffffff); } @@ -5266,7 +5267,7 @@ void janus_videoroom_data_ready(janus_plugin_session *handle) { } } -void janus_videoroom_slow_link(janus_plugin_session *handle, int uplink, int video) { +void janus_videoroom_slow_link(janus_plugin_session *handle, int mindex, gboolean video, gboolean uplink) { /* The core is informing us that our peer got too many NACKs, are we pushing media too hard? */ if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) return; @@ -6107,7 +6108,8 @@ static void *janus_videoroom_handler(void *data) { subscriber->pvt_id = pvt_id; subscriber->close_pc = close_pc; /* Initialize the subscriber context */ - janus_rtp_switching_context_reset(&subscriber->context); + janus_rtp_switching_context_reset(&subscriber->acontext); + janus_rtp_switching_context_reset(&subscriber->vcontext); subscriber->audio_offered = offer_audio ? json_is_true(offer_audio) : TRUE; /* True by default */ subscriber->video_offered = offer_video ? json_is_true(offer_video) : TRUE; /* True by default */ subscriber->data_offered = offer_data ? json_is_true(offer_data) : TRUE; /* True by default */ @@ -6303,7 +6305,7 @@ static void *janus_videoroom_handler(void *data) { while(ps) { janus_videoroom_subscriber *l = (janus_videoroom_subscriber *)ps->data; if(l) - l->context.a_seq_reset = TRUE; + l->acontext.seq_reset = TRUE; ps = ps->next; } janus_mutex_unlock(&participant->subscribers_mutex); @@ -6339,7 +6341,7 @@ static void *janus_videoroom_handler(void *data) { while(ps) { janus_videoroom_subscriber *l = (janus_videoroom_subscriber *)ps->data; if(l) - l->context.v_seq_reset = TRUE; + l->vcontext.seq_reset = TRUE; ps = ps->next; } janus_mutex_unlock(&participant->subscribers_mutex); @@ -6534,8 +6536,8 @@ static void *janus_videoroom_handler(void *data) { /* Start/restart receiving the publisher streams */ if(subscriber->paused && msg->jsep == NULL) { /* This is just resuming a paused stream, reset the RTP sequence numbers */ - subscriber->context.a_seq_reset = TRUE; - subscriber->context.v_seq_reset = TRUE; + subscriber->acontext.seq_reset = TRUE; + subscriber->vcontext.seq_reset = TRUE; } subscriber->paused = FALSE; event = json_object(); @@ -6591,7 +6593,7 @@ static void *janus_videoroom_handler(void *data) { gboolean newaudio = json_is_true(audio); if(!oldaudio && newaudio) { /* Audio just resumed, reset the RTP sequence numbers */ - subscriber->context.a_seq_reset = TRUE; + subscriber->acontext.seq_reset = TRUE; } subscriber->audio = newaudio; } @@ -6600,7 +6602,7 @@ static void *janus_videoroom_handler(void *data) { gboolean newvideo = json_is_true(video); if(!oldvideo && newvideo) { /* Video just resumed, reset the RTP sequence numbers */ - subscriber->context.v_seq_reset = TRUE; + subscriber->vcontext.seq_reset = TRUE; } subscriber->video = newvideo; if(subscriber->video) { @@ -7140,7 +7142,7 @@ static void *janus_videoroom_handler(void *data) { for(i=0; i<3; i++) { if(videoroom->acodec[i] == JANUS_AUDIOCODEC_NONE) continue; - if(janus_sdp_get_codec_pt(offer, janus_audiocodec_name(videoroom->acodec[i])) != -1) { + if(janus_sdp_get_codec_pt(offer, -1, janus_audiocodec_name(videoroom->acodec[i])) != -1) { participant->acodec = videoroom->acodec[i]; break; } @@ -7161,7 +7163,7 @@ static void *janus_videoroom_handler(void *data) { continue; if(videoroom->vcodec[i] == JANUS_VIDEOCODEC_VP9 && vp9_profile) { /* Check if this VP9 profile is available */ - if(janus_sdp_get_codec_pt_full(offer, janus_videocodec_name(videoroom->vcodec[i]), vp9_profile) != -1) { + if(janus_sdp_get_codec_pt_full(offer, -1, janus_videocodec_name(videoroom->vcodec[i]), vp9_profile) != -1) { /* It is */ h264_profile = NULL; participant->vcodec = videoroom->vcodec[i]; @@ -7171,7 +7173,7 @@ static void *janus_videoroom_handler(void *data) { vp9_profile = NULL; } else if(videoroom->vcodec[i] == JANUS_VIDEOCODEC_H264 && h264_profile) { /* Check if this H.264 profile is available */ - if(janus_sdp_get_codec_pt_full(offer, janus_videocodec_name(videoroom->vcodec[i]), h264_profile) != -1) { + if(janus_sdp_get_codec_pt_full(offer, -1, janus_videocodec_name(videoroom->vcodec[i]), h264_profile) != -1) { /* It is */ vp9_profile = NULL; participant->vcodec = videoroom->vcodec[i]; @@ -7181,7 +7183,7 @@ static void *janus_videoroom_handler(void *data) { h264_profile = NULL; } /* Check if the codec is available */ - if(janus_sdp_get_codec_pt(offer, janus_videocodec_name(videoroom->vcodec[i])) != -1) { + if(janus_sdp_get_codec_pt(offer, -1, janus_videocodec_name(videoroom->vcodec[i])) != -1) { participant->vcodec = videoroom->vcodec[i]; break; } @@ -7189,23 +7191,48 @@ static void *janus_videoroom_handler(void *data) { } JANUS_LOG(LOG_VERB, "The publisher is going to use the %s video codec\n", janus_videocodec_name(participant->vcodec)); participant->video_pt = janus_videocodec_pt(participant->vcodec); - janus_sdp *answer = janus_sdp_generate_answer(offer, - JANUS_SDP_OA_AUDIO_CODEC, janus_audiocodec_name(participant->acodec), - JANUS_SDP_OA_AUDIO_DIRECTION, JANUS_SDP_RECVONLY, - JANUS_SDP_OA_AUDIO_FMTP, audio_fmtp ? audio_fmtp : (participant->do_opusfec ? "useinbandfec=1" : NULL), - JANUS_SDP_OA_VIDEO_CODEC, janus_videocodec_name(participant->vcodec), - JANUS_SDP_OA_VP9_PROFILE, vp9_profile, - JANUS_SDP_OA_H264_PROFILE, h264_profile, - JANUS_SDP_OA_VIDEO_DIRECTION, JANUS_SDP_RECVONLY, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RID, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_REPAIRED_RID, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_FRAME_MARKING, - JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->audiolevel_ext ? JANUS_RTP_EXTMAP_AUDIO_LEVEL : NULL, - JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->videoorient_ext ? JANUS_RTP_EXTMAP_VIDEO_ORIENTATION : NULL, - JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->playoutdelay_ext ? JANUS_RTP_EXTMAP_PLAYOUT_DELAY : NULL, - JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->transport_wide_cc_ext ? JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC : NULL, - JANUS_SDP_OA_DONE); + janus_sdp *answer = janus_sdp_generate_answer(offer); + gboolean audio_accepted = FALSE, video_accepted = FALSE, data_accepted = FALSE; + temp = offer->m_lines; + while(temp) { + janus_sdp_mline *m = (janus_sdp_mline *)temp->data; + if(m->type == JANUS_SDP_AUDIO && participant->audio && !audio_accepted) { + audio_accepted = TRUE; + janus_sdp_generate_answer_mline(offer, answer, m, + JANUS_SDP_OA_MLINE, JANUS_SDP_AUDIO, + JANUS_SDP_OA_CODEC, janus_audiocodec_name(participant->acodec), + JANUS_SDP_OA_DIRECTION, JANUS_SDP_RECVONLY, + JANUS_SDP_OA_FMTP, audio_fmtp ? audio_fmtp : (participant->do_opusfec ? "useinbandfec=1" : NULL), + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_REPAIRED_RID, + JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->audiolevel_ext ? JANUS_RTP_EXTMAP_AUDIO_LEVEL : NULL, + JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->transport_wide_cc_ext ? JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC : NULL, + JANUS_SDP_OA_DONE); + } else if(m->type == JANUS_SDP_VIDEO && participant->video && !video_accepted) { + video_accepted = TRUE; + janus_sdp_generate_answer_mline(offer, answer, m, + JANUS_SDP_OA_MLINE, JANUS_SDP_VIDEO, + JANUS_SDP_OA_CODEC, janus_videocodec_name(participant->vcodec), + JANUS_SDP_OA_DIRECTION, JANUS_SDP_RECVONLY, + JANUS_SDP_OA_VP9_PROFILE, vp9_profile, + JANUS_SDP_OA_H264_PROFILE, h264_profile, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_REPAIRED_RID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_FRAME_MARKING, + JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->videoorient_ext ? JANUS_RTP_EXTMAP_VIDEO_ORIENTATION : NULL, + JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->playoutdelay_ext ? JANUS_RTP_EXTMAP_PLAYOUT_DELAY : NULL, + JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->transport_wide_cc_ext ? JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC : NULL, + JANUS_SDP_OA_DONE); + } else if(m->type == JANUS_SDP_APPLICATION && participant->data && !data_accepted) { + data_accepted = TRUE; + janus_sdp_generate_answer_mline(offer, answer, m, + JANUS_SDP_OA_MLINE, JANUS_SDP_APPLICATION, + JANUS_SDP_OA_DONE); + } + temp = temp->next; + } janus_sdp_destroy(offer); /* Replace the session name */ g_free(answer->s_name); @@ -7262,7 +7289,7 @@ static void *janus_videoroom_handler(void *data) { int video_pt = -1; if(m->ptypes && m->ptypes->data) video_pt = GPOINTER_TO_INT(m->ptypes->data); - video_profile = janus_sdp_get_fmtp(answer, video_pt); + video_profile = janus_sdp_get_fmtp(answer, -1, video_pt); if(video_profile != NULL) participant->vfmtp = g_strdup(video_profile); } @@ -7287,27 +7314,30 @@ static void *janus_videoroom_handler(void *data) { twcc_ext_id++; } offer = janus_sdp_generate_offer(s_name, answer->c_addr, - JANUS_SDP_OA_AUDIO, participant->audio, - JANUS_SDP_OA_AUDIO_CODEC, janus_audiocodec_name(participant->acodec), - JANUS_SDP_OA_AUDIO_PT, janus_audiocodec_pt(participant->acodec), - JANUS_SDP_OA_AUDIO_DIRECTION, JANUS_SDP_SENDONLY, - JANUS_SDP_OA_AUDIO_FMTP, audio_fmtp ? audio_fmtp : (participant->do_opusfec ? "useinbandfec=1" : NULL), - JANUS_SDP_OA_AUDIO_EXTENSION, JANUS_RTP_EXTMAP_AUDIO_LEVEL, - participant->audio_level_extmap_id > 0 ? participant->audio_level_extmap_id : 0, - JANUS_SDP_OA_AUDIO_EXTENSION, JANUS_RTP_EXTMAP_MID, mid_ext_id, - JANUS_SDP_OA_VIDEO, participant->video, - JANUS_SDP_OA_VIDEO_CODEC, janus_videocodec_name(participant->vcodec), - JANUS_SDP_OA_VIDEO_PT, janus_videocodec_pt(participant->vcodec), - JANUS_SDP_OA_VIDEO_FMTP, video_profile, - JANUS_SDP_OA_VIDEO_DIRECTION, JANUS_SDP_SENDONLY, - JANUS_SDP_OA_VIDEO_EXTENSION, JANUS_RTP_EXTMAP_MID, mid_ext_id, - JANUS_SDP_OA_VIDEO_EXTENSION, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION, - participant->video_orient_extmap_id > 0 ? participant->video_orient_extmap_id : 0, - JANUS_SDP_OA_VIDEO_EXTENSION, JANUS_RTP_EXTMAP_PLAYOUT_DELAY, - participant->playout_delay_extmap_id > 0 ? participant->playout_delay_extmap_id : 0, - JANUS_SDP_OA_VIDEO_EXTENSION, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC, - videoroom->transport_wide_cc_ext ? twcc_ext_id : 0, - JANUS_SDP_OA_DATA, participant->data, + JANUS_SDP_OA_MLINE, JANUS_SDP_AUDIO, + JANUS_SDP_OA_ENABLED, participant->audio, + JANUS_SDP_OA_CODEC, janus_audiocodec_name(participant->acodec), + JANUS_SDP_OA_PT, janus_audiocodec_pt(participant->acodec), + JANUS_SDP_OA_DIRECTION, JANUS_SDP_SENDONLY, + JANUS_SDP_OA_FMTP, audio_fmtp ? audio_fmtp : (participant->do_opusfec ? "useinbandfec=1" : NULL), + JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_AUDIO_LEVEL, + participant->audio_level_extmap_id > 0 ? participant->audio_level_extmap_id : 0, + JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_MID, mid_ext_id, + JANUS_SDP_OA_MLINE, JANUS_SDP_VIDEO, + JANUS_SDP_OA_ENABLED, participant->video, + JANUS_SDP_OA_CODEC, janus_videocodec_name(participant->vcodec), + JANUS_SDP_OA_PT, janus_videocodec_pt(participant->vcodec), + JANUS_SDP_OA_FMTP, video_profile, + JANUS_SDP_OA_DIRECTION, JANUS_SDP_SENDONLY, + JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_MID, mid_ext_id, + JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION, + participant->video_orient_extmap_id > 0 ? participant->video_orient_extmap_id : 0, + JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_PLAYOUT_DELAY, + participant->playout_delay_extmap_id > 0 ? participant->playout_delay_extmap_id : 0, + JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC, + videoroom->transport_wide_cc_ext ? twcc_ext_id : 0, + JANUS_SDP_OA_MLINE, JANUS_SDP_APPLICATION, + JANUS_SDP_OA_ENABLED, participant->data, JANUS_SDP_OA_DONE); /* Is this room recorded, or are we recording this publisher already? */ janus_mutex_lock(&participant->rec_mutex); @@ -7525,7 +7555,7 @@ static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) if(spatial_layer < packet->svc_info.spatial_layer) { /* Drop the packet: update the context to make sure sequence number is increased normally later */ JANUS_LOG(LOG_HUGE, "Dropping packet (spatial layer %d < %d)\n", spatial_layer, packet->svc_info.spatial_layer); - subscriber->context.v_base_seq++; + subscriber->vcontext.base_seq++; return; } else if(packet->svc_info.ebit && spatial_layer == packet->svc_info.spatial_layer) { /* If we stop at layer 0, we need a marker bit now, as the one from layer 1 will not be received */ @@ -7571,7 +7601,7 @@ static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) if(temporal_layer < packet->svc_info.temporal_layer) { /* Drop the packet: update the context to make sure sequence number is increased normally later */ JANUS_LOG(LOG_HUGE, "Dropping packet (temporal layer %d < %d)\n", temporal_layer, packet->svc_info.temporal_layer); - subscriber->context.v_base_seq++; + subscriber->vcontext.base_seq++; return; } /* If we got here, we can send the frame: this doesn't necessarily mean it's @@ -7579,12 +7609,12 @@ static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) JANUS_LOG(LOG_HUGE, "Sending packet (spatial=%d, temporal=%d)\n", packet->svc_info.spatial_layer, packet->svc_info.temporal_layer); /* Fix sequence number and timestamp (publisher switching may be involved) */ - janus_rtp_header_update(packet->data, &subscriber->context, TRUE, 0); + janus_rtp_header_update(packet->data, &subscriber->vcontext, TRUE, 0); if(override_mark_bit && !has_marker_bit) { packet->data->markerbit = 1; } if(gateway != NULL) { - janus_plugin_rtp rtp = { .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length, + janus_plugin_rtp rtp = { .mindex = -1, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length, .extensions = packet->extensions }; gateway->relay_rtp(session->handle, &rtp); } @@ -7602,7 +7632,7 @@ static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) return; /* Process this packet: don't relay if it's not the SSRC/layer we wanted to handle */ gboolean relay = janus_rtp_simulcasting_context_process_rtp(&subscriber->sim_context, - (char *)packet->data, packet->length, packet->ssrc, NULL, subscriber->feed->vcodec, &subscriber->context); + (char *)packet->data, packet->length, packet->ssrc, NULL, subscriber->feed->vcodec, &subscriber->vcontext); if(!relay) { /* Did a lot of time pass before we could relay a packet? */ gint64 now = janus_get_monotonic_time(); @@ -7639,7 +7669,7 @@ static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) json_decref(event); } /* If we got here, update the RTP header and send the packet */ - janus_rtp_header_update(packet->data, &subscriber->context, TRUE, 0); + janus_rtp_header_update(packet->data, &subscriber->vcontext, TRUE, 0); char vp8pd[6]; if(subscriber->feed && subscriber->feed->vcodec == JANUS_VIDEOCODEC_VP8) { /* For VP8, we save the original payload descriptor, to restore it after */ @@ -7649,7 +7679,7 @@ static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) } /* Send the packet */ if(gateway != NULL) { - janus_plugin_rtp rtp = { .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length, + janus_plugin_rtp rtp = { .mindex = -1, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length, .extensions = packet->extensions }; gateway->relay_rtp(session->handle, &rtp); } @@ -7662,10 +7692,10 @@ static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) } } else { /* Fix sequence number and timestamp (publisher switching may be involved) */ - janus_rtp_header_update(packet->data, &subscriber->context, TRUE, 0); + janus_rtp_header_update(packet->data, &subscriber->vcontext, TRUE, 0); /* Send the packet */ if(gateway != NULL) { - janus_plugin_rtp rtp = { .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length, + janus_plugin_rtp rtp = { .mindex = -1, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length, .extensions = packet->extensions }; gateway->relay_rtp(session->handle, &rtp); } @@ -7680,10 +7710,10 @@ static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) return; } /* Fix sequence number and timestamp (publisher switching may be involved) */ - janus_rtp_header_update(packet->data, &subscriber->context, FALSE, 0); + janus_rtp_header_update(packet->data, &subscriber->acontext, FALSE, 0); /* Send the packet */ if(gateway != NULL) { - janus_plugin_rtp rtp = { .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length, + janus_plugin_rtp rtp = { .mindex = -1, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length, .extensions = packet->extensions }; gateway->relay_rtp(session->handle, &rtp); } diff --git a/plugins/plugin.c b/plugins/plugin.c index 22a578258e..080dd7e08f 100644 --- a/plugins/plugin.c +++ b/plugins/plugin.c @@ -49,12 +49,15 @@ void janus_plugin_rtp_extensions_reset(janus_plugin_rtp_extensions *extensions) void janus_plugin_rtp_reset(janus_plugin_rtp *packet) { if(packet) { memset(packet, 0, sizeof(janus_plugin_rtp)); + packet->mindex = -1; janus_plugin_rtp_extensions_reset(&packet->extensions); } } void janus_plugin_rtcp_reset(janus_plugin_rtcp *packet) { - if(packet) + if(packet) { memset(packet, 0, sizeof(janus_plugin_rtcp)); + packet->mindex = -1; + } } void janus_plugin_data_reset(janus_plugin_data *packet) { if(packet) diff --git a/plugins/plugin.h b/plugins/plugin.h index 7794dd4923..bcaeecb510 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -66,7 +66,7 @@ janus_plugin *create(void) { * - \c incoming_rtcp(): a callback to notify you a peer has sent you a RTCP message; * - \c incoming_data(): a callback to notify you a peer has sent you a message on a SCTP DataChannel; * - \c data_ready(): a callback to notify you data can be sent on the SCTP DataChannel; - * - \c slow_link(): a callback to notify you a peer has sent a lot of NACKs recently, and the media path may be slow; + * - \c slow_link(): a callback to notify you Janus or the peer have lost packets recently, and the media path may be slow; * - \c hangup_media(): a callback to notify you the peer PeerConnection has been closed (e.g., after a DTLS alert); * - \c query_session(): this method is called by the core to get plugin-specific info on a session between you and a peer; * - \c destroy_session(): this method is called by the core to destroy a session between you and a peer. @@ -330,10 +330,11 @@ struct janus_plugin { * Nevertheless, it can be useful for debugging, or for informing your * users about potential issues that may be happening media-wise. * @param[in] handle The plugin/gateway session used for this peer + * @param[in] mindex Index of the stream the event refers to (relative to the SDP) + * @param[in] video Whether this is related to an audio or a video stream * @param[in] uplink Whether this is related to the uplink (Janus to peer) - * or downlink (peer to Janus) - * @param[in] video Whether this is related to an audio or a video stream */ - void (* const slow_link)(janus_plugin_session *handle, gboolean uplink, gboolean video); + * or downlink (peer to Janus) */ + void (* const slow_link)(janus_plugin_session *handle, int mindex, gboolean video, gboolean uplink); /*! \brief Callback to be notified about DTLS alerts from a peer (i.e., the PeerConnection is not valid any more) * @param[in] handle The plugin/gateway session used for this peer */ void (* const hangup_media)(janus_plugin_session *handle); @@ -569,6 +570,13 @@ void janus_plugin_rtp_extensions_reset(janus_plugin_rtp_extensions *extensions); /*! \brief Janus plugin RTP packet */ struct janus_plugin_rtp { + /*! \brief Index of the stream (relative to the SDP) + * @note On outgoing packets you can set this to -1, to let the Janus + * core find the first audio/video (depending on the \c video property) + * to send this on; notice that this tweak is only there for convenience, + * and to make it easier for plugins not dealing with multistream, but + * this shouldn't be relied upon too much as it may go away soon. */ + int mindex; /*! \brief Whether this is an audio or video RTP packet */ gboolean video; /*! \brief The packet data */ @@ -587,6 +595,13 @@ void janus_plugin_rtp_reset(janus_plugin_rtp *packet); /*! \brief Janus plugin RTCP packet */ struct janus_plugin_rtcp { + /*! \brief Index of the stream (relative to the SDP) + * @note On outgoing packets you can set this to -1, to let the Janus + * core find the first audio/video (depending on the \c video property) + * to send this on; notice that this tweak is only there for convenience, + * and to make it easier for plugins not dealing with multistream, but + * this shouldn't be relied upon too much as it may go away soon. */ + int mindex; /*! \brief Whether this is an audio or video RTCP packet */ gboolean video; /*! \brief The packet data */ diff --git a/rtp.c b/rtp.c index 6ff942278d..f11d58c305 100644 --- a/rtp.c +++ b/rtp.c @@ -377,18 +377,18 @@ void janus_rtp_switching_context_reset(janus_rtp_switching_context *context) { int janus_rtp_skew_compensate_audio(janus_rtp_header *header, janus_rtp_switching_context *context, gint64 now) { /* Reset values if a new ssrc has been detected */ - if (context->a_new_ssrc) { - JANUS_LOG(LOG_VERB, "audio skew SSRC=%"SCNu32" resetting status\n", context->a_last_ssrc); - context->a_reference_time = now; - context->a_start_time = 0; - context->a_evaluating_start_time = 0; - context->a_start_ts = 0; - context->a_active_delay = 0; - context->a_prev_delay = 0; - context->a_seq_offset = 0; - context->a_ts_offset = 0; - context->a_target_ts = 0; - context->a_new_ssrc = FALSE; + if(context->new_ssrc) { + JANUS_LOG(LOG_VERB, "audio skew SSRC=%"SCNu32" resetting status\n", context->last_ssrc); + context->reference_time = now; + context->start_time = 0; + context->evaluating_start_time = 0; + context->start_ts = 0; + context->active_delay = 0; + context->prev_delay = 0; + context->seq_offset = 0; + context->ts_offset = 0; + context->target_ts = 0; + context->new_ssrc = FALSE; } /* N : a N sequence number jump has been performed */ @@ -397,84 +397,84 @@ int janus_rtp_skew_compensate_audio(janus_rtp_header *header, janus_rtp_switchin int exit_status = 0; /* Do not execute skew analysis in the first seconds */ - if (now-context->a_reference_time < SKEW_DETECTION_WAIT_TIME_SECS/2 * G_USEC_PER_SEC) { + if(now-context->reference_time < SKEW_DETECTION_WAIT_TIME_SECS/2 * G_USEC_PER_SEC) { return 0; - } else if (!context->a_start_time) { - JANUS_LOG(LOG_VERB, "audio skew SSRC=%"SCNu32" evaluation phase start\n", context->a_last_ssrc); - context->a_start_time = now; - context->a_evaluating_start_time = now; - context->a_start_ts = context->a_last_ts; + } else if(!context->start_time) { + JANUS_LOG(LOG_VERB, "audio skew SSRC=%"SCNu32" evaluation phase start\n", context->last_ssrc); + context->start_time = now; + context->evaluating_start_time = now; + context->start_ts = context->last_ts; } /* Skew analysis */ /* Are we waiting for a target timestamp? (a negative skew has been evaluated in a previous iteration) */ - if (context->a_target_ts > 0 && (gint32)(context->a_target_ts - context->a_last_ts) > 0) { - context->a_seq_offset--; + if(context->target_ts > 0 && (gint32)(context->target_ts - context->last_ts) > 0) { + context->seq_offset--; exit_status = -1; } else { - context->a_target_ts = 0; + context->target_ts = 0; /* Do not execute analysis for out of order packets or multi-packets frame */ - if (context->a_last_seq == context->a_prev_seq + 1 && context->a_last_ts != context->a_prev_ts) { + if(context->last_seq == context->prev_seq + 1 && context->last_ts != context->prev_ts) { /* Set the sample rate according to the header */ guint32 akhz = 48; /* 48khz for Opus */ if(header->type == 0 || header->type == 8 || header->type == 9) akhz = 8; /* Evaluate the local RTP timestamp according to the local clock */ - guint32 expected_ts = ((now - context->a_start_time)*akhz)/1000 + context->a_start_ts; + guint32 expected_ts = ((now - context->start_time)*akhz)/1000 + context->start_ts; /* Evaluate current delay */ - gint32 delay_now = context->a_last_ts - expected_ts; + gint32 delay_now = context->last_ts - expected_ts; /* Exponentially weighted moving average estimation */ - gint32 delay_estimate = (63*context->a_prev_delay + delay_now)/64; + gint32 delay_estimate = (63*context->prev_delay + delay_now)/64; /* Save previous delay for the next iteration*/ - context->a_prev_delay = delay_estimate; + context->prev_delay = delay_estimate; /* Evaluate the distance between active delay and current delay estimate */ - gint32 offset = context->a_active_delay - delay_estimate; - JANUS_LOG(LOG_HUGE, "audio skew status SSRC=%"SCNu32" RECVD_TS=%"SCNu32" EXPTD_TS=%"SCNu32" OFFSET=%"SCNi32" TS_OFFSET=%"SCNi32" SEQ_OFFSET=%"SCNi16"\n", context->a_last_ssrc, context->a_last_ts, expected_ts, offset, context->a_ts_offset, context->a_seq_offset); + gint32 offset = context->active_delay - delay_estimate; + JANUS_LOG(LOG_HUGE, "audio skew status SSRC=%"SCNu32" RECVD_TS=%"SCNu32" EXPTD_TS=%"SCNu32" OFFSET=%"SCNi32" TS_OFFSET=%"SCNi32" SEQ_OFFSET=%"SCNi16"\n", context->last_ssrc, context->last_ts, expected_ts, offset, context->ts_offset, context->seq_offset); gint32 skew_th = RTP_AUDIO_SKEW_TH_MS*akhz; /* Evaluation phase */ - if (context->a_evaluating_start_time > 0) { + if(context->evaluating_start_time > 0) { /* Check if the offset has surpassed half the threshold during the evaluating phase */ - if (now-context->a_evaluating_start_time <= SKEW_DETECTION_WAIT_TIME_SECS/2 * G_USEC_PER_SEC) { - if (abs(offset) <= skew_th/2) { - JANUS_LOG(LOG_HUGE, "audio skew SSRC=%"SCNu32" evaluation phase continue\n", context->a_last_ssrc); + if(now-context->evaluating_start_time <= SKEW_DETECTION_WAIT_TIME_SECS/2 * G_USEC_PER_SEC) { + if(abs(offset) <= skew_th/2) { + JANUS_LOG(LOG_HUGE, "audio skew SSRC=%"SCNu32" evaluation phase continue\n", context->last_ssrc); } else { - JANUS_LOG(LOG_VERB, "audio skew SSRC=%"SCNu32" evaluation phase reset\n", context->a_last_ssrc); - context->a_start_time = now; - context->a_evaluating_start_time = now; - context->a_start_ts = context->a_last_ts; + JANUS_LOG(LOG_VERB, "audio skew SSRC=%"SCNu32" evaluation phase reset\n", context->last_ssrc); + context->start_time = now; + context->evaluating_start_time = now; + context->start_ts = context->last_ts; } } else { - JANUS_LOG(LOG_VERB, "audio skew SSRC=%"SCNu32" evaluation phase stop\n", context->a_last_ssrc); - context->a_evaluating_start_time = 0; + JANUS_LOG(LOG_VERB, "audio skew SSRC=%"SCNu32" evaluation phase stop\n", context->last_ssrc); + context->evaluating_start_time = 0; } return 0; } /* Check if the offset has surpassed the threshold */ - if (offset >= skew_th) { + if(offset >= skew_th) { /* The source is slowing down */ /* Update active delay */ - context->a_active_delay = delay_estimate; + context->active_delay = delay_estimate; /* Adjust ts offset */ - context->a_ts_offset += skew_th; + context->ts_offset += skew_th; /* Calculate last ts increase */ - guint32 ts_incr = context->a_last_ts-context->a_prev_ts; + guint32 ts_incr = context->last_ts-context->prev_ts; /* Evaluate sequence number jump */ guint16 jump = (skew_th+ts_incr-1)/ts_incr; /* Adjust seq num offset */ - context->a_seq_offset += jump; + context->seq_offset += jump; exit_status = jump; - } else if (offset <= -skew_th) { + } else if(offset <= -skew_th) { /* The source is speeding up*/ /* Update active delay */ - context->a_active_delay = delay_estimate; + context->active_delay = delay_estimate; /* Adjust ts offset */ - context->a_ts_offset -= skew_th; + context->ts_offset -= skew_th; /* Set target ts */ - context->a_target_ts = context->a_last_ts + skew_th; - if (context->a_target_ts == 0) - context->a_target_ts = 1; + context->target_ts = context->last_ts + skew_th; + if (context->target_ts == 0) + context->target_ts = 1; /* Adjust seq num offset */ - context->a_seq_offset--; + context->seq_offset--; exit_status = -1; } } @@ -482,10 +482,10 @@ int janus_rtp_skew_compensate_audio(janus_rtp_header *header, janus_rtp_switchin /* Skew compensation */ /* Fix header timestamp considering the active offset */ - guint32 fixed_rtp_ts = context->a_last_ts + context->a_ts_offset; + guint32 fixed_rtp_ts = context->last_ts + context->ts_offset; header->timestamp = htonl(fixed_rtp_ts); /* Fix header sequence number considering the total offset */ - guint16 fixed_rtp_seq = context->a_last_seq + context->a_seq_offset; + guint16 fixed_rtp_seq = context->last_seq + context->seq_offset; header->seq_number = htons(fixed_rtp_seq); return exit_status; @@ -493,18 +493,18 @@ int janus_rtp_skew_compensate_audio(janus_rtp_header *header, janus_rtp_switchin int janus_rtp_skew_compensate_video(janus_rtp_header *header, janus_rtp_switching_context *context, gint64 now) { /* Reset values if a new ssrc has been detected */ - if (context->v_new_ssrc) { - JANUS_LOG(LOG_VERB, "video skew SSRC=%"SCNu32" resetting status\n", context->v_last_ssrc); - context->v_reference_time = now; - context->v_start_time = 0; - context->v_evaluating_start_time = 0; - context->v_start_ts = 0; - context->v_active_delay = 0; - context->v_prev_delay = 0; - context->v_seq_offset = 0; - context->v_ts_offset = 0; - context->v_target_ts = 0; - context->v_new_ssrc = FALSE; + if(context->new_ssrc) { + JANUS_LOG(LOG_VERB, "video skew SSRC=%"SCNu32" resetting status\n", context->last_ssrc); + context->reference_time = now; + context->start_time = 0; + context->evaluating_start_time = 0; + context->start_ts = 0; + context->active_delay = 0; + context->prev_delay = 0; + context->seq_offset = 0; + context->ts_offset = 0; + context->target_ts = 0; + context->new_ssrc = FALSE; } /* N : a N sequence numbers jump has been performed */ @@ -513,82 +513,82 @@ int janus_rtp_skew_compensate_video(janus_rtp_header *header, janus_rtp_switchin int exit_status = 0; /* Do not execute skew analysis in the first seconds */ - if (now-context->v_reference_time < SKEW_DETECTION_WAIT_TIME_SECS/2 *G_USEC_PER_SEC) { + if(now-context->reference_time < SKEW_DETECTION_WAIT_TIME_SECS/2 *G_USEC_PER_SEC) { return 0; - } else if (!context->v_start_time) { - JANUS_LOG(LOG_VERB, "video skew SSRC=%"SCNu32" evaluation phase start\n", context->v_last_ssrc); - context->v_start_time = now; - context->v_evaluating_start_time = now; - context->v_start_ts = context->v_last_ts; + } else if(!context->start_time) { + JANUS_LOG(LOG_VERB, "video skew SSRC=%"SCNu32" evaluation phase start\n", context->last_ssrc); + context->start_time = now; + context->evaluating_start_time = now; + context->start_ts = context->last_ts; } /* Skew analysis */ /* Are we waiting for a target timestamp? (a negative skew has been evaluated in a previous iteration) */ - if (context->v_target_ts > 0 && (gint32)(context->v_target_ts - context->v_last_ts) > 0) { - context->v_seq_offset--; + if(context->target_ts > 0 && (gint32)(context->target_ts - context->last_ts) > 0) { + context->seq_offset--; exit_status = -1; } else { - context->v_target_ts = 0; + context->target_ts = 0; /* Do not execute analysis for out of order packets or multi-packets frame */ - if (context->v_last_seq == context->v_prev_seq + 1 && context->v_last_ts != context->v_prev_ts) { + if(context->last_seq == context->prev_seq + 1 && context->last_ts != context->prev_ts) { /* Set the sample rate */ guint32 vkhz = 90; /* 90khz */ /* Evaluate the local RTP timestamp according to the local clock */ - guint32 expected_ts = ((now - context->v_start_time)*vkhz)/1000 + context->v_start_ts; + guint32 expected_ts = ((now - context->start_time)*vkhz)/1000 + context->start_ts; /* Evaluate current delay */ - gint32 delay_now = context->v_last_ts - expected_ts; + gint32 delay_now = context->last_ts - expected_ts; /* Exponentially weighted moving average estimation */ - gint32 delay_estimate = (63*context->v_prev_delay + delay_now)/64; + gint32 delay_estimate = (63*context->prev_delay + delay_now)/64; /* Save previous delay for the next iteration*/ - context->v_prev_delay = delay_estimate; + context->prev_delay = delay_estimate; /* Evaluate the distance between active delay and current delay estimate */ - gint32 offset = context->v_active_delay - delay_estimate; - JANUS_LOG(LOG_HUGE, "video skew status SSRC=%"SCNu32" RECVD_TS=%"SCNu32" EXPTD_TS=%"SCNu32" OFFSET=%"SCNi32" TS_OFFSET=%"SCNi32" SEQ_OFFSET=%"SCNi16"\n", context->v_last_ssrc, context->v_last_ts, expected_ts, offset, context->v_ts_offset, context->v_seq_offset); + gint32 offset = context->active_delay - delay_estimate; + JANUS_LOG(LOG_HUGE, "video skew status SSRC=%"SCNu32" RECVD_TS=%"SCNu32" EXPTD_TS=%"SCNu32" OFFSET=%"SCNi32" TS_OFFSET=%"SCNi32" SEQ_OFFSET=%"SCNi16"\n", context->last_ssrc, context->last_ts, expected_ts, offset, context->ts_offset, context->seq_offset); gint32 skew_th = RTP_VIDEO_SKEW_TH_MS*vkhz; /* Evaluation phase */ - if (context->v_evaluating_start_time > 0) { + if(context->evaluating_start_time > 0) { /* Check if the offset has surpassed half the threshold during the evaluating phase */ - if (now-context->v_evaluating_start_time <= SKEW_DETECTION_WAIT_TIME_SECS/2 * G_USEC_PER_SEC) { - if (abs(offset) <= skew_th/2) { - JANUS_LOG(LOG_HUGE, "video skew SSRC=%"SCNu32" evaluation phase continue\n", context->v_last_ssrc); + if(now-context->evaluating_start_time <= SKEW_DETECTION_WAIT_TIME_SECS/2 * G_USEC_PER_SEC) { + if(abs(offset) <= skew_th/2) { + JANUS_LOG(LOG_HUGE, "video skew SSRC=%"SCNu32" evaluation phase continue\n", context->last_ssrc); } else { - JANUS_LOG(LOG_VERB, "video skew SSRC=%"SCNu32" evaluation phase reset\n", context->v_last_ssrc); - context->v_start_time = now; - context->v_evaluating_start_time = now; - context->v_start_ts = context->v_last_ts; + JANUS_LOG(LOG_VERB, "video skew SSRC=%"SCNu32" evaluation phase reset\n", context->last_ssrc); + context->start_time = now; + context->evaluating_start_time = now; + context->start_ts = context->last_ts; } } else { - JANUS_LOG(LOG_VERB, "video skew SSRC=%"SCNu32" evaluation phase stop\n", context->v_last_ssrc); - context->v_evaluating_start_time = 0; + JANUS_LOG(LOG_VERB, "video skew SSRC=%"SCNu32" evaluation phase stop\n", context->last_ssrc); + context->evaluating_start_time = 0; } return 0; } /* Check if the offset has surpassed the threshold */ - if (offset >= skew_th) { + if(offset >= skew_th) { /* The source is slowing down */ /* Update active delay */ - context->v_active_delay = delay_estimate; + context->active_delay = delay_estimate; /* Adjust ts offset */ - context->v_ts_offset += skew_th; + context->ts_offset += skew_th; /* Calculate last ts increase */ - guint32 ts_incr = context->v_last_ts-context->v_prev_ts; + guint32 ts_incr = context->last_ts-context->prev_ts; /* Evaluate sequence number jump */ guint16 jump = (skew_th+ts_incr-1)/ts_incr; /* Adjust seq num offset */ - context->v_seq_offset += jump; + context->seq_offset += jump; exit_status = jump; - } else if (offset <= -skew_th) { + } else if(offset <= -skew_th) { /* The source is speeding up*/ /* Update active delay */ - context->v_active_delay = delay_estimate; + context->active_delay = delay_estimate; /* Adjust ts offset */ - context->v_ts_offset -= skew_th; + context->ts_offset -= skew_th; /* Set target ts */ - context->v_target_ts = context->v_last_ts + skew_th; - if (context->v_target_ts == 0) - context->v_target_ts = 1; + context->target_ts = context->last_ts + skew_th; + if(context->target_ts == 0) + context->target_ts = 1; /* Adjust seq num offset */ - context->v_seq_offset--; + context->seq_offset--; exit_status = -1; } } @@ -596,10 +596,10 @@ int janus_rtp_skew_compensate_video(janus_rtp_header *header, janus_rtp_switchin /* Skew compensation */ /* Fix header timestamp considering the active offset */ - guint32 fixed_rtp_ts = context->v_last_ts + context->v_ts_offset; + guint32 fixed_rtp_ts = context->last_ts + context->ts_offset; header->timestamp = htonl(fixed_rtp_ts); /* Fix header sequence number considering the total offset */ - guint16 fixed_rtp_seq = context->v_last_seq + context->v_seq_offset; + guint16 fixed_rtp_seq = context->last_seq + context->seq_offset; header->seq_number = htons(fixed_rtp_seq); return exit_status; @@ -615,89 +615,49 @@ void janus_rtp_header_update(janus_rtp_header *header, janus_rtp_switching_conte uint32_t ssrc = ntohl(header->ssrc); uint32_t timestamp = ntohl(header->timestamp); uint16_t seq = ntohs(header->seq_number); - if(video) { - if(ssrc != context->v_last_ssrc) { - /* Video SSRC changed: update both sequence number and timestamp */ - JANUS_LOG(LOG_VERB, "Video SSRC changed, %"SCNu32" --> %"SCNu32"\n", - context->v_last_ssrc, ssrc); - context->v_last_ssrc = ssrc; - context->v_base_ts_prev = context->v_last_ts; - context->v_base_ts = timestamp; - context->v_base_seq_prev = context->v_last_seq; - context->v_base_seq = seq; - /* How much time since the last video RTP packet? We compute an offset accordingly */ - if(context->v_last_time > 0) { - gint64 time_diff = janus_get_monotonic_time() - context->v_last_time; - time_diff = (time_diff*90)/1000; /* We're assuming 90khz here */ - if(time_diff == 0) - time_diff = 1; - context->v_base_ts_prev += (guint32)time_diff; - context->v_last_ts += (guint32)time_diff; - JANUS_LOG(LOG_VERB, "Computed offset for video RTP timestamp: %"SCNu32"\n", (guint32)time_diff); - } - /* Reset skew compensation data */ - context->v_new_ssrc = TRUE; - } - if(context->v_seq_reset) { - /* Video sequence number was paused for a while: just update that */ - context->v_seq_reset = FALSE; - context->v_base_seq_prev = context->v_last_seq; - context->v_base_seq = seq; + if(ssrc != context->last_ssrc) { + /* Audio SSRC changed: update both sequence number and timestamp */ + JANUS_LOG(LOG_VERB, "SSRC changed, %"SCNu32" --> %"SCNu32"\n", + context->last_ssrc, ssrc); + context->last_ssrc = ssrc; + context->base_ts_prev = context->last_ts; + context->base_ts = timestamp; + context->base_seq_prev = context->last_seq; + context->base_seq = seq; + /* How much time since the last audio RTP packet? We compute an offset accordingly */ + if(context->last_time > 0) { + gint64 time_diff = janus_get_monotonic_time() - context->last_time; + /* We're assuming 90khz for video and 48khz for audio, here */ + int khz = video ? 90 : 48; + if(!video && (header->type == 0 || header->type == 8 || header->type == 9)) + khz = 8; /* We're assuming 48khz here (Opus), unless it's G.711/G.722 (8khz) */ + time_diff = (time_diff*khz)/1000; + if(time_diff == 0) + time_diff = 1; + context->base_ts_prev += (guint32)time_diff; + context->prev_ts += (guint32)time_diff; + context->last_ts += (guint32)time_diff; + JANUS_LOG(LOG_VERB, "Computed offset for RTP timestamp: %"SCNu32"\n", (guint32)time_diff); } - /* Compute a coherent timestamp and sequence number */ - context->v_prev_ts = context->v_last_ts; - context->v_last_ts = (timestamp-context->v_base_ts) + context->v_base_ts_prev; - context->v_prev_seq = context->v_last_seq; - context->v_last_seq = (seq-context->v_base_seq)+context->v_base_seq_prev+1; - /* Update the timestamp and sequence number in the RTP packet */ - header->timestamp = htonl(context->v_last_ts); - header->seq_number = htons(context->v_last_seq); - /* Take note of when we last handled this RTP packet */ - context->v_last_time = janus_get_monotonic_time(); - } else { - if(ssrc != context->a_last_ssrc) { - /* Audio SSRC changed: update both sequence number and timestamp */ - JANUS_LOG(LOG_VERB, "Audio SSRC changed, %"SCNu32" --> %"SCNu32"\n", - context->a_last_ssrc, ssrc); - context->a_last_ssrc = ssrc; - context->a_base_ts_prev = context->a_last_ts; - context->a_base_ts = timestamp; - context->a_base_seq_prev = context->a_last_seq; - context->a_base_seq = seq; - /* How much time since the last audio RTP packet? We compute an offset accordingly */ - if(context->a_last_time > 0) { - gint64 time_diff = janus_get_monotonic_time() - context->a_last_time; - int akhz = 48; - if(header->type == 0 || header->type == 8 || header->type == 9) - akhz = 8; /* We're assuming 48khz here (Opus), unless it's G.711/G.722 (8khz) */ - time_diff = (time_diff*akhz)/1000; - if(time_diff == 0) - time_diff = 1; - context->a_base_ts_prev += (guint32)time_diff; - context->a_prev_ts += (guint32)time_diff; - context->a_last_ts += (guint32)time_diff; - JANUS_LOG(LOG_VERB, "Computed offset for audio RTP timestamp: %"SCNu32"\n", (guint32)time_diff); - } - /* Reset skew compensation data */ - context->a_new_ssrc = TRUE; - } - if(context->a_seq_reset) { - /* Audio sequence number was paused for a while: just update that */ - context->a_seq_reset = FALSE; - context->a_base_seq_prev = context->a_last_seq; - context->a_base_seq = seq; - } - /* Compute a coherent timestamp and sequence number */ - context->a_prev_ts = context->a_last_ts; - context->a_last_ts = (timestamp-context->a_base_ts) + context->a_base_ts_prev; - context->a_prev_seq = context->a_last_seq; - context->a_last_seq = (seq-context->a_base_seq)+context->a_base_seq_prev+1; - /* Update the timestamp and sequence number in the RTP packet */ - header->timestamp = htonl(context->a_last_ts); - header->seq_number = htons(context->a_last_seq); - /* Take note of when we last handled this RTP packet */ - context->a_last_time = janus_get_monotonic_time(); + /* Reset skew compensation data */ + context->new_ssrc = TRUE; + } + if(context->seq_reset) { + /* Audio sequence number was paused for a while: just update that */ + context->seq_reset = FALSE; + context->base_seq_prev = context->last_seq; + context->base_seq = seq; } + /* Compute a coherent timestamp and sequence number */ + context->prev_ts = context->last_ts; + context->last_ts = (timestamp-context->base_ts) + context->base_ts_prev; + context->prev_seq = context->last_seq; + context->last_seq = (seq-context->base_seq)+context->base_seq_prev+1; + /* Update the timestamp and sequence number in the RTP packet */ + header->timestamp = htonl(context->last_ts); + header->seq_number = htons(context->last_seq); + /* Take note of when we last handled this RTP packet */ + context->last_time = janus_get_monotonic_time(); } @@ -1098,7 +1058,7 @@ gboolean janus_rtp_simulcasting_context_process_rtp(janus_rtp_simulcasting_conte tid, context->templayer); /* We increase the base sequence number, or there will be gaps when delivering later */ if(sc) - sc->v_base_seq++; + sc->base_seq++; return FALSE; } } @@ -1119,7 +1079,7 @@ gboolean janus_rtp_simulcasting_context_process_rtp(janus_rtp_simulcasting_conte tid, context->templayer); /* We increase the base sequence number, or there will be gaps when delivering later */ if(sc) - sc->v_base_seq++; + sc->base_seq++; return FALSE; } } diff --git a/rtp.h b/rtp.h index 92284e779d..71c8a59736 100644 --- a/rtp.h +++ b/rtp.h @@ -234,18 +234,12 @@ int janus_rtp_header_extension_replace_id(char *buf, int len, int id, int new_id /*! \brief RTP context, in order to make sure SSRC changes result in coherent seq/ts increases */ typedef struct janus_rtp_switching_context { - uint32_t a_last_ssrc, a_last_ts, a_base_ts, a_base_ts_prev, a_prev_ts, a_target_ts, a_start_ts, - v_last_ssrc, v_last_ts, v_base_ts, v_base_ts_prev, v_prev_ts, v_target_ts, v_start_ts; - uint16_t a_last_seq, a_prev_seq, a_base_seq, a_base_seq_prev, - v_last_seq, v_prev_seq, v_base_seq, v_base_seq_prev; - gboolean a_seq_reset, a_new_ssrc, - v_seq_reset, v_new_ssrc; - gint16 a_seq_offset, - v_seq_offset; - gint32 a_prev_delay, a_active_delay, a_ts_offset, - v_prev_delay, v_active_delay, v_ts_offset; - gint64 a_last_time, a_reference_time, a_start_time, a_evaluating_start_time, - v_last_time, v_reference_time, v_start_time, v_evaluating_start_time; + uint32_t last_ssrc, last_ts, base_ts, base_ts_prev, prev_ts, target_ts, start_ts; + uint16_t last_seq, prev_seq, base_seq, base_seq_prev; + gboolean seq_reset, new_ssrc; + gint16 seq_offset; + gint32 prev_delay, active_delay, ts_offset; + gint64 last_time, reference_time, start_time, evaluating_start_time; } janus_rtp_switching_context; /*! \brief Set (or reset) the context fields to their default values diff --git a/sdp-utils.c b/sdp-utils.c index 80d646778e..24c6bf5b53 100644 --- a/sdp-utils.c +++ b/sdp-utils.c @@ -19,7 +19,7 @@ #include "utils.h" #include "debug.h" -#define JANUS_BUFSIZE 8192 +#define JANUS_BUFSIZE 16384 /* Preferred codecs when negotiating audio/video, and number of supported codecs */ const char *janus_preferred_audio_codecs[] = { @@ -138,6 +138,19 @@ janus_sdp_mline *janus_sdp_mline_find(janus_sdp *sdp, janus_sdp_mtype type) { return NULL; } +janus_sdp_mline *janus_sdp_mline_find_by_index(janus_sdp *sdp, int index) { + if(sdp == NULL || index < 0) + return NULL; + GList *ml = sdp->m_lines; + while(ml) { + janus_sdp_mline *m = (janus_sdp_mline *)ml->data; + if(m->index == index) + return m; + ml = ml->next; + } + return NULL; +} + int janus_sdp_mline_remove(janus_sdp *sdp, janus_sdp_mtype type) { if(sdp == NULL) return -1; @@ -242,6 +255,46 @@ const char *janus_sdp_mdirection_str(janus_sdp_mdirection direction) { return NULL; } +const char *janus_sdp_oa_type_str(janus_sdp_oa_type type) { + switch(type) { + case JANUS_SDP_OA_MLINE: + return "JANUS_SDP_OA_MLINE"; + case JANUS_SDP_OA_ENABLED: + return "JANUS_SDP_OA_ENABLED"; + case JANUS_SDP_OA_MID: + return "JANUS_SDP_OA_MID"; + case JANUS_SDP_OA_DIRECTION: + return "JANUS_SDP_OA_DIRECTION"; + case JANUS_SDP_OA_CODEC: + return "JANUS_SDP_OA_CODEC"; + case JANUS_SDP_OA_EXTENSION: + return "JANUS_SDP_OA_EXTENSION"; + case JANUS_SDP_OA_EXTENSIONS: + return "JANUS_SDP_OA_EXTENSIONS"; + case JANUS_SDP_OA_ACCEPT_EXTMAP: + return "JANUS_SDP_OA_ACCEPT_EXTMAP"; + case JANUS_SDP_OA_PT: + return "JANUS_SDP_OA_PT"; + case JANUS_SDP_OA_FMTP: + return "JANUS_SDP_OA_FMTP"; + case JANUS_SDP_OA_AUDIO_DTMF: + return "JANUS_SDP_OA_AUDIO_DTMF"; + case JANUS_SDP_OA_VP9_PROFILE: + return "JANUS_SDP_OA_VP9_PROFILE"; + case JANUS_SDP_OA_H264_PROFILE: + return "JANUS_SDP_OA_H264_PROFILE"; + case JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS: + return "JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS"; + case JANUS_SDP_OA_DATA_LEGACY: + return "JANUS_SDP_OA_DATA_LEGACY"; + case JANUS_SDP_OA_DONE: + return "JANUS_SDP_OA_DONE"; + default: + break; + } + return NULL; +} + janus_sdp *janus_sdp_parse(const char *sdp, char *error, size_t errlen) { if(!sdp) return NULL; @@ -258,6 +311,7 @@ janus_sdp *janus_sdp_parse(const char *sdp, char *error, size_t errlen) { gboolean success = TRUE; janus_sdp_mline *mline = NULL; + int mlines = 0; gchar **parts = g_strsplit(sdp, "\n", -1); if(parts) { @@ -425,6 +479,8 @@ janus_sdp *janus_sdp_parse(const char *sdp, char *error, size_t errlen) { success = FALSE; break; } + m->index = mlines; + mlines++; m->type = janus_sdp_parse_mtype(type); m->type_str = g_strdup(type); m->proto = g_strdup(proto); @@ -603,12 +659,16 @@ janus_sdp *janus_sdp_parse(const char *sdp, char *error, size_t errlen) { return imported; } -int janus_sdp_remove_payload_type(janus_sdp *sdp, int pt) { +int janus_sdp_remove_payload_type(janus_sdp *sdp, int index, int pt) { if(!sdp || pt < 0) return -1; GList *ml = sdp->m_lines; while(ml) { janus_sdp_mline *m = (janus_sdp_mline *)ml->data; + if(index != -1 && index != m->index) { + ml = ml->next; + continue; + } /* Remove any reference from the m-line */ m->ptypes = g_list_remove(m->ptypes, GINT_TO_POINTER(pt)); /* Also remove all attributes that reference the same payload type */ @@ -623,16 +683,18 @@ int janus_sdp_remove_payload_type(janus_sdp *sdp, int pt) { } ma = ma->next; } + if(index != -1) + break; ml = ml->next; } return 0; } -int janus_sdp_get_codec_pt(janus_sdp *sdp, const char *codec) { - return janus_sdp_get_codec_pt_full(sdp, codec, NULL); +int janus_sdp_get_codec_pt(janus_sdp *sdp, int index, const char *codec) { + return janus_sdp_get_codec_pt_full(sdp, index, codec, NULL); } -int janus_sdp_get_codec_pt_full(janus_sdp *sdp, const char *codec, const char *profile) { +int janus_sdp_get_codec_pt_full(janus_sdp *sdp, int index, const char *codec, const char *profile) { if(sdp == NULL || codec == NULL) return -1; /* Check the format string (note that we only parse what browsers can negotiate) */ @@ -700,6 +762,10 @@ int janus_sdp_get_codec_pt_full(janus_sdp *sdp, const char *codec, const char *p ml = ml->next; continue; } + if(index != -1 && index != m->index) { + ml = ml->next; + continue; + } /* Look in all rtpmap attributes */ GList *ma = m->attributes; int pt = -1; @@ -766,12 +832,14 @@ int janus_sdp_get_codec_pt_full(janus_sdp *sdp, const char *codec, const char *p } ma = ma->next; } + if(index != -1) + break; ml = ml->next; } return -1; } -const char *janus_sdp_get_codec_name(janus_sdp *sdp, int pt) { +const char *janus_sdp_get_codec_name(janus_sdp *sdp, int index, int pt) { if(sdp == NULL || pt < 0) return NULL; if(pt == 0) @@ -783,6 +851,10 @@ const char *janus_sdp_get_codec_name(janus_sdp *sdp, int pt) { GList *ml = sdp->m_lines; while(ml) { janus_sdp_mline *m = (janus_sdp_mline *)ml->data; + if(index != -1 && index != m->index) { + ml = ml->next; + continue; + } /* Look in all rtpmap attributes */ GList *ma = m->attributes; while(ma) { @@ -823,6 +895,8 @@ const char *janus_sdp_get_codec_name(janus_sdp *sdp, int pt) { } ma = ma->next; } + if(index != -1) + break; ml = ml->next; } return NULL; @@ -862,12 +936,16 @@ const char *janus_sdp_get_codec_rtpmap(const char *codec) { return NULL; } -const char *janus_sdp_get_fmtp(janus_sdp *sdp, int pt) { +const char *janus_sdp_get_fmtp(janus_sdp *sdp, int index, int pt) { if(sdp == NULL || pt < 0) return NULL; GList *ml = sdp->m_lines; while(ml) { janus_sdp_mline *m = (janus_sdp_mline *)ml->data; + if(index != -1 && index != m->index) { + ml = ml->next; + continue; + } /* Look in all rtpmap attributes */ GList *ma = m->attributes; while(ma) { @@ -884,6 +962,8 @@ const char *janus_sdp_get_fmtp(janus_sdp *sdp, int pt) { } ma = ma->next; } + if(index != -1) + break; ml = ml->next; } return NULL; @@ -1002,80 +1082,62 @@ char *janus_sdp_write(janus_sdp *imported) { return sdp; } -void janus_sdp_find_preferred_codecs(janus_sdp *sdp, const char **acodec, const char **vcodec) { +void janus_sdp_find_preferred_codec(janus_sdp *sdp, janus_sdp_mtype type, int index, const char **codec) { if(sdp == NULL) return; janus_refcount_increase(&sdp->ref); - gboolean audio = FALSE, video = FALSE; + gboolean found = FALSE; GList *temp = sdp->m_lines; while(temp) { /* Which media are available? */ janus_sdp_mline *m = (janus_sdp_mline *)temp->data; - if(m->type == JANUS_SDP_AUDIO && m->port > 0 && m->direction != JANUS_SDP_INACTIVE) { - if(audio == FALSE) { - uint i=0; - for(i=0; i 0) { - audio = TRUE; - if(acodec) - *acodec = janus_preferred_audio_codecs[i]; - break; - } - } - } - } else if(m->type == JANUS_SDP_VIDEO && m->port > 0 && m->direction != JANUS_SDP_INACTIVE) { - if(video == FALSE) { - uint i=0; - for(i=0; i 0) { - video = TRUE; - if(vcodec) - *vcodec = janus_preferred_video_codecs[i]; - break; - } + if(index != -1 && index != m->index) { + temp = temp->next; + continue; + } + if(m->type == type && m->port > 0 && m->direction != JANUS_SDP_INACTIVE) { + uint i=0; + for(i=0; i<(type == JANUS_SDP_AUDIO ? janus_audio_codecs : janus_video_codecs); i++) { + if(janus_sdp_get_codec_pt(sdp, m->index, + type == JANUS_SDP_AUDIO ? janus_preferred_audio_codecs[i] : janus_preferred_video_codecs[i]) > 0) { + found = TRUE; + if(codec) + *codec = (type == JANUS_SDP_AUDIO ? janus_preferred_audio_codecs[i] : janus_preferred_video_codecs[i]); + break; } } } - if(audio && video) + if(found || index != -1) break; temp = temp->next; } janus_refcount_decrease(&sdp->ref); } -void janus_sdp_find_first_codecs(janus_sdp *sdp, const char **acodec, const char **vcodec) { +void janus_sdp_find_first_codec(janus_sdp *sdp, janus_sdp_mtype type, int index, const char **codec) { if(sdp == NULL) return; janus_refcount_increase(&sdp->ref); - gboolean audio = FALSE, video = FALSE; + gboolean found = FALSE; GList *temp = sdp->m_lines; while(temp) { /* Which media are available? */ janus_sdp_mline *m = (janus_sdp_mline *)temp->data; - if(m->type == JANUS_SDP_AUDIO && m->port > 0 && m->direction != JANUS_SDP_INACTIVE) { - if(audio == FALSE && m->ptypes) { - int pt = GPOINTER_TO_INT(m->ptypes->data); - const char *codec = janus_sdp_get_codec_name(sdp, pt); - codec = janus_sdp_match_preferred_codec(m->type, (char *)codec); - if(codec) { - audio = TRUE; - if(acodec) - *acodec = codec; - } - } - } else if(m->type == JANUS_SDP_VIDEO && m->port > 0 && m->direction != JANUS_SDP_INACTIVE) { - if(video == FALSE && m->ptypes) { - int pt = GPOINTER_TO_INT(m->ptypes->data); - const char *codec = janus_sdp_get_codec_name(sdp, pt); - codec = janus_sdp_match_preferred_codec(m->type, (char *)codec); - if(codec) { - video = TRUE; - if(vcodec) - *vcodec = codec; - } + if(index != -1 && index != m->index) { + temp = temp->next; + continue; + } + if(m->type == type && m->port > 0 && m->direction != JANUS_SDP_INACTIVE && m->ptypes) { + int pt = GPOINTER_TO_INT(m->ptypes->data); + const char *c = janus_sdp_get_codec_name(sdp, m->index, pt); + c = janus_sdp_match_preferred_codec(m->type, (char *)c); + if(c) { + found = TRUE; + if(codec) + *codec = c; } } - if(audio && video) + if(found || index != -1) break; temp = temp->next; } @@ -1125,316 +1187,474 @@ static int janus_sdp_id_compare(gconstpointer a, gconstpointer b) { } janus_sdp *janus_sdp_generate_offer(const char *name, const char *address, ...) { /* This method has a variable list of arguments, telling us what we should offer */ + int property = -1; va_list args; va_start(args, address); - /* Let's see what we should do with the media */ - gboolean do_audio = TRUE, do_video = TRUE, do_data = TRUE, - audio_dtmf = FALSE, video_rtcpfb = TRUE, data_legacy = TRUE; - const char *audio_codec = NULL, *video_codec = NULL, - *vp9_profile = NULL, *h264_profile = NULL, - *audio_fmtp = NULL, *video_fmtp = NULL; - int audio_pt = 111, video_pt = 96; - janus_sdp_mdirection audio_dir = JANUS_SDP_SENDRECV, video_dir = JANUS_SDP_SENDRECV; - GHashTable *audio_extmaps = NULL, *audio_extids = NULL, - *video_extmaps = NULL, *video_extids = NULL; - int property = va_arg(args, int); + + /* Create a new janus_sdp object */ + janus_sdp *offer = janus_sdp_new(name, address); + + gboolean new_mline = FALSE, mline_enabled = FALSE; + janus_sdp_mtype type = JANUS_SDP_OTHER; + gboolean audio_dtmf = FALSE, video_rtcpfb = TRUE, data_legacy = FALSE; + int pt = -1; + const char *codec = NULL, *mid = NULL, *fmtp = NULL, + *vp9_profile = NULL, *h264_profile = NULL; + janus_sdp_mdirection mdir = JANUS_SDP_DEFAULT; + GHashTable *extmaps = NULL, *extids = NULL, *m_extids = NULL; + while(property != JANUS_SDP_OA_DONE) { - if(property == JANUS_SDP_OA_AUDIO) { - do_audio = va_arg(args, gboolean); - } else if(property == JANUS_SDP_OA_VIDEO) { - do_video = va_arg(args, gboolean); - } else if(property == JANUS_SDP_OA_DATA) { - do_data = va_arg(args, gboolean); - } else if(property == JANUS_SDP_OA_AUDIO_DIRECTION) { - audio_dir = va_arg(args, janus_sdp_mdirection); - } else if(property == JANUS_SDP_OA_VIDEO_DIRECTION) { - video_dir = va_arg(args, janus_sdp_mdirection); - } else if(property == JANUS_SDP_OA_AUDIO_CODEC) { - audio_codec = va_arg(args, char *); - } else if(property == JANUS_SDP_OA_VIDEO_CODEC) { - video_codec = va_arg(args, char *); + property = va_arg(args, int); + if(!new_mline && property != JANUS_SDP_OA_MLINE && property != JANUS_SDP_OA_DONE) { + /* The first attribute MUST be JANUS_SDP_OA_MLINE or JANUS_SDP_OA_DONE */ + JANUS_LOG(LOG_ERR, "First attribute is not JANUS_SDP_OA_MLINE or JANUS_SDP_OA_DONE\n"); + janus_sdp_destroy(offer); + if(extmaps != NULL) + g_hash_table_destroy(extmaps); + if(extids != NULL) + g_hash_table_destroy(extids); + if(m_extids != NULL) + g_hash_table_destroy(m_extids); + va_end(args); + return NULL; + } + if(property == JANUS_SDP_OA_MLINE || property == JANUS_SDP_OA_DONE) { + /* A new m-line is starting or we're done, should we wrap the previous one? */ + new_mline = TRUE; + if(mline_enabled) { + /* Create a new m-line with the data collected so far */ + if(type == JANUS_SDP_AUDIO) { + if(janus_sdp_generate_offer_mline(offer, + JANUS_SDP_OA_MLINE, JANUS_SDP_AUDIO, + JANUS_SDP_OA_MID, mid, + JANUS_SDP_OA_PT, pt, + JANUS_SDP_OA_CODEC, codec, + JANUS_SDP_OA_DIRECTION, mdir, + JANUS_SDP_OA_FMTP, fmtp, + JANUS_SDP_OA_EXTENSIONS, m_extids, + JANUS_SDP_OA_AUDIO_DTMF, audio_dtmf, + JANUS_SDP_OA_DONE + ) < 0) { + janus_sdp_destroy(offer); + if(extmaps != NULL) + g_hash_table_destroy(extmaps); + if(extids != NULL) + g_hash_table_destroy(extids); + if(m_extids != NULL) + g_hash_table_destroy(m_extids); + va_end(args); + return NULL; + } + } else if(type == JANUS_SDP_VIDEO) { + if(janus_sdp_generate_offer_mline(offer, + JANUS_SDP_OA_MLINE, JANUS_SDP_VIDEO, + JANUS_SDP_OA_MID, mid, + JANUS_SDP_OA_PT, pt, + JANUS_SDP_OA_CODEC, codec, + JANUS_SDP_OA_DIRECTION, mdir, + JANUS_SDP_OA_FMTP, fmtp, + JANUS_SDP_OA_EXTENSIONS, m_extids, + JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS, video_rtcpfb, + JANUS_SDP_OA_VP9_PROFILE, vp9_profile, + JANUS_SDP_OA_H264_PROFILE, h264_profile, + JANUS_SDP_OA_DONE + ) < 0) { + janus_sdp_destroy(offer); + if(extmaps != NULL) + g_hash_table_destroy(extmaps); + if(extids != NULL) + g_hash_table_destroy(extids); + if(m_extids != NULL) + g_hash_table_destroy(m_extids); + va_end(args); + return NULL; + } + } else if(type == JANUS_SDP_APPLICATION) { + if(janus_sdp_generate_offer_mline(offer, + JANUS_SDP_OA_MLINE, JANUS_SDP_APPLICATION, + JANUS_SDP_OA_MID, mid, + JANUS_SDP_OA_DATA_LEGACY, data_legacy, + JANUS_SDP_OA_DONE + ) < 0) { + janus_sdp_destroy(offer); + if(extmaps != NULL) + g_hash_table_destroy(extmaps); + if(extids != NULL) + g_hash_table_destroy(extids); + if(m_extids != NULL) + g_hash_table_destroy(m_extids); + va_end(args); + return NULL; + } + } + } + if(property != JANUS_SDP_OA_MLINE) + continue; + /* Now reset the properties */ + audio_dtmf = FALSE; + video_rtcpfb = TRUE; + data_legacy = FALSE; + pt = -1; + mid = NULL; + codec = NULL; + fmtp = NULL; + vp9_profile = NULL; + h264_profile = NULL; + if(m_extids != NULL) + g_hash_table_destroy(m_extids); + m_extids = NULL; + mdir = JANUS_SDP_DEFAULT; + mline_enabled = TRUE; + /* The value of JANUS_SDP_OA_MLINE MUST be the media we want to add */ + type = va_arg(args, int); + if(type == JANUS_SDP_AUDIO) { + /* Audio, let's set some defaults */ + pt = 111; + codec = "opus"; + } else if(type == JANUS_SDP_VIDEO) { + /* Video, let's set some defaults */ + pt = 96; + codec = "vp8"; + } else if(type == JANUS_SDP_APPLICATION) { + /* Data */ + } else { + /* Unsupported m-line type */ + JANUS_LOG(LOG_ERR, "Invalid m-line type\n"); + janus_sdp_destroy(offer); + if(extmaps != NULL) + g_hash_table_destroy(extmaps); + if(extids != NULL) + g_hash_table_destroy(extids); + if(m_extids != NULL) + g_hash_table_destroy(m_extids); + va_end(args); + return NULL; + } + /* Let's assume the m-line is enabled, by default */ + mline_enabled = TRUE; + } else if(property == JANUS_SDP_OA_ENABLED) { + mline_enabled = va_arg(args, gboolean); + } else if(property == JANUS_SDP_OA_MID) { + mid = va_arg(args, char *); + } else if(property == JANUS_SDP_OA_DIRECTION) { + mdir = va_arg(args, janus_sdp_mdirection); + } else if(property == JANUS_SDP_OA_CODEC) { + codec = va_arg(args, char *); + } else if(property == JANUS_SDP_OA_PT) { + pt = va_arg(args, int); + } else if(property == JANUS_SDP_OA_FMTP) { + fmtp = va_arg(args, char *); } else if(property == JANUS_SDP_OA_VP9_PROFILE) { vp9_profile = va_arg(args, char *); } else if(property == JANUS_SDP_OA_H264_PROFILE) { h264_profile = va_arg(args, char *); - } else if(property == JANUS_SDP_OA_AUDIO_PT) { - audio_pt = va_arg(args, int); - } else if(property == JANUS_SDP_OA_VIDEO_PT) { - video_pt = va_arg(args, int); } else if(property == JANUS_SDP_OA_AUDIO_DTMF) { audio_dtmf = va_arg(args, gboolean); - } else if(property == JANUS_SDP_OA_AUDIO_FMTP) { - audio_fmtp = va_arg(args, char *); - } else if(property == JANUS_SDP_OA_VIDEO_FMTP) { - video_fmtp = va_arg(args, char *); } else if(property == JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS) { video_rtcpfb = va_arg(args, gboolean); } else if(property == JANUS_SDP_OA_DATA_LEGACY) { data_legacy = va_arg(args, gboolean); - } else if(property == JANUS_SDP_OA_AUDIO_EXTENSION || property == JANUS_SDP_OA_VIDEO_EXTENSION) { + } else if(property == JANUS_SDP_OA_EXTENSION) { char *extmap = va_arg(args, char *); int id = va_arg(args, int); if(extmap != NULL && id > 0 && id < 15) { - if(audio_extmaps == NULL) - audio_extmaps = g_hash_table_new(g_str_hash, g_str_equal); - if(audio_extids == NULL) - audio_extids = g_hash_table_new(NULL, NULL); - if(video_extmaps == NULL) - video_extmaps = g_hash_table_new(g_str_hash, g_str_equal); - if(video_extids == NULL) - video_extids = g_hash_table_new(NULL, NULL); + if(extmaps == NULL) + extmaps = g_hash_table_new(g_str_hash, g_str_equal); + if(extids == NULL) + extids = g_hash_table_new(NULL, NULL); /* Make sure the extmap and ID have not been added already */ - char *audio_extmap = g_hash_table_lookup(audio_extids, GINT_TO_POINTER(id)); - char *video_extmap = g_hash_table_lookup(video_extids, GINT_TO_POINTER(id)); - if((property == JANUS_SDP_OA_VIDEO_EXTENSION && audio_extmap != NULL && strcasecmp(audio_extmap, extmap)) || - (property == JANUS_SDP_OA_AUDIO_EXTENSION && video_extmap != NULL && strcasecmp(video_extmap, extmap))) { - JANUS_LOG(LOG_WARN, "Ignoring duplicate extension %d (already added: %s)\n", - id, audio_extmap ? audio_extmap : video_extmap); - } else { - if(property == JANUS_SDP_OA_AUDIO_EXTENSION) { - if(g_hash_table_lookup(audio_extmaps, extmap) != NULL) { - JANUS_LOG(LOG_WARN, "Ignoring duplicate audio extension %s (already added: %d)\n", - extmap, GPOINTER_TO_INT(g_hash_table_lookup(audio_extmaps, extmap))); - } else { - g_hash_table_insert(audio_extmaps, extmap, GINT_TO_POINTER(id)); - g_hash_table_insert(audio_extids, GINT_TO_POINTER(id), extmap); - } - } else { - if(g_hash_table_lookup(video_extmaps, extmap) != NULL) { - JANUS_LOG(LOG_WARN, "Ignoring duplicate video extension %s (already added: %d)\n", - extmap, GPOINTER_TO_INT(g_hash_table_lookup(video_extmaps, extmap))); - } else { - g_hash_table_insert(video_extmaps, extmap, GINT_TO_POINTER(id)); - g_hash_table_insert(video_extids, GINT_TO_POINTER(id), extmap); - } - } + if(g_hash_table_lookup(extids, GINT_TO_POINTER(id)) == NULL && + g_hash_table_lookup(extmaps, extmap) == NULL) { + g_hash_table_insert(extmaps, extmap, GINT_TO_POINTER(id)); + g_hash_table_insert(extids, GINT_TO_POINTER(id), extmap); + } + if(g_hash_table_lookup(extmaps, extmap) == GINT_TO_POINTER(id)) { + if(m_extids == NULL) + m_extids = g_hash_table_new(NULL, NULL); + g_hash_table_insert(m_extids, GINT_TO_POINTER(id), extmap); } } } else { - JANUS_LOG(LOG_WARN, "Unknown property %d for preparing SDP answer, ignoring...\n", property); + JANUS_LOG(LOG_WARN, "Unknown property %d for preparing SDP offer, ignoring...\n", property); } - property = va_arg(args, int); - } - if(audio_codec == NULL) - audio_codec = "opus"; - const char *audio_rtpmap = do_audio ? janus_sdp_get_codec_rtpmap(audio_codec) : NULL; - if(do_audio && audio_rtpmap == NULL) { - JANUS_LOG(LOG_ERR, "Unsupported audio codec '%s', can't prepare an offer\n", audio_codec); - va_end(args); - if(audio_extmaps != NULL) - g_hash_table_destroy(audio_extmaps); - if(audio_extids != NULL) - g_hash_table_destroy(audio_extids); - if(video_extmaps != NULL) - g_hash_table_destroy(video_extmaps); - if(video_extids != NULL) - g_hash_table_destroy(video_extids); - return NULL; } - if(video_codec == NULL) - video_codec = "vp8"; - const char *video_rtpmap = do_video ? janus_sdp_get_codec_rtpmap(video_codec) : NULL; - if(do_video && video_rtpmap == NULL) { - JANUS_LOG(LOG_ERR, "Unsupported video codec '%s', can't prepare an offer\n", video_codec); + if(extmaps != NULL) + g_hash_table_destroy(extmaps); + if(extids != NULL) + g_hash_table_destroy(extids); + + /* Done */ + va_end(args); + + return offer; +} + +int janus_sdp_generate_offer_mline(janus_sdp *offer, ...) { + if(offer == NULL) + return -1; + + /* This method has a variable list of arguments, telling us what we should offer */ + va_list args; + va_start(args, offer); + + /* First of all, let's see what we should add */ + janus_sdp_mtype type = JANUS_SDP_OTHER; + gboolean audio_dtmf = FALSE, video_rtcpfb = TRUE, data_legacy = FALSE; + int pt = -1; + const char *codec = NULL, *mid = NULL, *rtpmap = NULL, *fmtp = NULL, + *vp9_profile = NULL, *h264_profile = NULL; + janus_sdp_mdirection mdir = JANUS_SDP_DEFAULT; + GHashTable *extmaps = NULL, *extids = NULL; + gboolean extids_allocated = FALSE; + + int property = va_arg(args, int); + if(property != JANUS_SDP_OA_MLINE) { + /* The first attribute MUST be JANUS_SDP_OA_MLINE */ + JANUS_LOG(LOG_ERR, "First attribute is not JANUS_SDP_OA_MLINE\n"); va_end(args); - if(audio_extmaps != NULL) - g_hash_table_destroy(audio_extmaps); - if(audio_extids != NULL) - g_hash_table_destroy(audio_extids); - if(video_extmaps != NULL) - g_hash_table_destroy(video_extmaps); - if(video_extids != NULL) - g_hash_table_destroy(video_extids); - return NULL; + return -2; } + type = va_arg(args, int); + if(type == JANUS_SDP_AUDIO) { + /* Audio */ + pt = 111; + codec = "opus"; + } else if(type == JANUS_SDP_VIDEO) { + /* Video */ + pt = 96; + codec = "vp8"; + } else if(type == JANUS_SDP_APPLICATION) { + /* Data */ #ifndef HAVE_SCTP - do_data = FALSE; + va_end(args); + return -3; #endif + } else { + /* Unsupported m-line type */ + JANUS_LOG(LOG_ERR, "Invalid m-line type\n"); + janus_sdp_destroy(offer); + va_end(args); + return -4; + } - /* Create a new janus_sdp object */ - janus_sdp *offer = janus_sdp_new(name, address); - /* Now add all the media we should */ - if(do_audio) { - janus_sdp_mline *m = janus_sdp_mline_create(JANUS_SDP_AUDIO, 1, "UDP/TLS/RTP/SAVPF", audio_dir); - m->c_ipv4 = TRUE; - m->c_addr = g_strdup(offer->c_addr); - /* Add the selected audio codec */ - m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(audio_pt)); - janus_sdp_attribute *a = janus_sdp_attribute_create("rtpmap", "%d %s", audio_pt, audio_rtpmap); - m->attributes = g_list_append(m->attributes, a); - /* Check if we need to add a payload type for DTMF tones (telephone-event/8000) */ - if(audio_dtmf) { - /* We do */ - int dtmf_pt = 126; - m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(dtmf_pt)); - janus_sdp_attribute *a = janus_sdp_attribute_create("rtpmap", "%d %s", dtmf_pt, janus_sdp_get_codec_rtpmap("dtmf")); - m->attributes = g_list_append(m->attributes, a); + /* Let's see what we should do with the media to add */ + property = va_arg(args, int); + while(property != JANUS_SDP_OA_DONE) { + if(property == JANUS_SDP_OA_DIRECTION) { + mdir = va_arg(args, janus_sdp_mdirection); + } else if(property == JANUS_SDP_OA_CODEC) { + codec = va_arg(args, char *); + } else if(property == JANUS_SDP_OA_MID) { + mid = va_arg(args, char *); + } else if(property == JANUS_SDP_OA_PT) { + pt = va_arg(args, int); + } else if(property == JANUS_SDP_OA_FMTP) { + fmtp = va_arg(args, char *); + } else if(type == JANUS_SDP_AUDIO && property == JANUS_SDP_OA_AUDIO_DTMF) { + audio_dtmf = va_arg(args, gboolean); + } else if(type == JANUS_SDP_VIDEO && property == JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS) { + video_rtcpfb = va_arg(args, gboolean); + } else if(type == JANUS_SDP_VIDEO && property == JANUS_SDP_OA_VP9_PROFILE) { + vp9_profile = va_arg(args, char *); + } else if(type == JANUS_SDP_VIDEO && property == JANUS_SDP_OA_H264_PROFILE) { + h264_profile = va_arg(args, char *); + } else if(type == JANUS_SDP_APPLICATION && property == JANUS_SDP_OA_DATA_LEGACY) { + data_legacy = va_arg(args, gboolean); + } else if(property == JANUS_SDP_OA_EXTENSION) { + if((extmaps != NULL || extids != NULL) && !extids_allocated) { + JANUS_LOG(LOG_ERR, "Conflicting extensions settings (can't use both JANUS_SDP_OA_EXTENSION and JANUS_SDP_OA_EXTENSIONS)\n"); + if(extmaps != NULL) + g_hash_table_destroy(extmaps); + if(extids_allocated) { + if(extids != NULL) + g_hash_table_destroy(extids); + } + va_end(args); + return -5; + } + char *extmap = va_arg(args, char *); + int id = va_arg(args, int); + if(extmap != NULL && id > 0 && id < 15) { + if(extmaps == NULL) + extmaps = g_hash_table_new(g_str_hash, g_str_equal); + if(extids == NULL) + extids = g_hash_table_new(NULL, NULL); + extids_allocated = TRUE; + /* Make sure the extmap and ID have not been added already */ + char *check_extmap = g_hash_table_lookup(extids, GINT_TO_POINTER(id)); + if(check_extmap != NULL) { + JANUS_LOG(LOG_WARN, "Ignoring duplicate extension %d (already added: %s)\n", id, check_extmap); + } else { + if(g_hash_table_lookup(extmaps, extmap) != NULL) { + JANUS_LOG(LOG_WARN, "Ignoring duplicate extension %s (already added: %d)\n", + extmap, GPOINTER_TO_INT(g_hash_table_lookup(extmaps, extmap))); + } else { + g_hash_table_insert(extmaps, extmap, GINT_TO_POINTER(id)); + g_hash_table_insert(extids, GINT_TO_POINTER(id), extmap); + } + } + } + } else if(property == JANUS_SDP_OA_EXTENSIONS) { + if(extmaps != NULL || extids != NULL) { + JANUS_LOG(LOG_ERR, "Conflicting extensions settings (can't use both JANUS_SDP_OA_EXTENSION and JANUS_SDP_OA_EXTENSIONS)\n"); + if(extmaps != NULL) + g_hash_table_destroy(extmaps); + if(extids_allocated) { + if(extids != NULL) + g_hash_table_destroy(extids); + } + va_end(args); + return -5; + } + extids = va_arg(args, GHashTable *); + extids_allocated = FALSE; + } else { + JANUS_LOG(LOG_WARN, "Unknown property %d for preparing SDP answer, ignoring...\n", property); } - /* Check if there's a custom fmtp line to add for audio */ - if(audio_fmtp) { - janus_sdp_attribute *a = janus_sdp_attribute_create("fmtp", "%d %s", audio_pt, audio_fmtp); - m->attributes = g_list_append(m->attributes, a); + property = va_arg(args, int); + } + /* Configure some defaults, if values weren't specified */ + if(type == JANUS_SDP_AUDIO) { + if(codec == NULL) + codec = "opus"; + rtpmap = janus_sdp_get_codec_rtpmap(codec); + if(rtpmap == NULL) { + JANUS_LOG(LOG_ERR, "Unsupported audio codec '%s', can't prepare an offer\n", codec); + if(extmaps != NULL) + g_hash_table_destroy(extmaps); + if(extids_allocated) { + if(extids != NULL) + g_hash_table_destroy(extids); + } + va_end(args); + return -3; } - /* Check if we need to add audio extensions to the SDP */ - if(audio_extids != NULL) { - GList *ids = g_list_sort(g_hash_table_get_keys(audio_extids), janus_sdp_id_compare), *iter = ids; - while(iter) { - char *extmap = g_hash_table_lookup(audio_extids, iter->data); - if(extmap != NULL) { - janus_sdp_attribute *a = janus_sdp_attribute_create("extmap", - "%d %s\r\n", GPOINTER_TO_INT(iter->data), extmap); - janus_sdp_attribute_add_to_mline(m, a); - } - iter = iter->next; + } else if(type == JANUS_SDP_VIDEO) { + if(codec == NULL) + codec = "vp8"; + rtpmap = janus_sdp_get_codec_rtpmap(codec); + if(rtpmap == NULL) { + JANUS_LOG(LOG_ERR, "Unsupported video codec '%s', can't prepare an offer\n", codec); + if(extmaps != NULL) + g_hash_table_destroy(extmaps); + if(extids_allocated) { + if(extids != NULL) + g_hash_table_destroy(extids); } - g_list_free(ids); + va_end(args); + return -4; } - /* It is safe to add transport-wide rtcp feedback message here, won't be used unless the header extension is negotiated */ - a = janus_sdp_attribute_create("rtcp-fb", "%d transport-cc", audio_pt); + } + + /* Create the m-line */ + const char *transport = "UDP/TLS/RTP/SAVPF"; + if(type == JANUS_SDP_APPLICATION) + transport = (data_legacy ? "DTLS/SCTP" : "UDP/DTLS/SCTP"); + janus_sdp_mline *m = janus_sdp_mline_create(type, 9, transport, mdir); + m->index = g_list_length(offer->m_lines); + m->c_ipv4 = TRUE; + m->c_addr = g_strdup(offer->c_addr); + janus_sdp_attribute *a = NULL; + /* Any mid we should set? */ + if(mid != NULL) { + a = janus_sdp_attribute_create("mid", "%s", mid); m->attributes = g_list_append(m->attributes, a); - offer->m_lines = g_list_append(offer->m_lines, m); } - if(do_video) { - janus_sdp_mline *m = janus_sdp_mline_create(JANUS_SDP_VIDEO, 1, "UDP/TLS/RTP/SAVPF", video_dir); - m->c_ipv4 = TRUE; - m->c_addr = g_strdup(offer->c_addr); - /* Add the selected video codec */ - m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(video_pt)); - janus_sdp_attribute *a = janus_sdp_attribute_create("rtpmap", "%d %s", video_pt, video_rtpmap); + if(type == JANUS_SDP_AUDIO || type == JANUS_SDP_VIDEO) { + /* Add the selected codec */ + m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(pt)); + a = janus_sdp_attribute_create("rtpmap", "%d %s", pt, rtpmap); m->attributes = g_list_append(m->attributes, a); - if(video_rtcpfb) { + if(type == JANUS_SDP_AUDIO) { + /* Check if we need to add a payload type for DTMF tones (telephone-event/8000) */ + if(audio_dtmf) { + /* We do */ + int dtmf_pt = 126; + m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(dtmf_pt)); + a = janus_sdp_attribute_create("rtpmap", "%d %s", dtmf_pt, janus_sdp_get_codec_rtpmap("dtmf")); + m->attributes = g_list_append(m->attributes, a); + } + } + if(type == JANUS_SDP_VIDEO && video_rtcpfb) { /* Add rtcp-fb attributes */ - a = janus_sdp_attribute_create("rtcp-fb", "%d ccm fir", video_pt); + a = janus_sdp_attribute_create("rtcp-fb", "%d ccm fir", pt); m->attributes = g_list_append(m->attributes, a); - a = janus_sdp_attribute_create("rtcp-fb", "%d nack", video_pt); + a = janus_sdp_attribute_create("rtcp-fb", "%d nack", pt); m->attributes = g_list_append(m->attributes, a); - a = janus_sdp_attribute_create("rtcp-fb", "%d nack pli", video_pt); + a = janus_sdp_attribute_create("rtcp-fb", "%d nack pli", pt); m->attributes = g_list_append(m->attributes, a); - a = janus_sdp_attribute_create("rtcp-fb", "%d goog-remb", video_pt); + a = janus_sdp_attribute_create("rtcp-fb", "%d goog-remb", pt); m->attributes = g_list_append(m->attributes, a); } - /* Check if we need to add audio extensions to the SDP */ - if(video_extids != NULL) { - GList *ids = g_list_sort(g_hash_table_get_keys(video_extids), janus_sdp_id_compare), *iter = ids; + /* Check if we need to add extensions to the SDP */ + if(extids != NULL) { + GList *ids = g_list_sort(g_hash_table_get_keys(extids), janus_sdp_id_compare), *iter = ids; while(iter) { - char *extmap = g_hash_table_lookup(video_extids, iter->data); + char *extmap = g_hash_table_lookup(extids, iter->data); if(extmap != NULL) { - janus_sdp_attribute *a = janus_sdp_attribute_create("extmap", + a = janus_sdp_attribute_create("extmap", "%d %s\r\n", GPOINTER_TO_INT(iter->data), extmap); janus_sdp_attribute_add_to_mline(m, a); } iter = iter->next; } - g_list_free(ids); } - if(!strcasecmp(video_codec, "vp9") && vp9_profile) { - /* Add a profile-id fmtp attribute */ - a = janus_sdp_attribute_create("fmtp", "%d profile-id=%s", video_pt, vp9_profile); - m->attributes = g_list_append(m->attributes, a); - } else if(!strcasecmp(video_codec, "h264") && h264_profile) { - /* Add a profile-level-id fmtp attribute */ - a = janus_sdp_attribute_create("fmtp", "%d profile-level-id=%s;packetization-mode=1", video_pt, h264_profile); - m->attributes = g_list_append(m->attributes, a); - } else if(video_fmtp) { - /* There's a custom fmtp line to add for video */ - a = janus_sdp_attribute_create("fmtp", "%d %s", video_pt, video_fmtp); + /* Check if there's a custom fmtp line to add */ + if(type == JANUS_SDP_AUDIO && fmtp != NULL) { + a = janus_sdp_attribute_create("fmtp", "%d %s", pt, fmtp); m->attributes = g_list_append(m->attributes, a); + } else if(type == JANUS_SDP_VIDEO) { + /* For video we can configure an fmtp in different ways */ + if(!strcasecmp(codec, "vp9") && vp9_profile) { + /* Add a profile-id fmtp attribute */ + a = janus_sdp_attribute_create("fmtp", "%d profile-id=%s", pt, vp9_profile); + m->attributes = g_list_append(m->attributes, a); + } else if(!strcasecmp(codec, "h264") && h264_profile) { + /* Add a profile-level-id fmtp attribute */ + a = janus_sdp_attribute_create("fmtp", "%d profile-level-id=%s;packetization-mode=1", + pt, h264_profile); + m->attributes = g_list_append(m->attributes, a); + } else if(fmtp) { + /* There's a custom fmtp line to add for video */ + a = janus_sdp_attribute_create("fmtp", "%d %s", pt, fmtp); + m->attributes = g_list_append(m->attributes, a); + } } - offer->m_lines = g_list_append(offer->m_lines, m); - } - if(do_data) { - janus_sdp_mline *m = janus_sdp_mline_create(JANUS_SDP_APPLICATION, 1, - data_legacy ? "DTLS/SCTP" : "UDP/DTLS/SCTP", JANUS_SDP_DEFAULT); - m->c_ipv4 = TRUE; - m->c_addr = g_strdup(offer->c_addr); + } else { m->fmts = g_list_append(m->fmts, g_strdup(data_legacy ? "5000" : "webrtc-datachannel")); /* Add an sctpmap attribute */ if(data_legacy) { - janus_sdp_attribute *aa = janus_sdp_attribute_create("sctpmap", "5000 webrtc-datachannel 16"); - m->attributes = g_list_append(m->attributes, aa); + a = janus_sdp_attribute_create("sctpmap", "5000 webrtc-datachannel 16"); + m->attributes = g_list_append(m->attributes, a); } else { - janus_sdp_attribute *aa = janus_sdp_attribute_create("sctp-port", "5000"); - m->attributes = g_list_append(m->attributes, aa); + a = janus_sdp_attribute_create("sctp-port", "5000"); + m->attributes = g_list_append(m->attributes, a); } - offer->m_lines = g_list_append(offer->m_lines, m); } - if(audio_extmaps != NULL) - g_hash_table_destroy(audio_extmaps); - if(audio_extids != NULL) - g_hash_table_destroy(audio_extids); - if(video_extmaps != NULL) - g_hash_table_destroy(video_extmaps); - if(video_extids != NULL) - g_hash_table_destroy(video_extids); + offer->m_lines = g_list_append(offer->m_lines, m); + + if(extmaps != NULL) + g_hash_table_destroy(extmaps); + if(extids_allocated) { + if(extids != NULL) + g_hash_table_destroy(extids); + } /* Done */ va_end(args); - return offer; + return 0; } -janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...) { +janus_sdp *janus_sdp_generate_answer(janus_sdp *offer) { if(offer == NULL) return NULL; janus_refcount_increase(&offer->ref); - /* This method has a variable list of arguments, telling us how we should respond */ - va_list args; - va_start(args, offer); - /* Let's see what we should do with the media */ - gboolean do_audio = TRUE, do_video = TRUE, do_data = TRUE, - audio_dtmf = FALSE, video_rtcpfb = TRUE; - const char *audio_codec = NULL, *video_codec = NULL, - *vp9_profile = NULL, *h264_profile = NULL, - *audio_fmtp = NULL, *video_fmtp = NULL; - char *custom_audio_fmtp = NULL; - GList *extmaps = NULL; - janus_sdp_mdirection audio_dir = JANUS_SDP_SENDRECV, video_dir = JANUS_SDP_SENDRECV; - int property = va_arg(args, int); - while(property != JANUS_SDP_OA_DONE) { - if(property == JANUS_SDP_OA_AUDIO) { - do_audio = va_arg(args, gboolean); - } else if(property == JANUS_SDP_OA_VIDEO) { - do_video = va_arg(args, gboolean); - } else if(property == JANUS_SDP_OA_DATA) { - do_data = va_arg(args, gboolean); - } else if(property == JANUS_SDP_OA_AUDIO_DIRECTION) { - audio_dir = va_arg(args, janus_sdp_mdirection); - } else if(property == JANUS_SDP_OA_VIDEO_DIRECTION) { - video_dir = va_arg(args, janus_sdp_mdirection); - } else if(property == JANUS_SDP_OA_AUDIO_CODEC) { - audio_codec = va_arg(args, char *); - } else if(property == JANUS_SDP_OA_VIDEO_CODEC) { - video_codec = va_arg(args, char *); - } else if(property == JANUS_SDP_OA_VP9_PROFILE) { - vp9_profile = va_arg(args, char *); - } else if(property == JANUS_SDP_OA_H264_PROFILE) { - h264_profile = va_arg(args, char *); - } else if(property == JANUS_SDP_OA_AUDIO_DTMF) { - audio_dtmf = va_arg(args, gboolean); - } else if(property == JANUS_SDP_OA_AUDIO_FMTP) { - audio_fmtp = va_arg(args, char *); - } else if(property == JANUS_SDP_OA_VIDEO_FMTP) { - video_fmtp = va_arg(args, char *); - } else if(property == JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS) { - video_rtcpfb = va_arg(args, gboolean); - } else if(property == JANUS_SDP_OA_ACCEPT_EXTMAP) { - const char *extension = va_arg(args, char *); - if(extension != NULL) - extmaps = g_list_append(extmaps, (char *)extension); - } else { - JANUS_LOG(LOG_WARN, "Unknown property %d for preparing SDP answer, ignoring...\n", property); - } - property = va_arg(args, int); - } -#ifndef HAVE_SCTP - do_data = FALSE; -#endif - + /* Create an SDP answer, and start by copying some of the headers */ janus_sdp *answer = g_malloc(sizeof(janus_sdp)); g_atomic_int_set(&answer->destroyed, 0); janus_refcount_init(&answer->ref, janus_sdp_free); - /* Start by copying some of the headers */ answer->version = offer->version; answer->o_name = g_strdup(offer->o_name ? offer->o_name : "-"); answer->o_sessid = offer->o_sessid; @@ -1449,91 +1669,145 @@ janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...) { answer->attributes = NULL; answer->m_lines = NULL; - /* Now iterate on all media, and let's see what we should do */ - int audio = 0, video = 0, data = 0; + /* Iterate on all m-lines to add, if any */ GList *temp = offer->m_lines; while(temp) { janus_sdp_mline *m = (janus_sdp_mline *)temp->data; /* For each m-line we parse, we'll need a corresponding one in the answer */ janus_sdp_mline *am = g_malloc0(sizeof(janus_sdp_mline)); - g_atomic_int_set(&am->destroyed, 0); janus_refcount_init(&am->ref, janus_sdp_mline_free); + am->index = m->index; am->type = m->type; am->type_str = m->type_str ? g_strdup(m->type_str) : NULL; am->proto = g_strdup(m->proto ? m->proto : "UDP/TLS/RTP/SAVPF"); - am->port = m->port; am->c_ipv4 = m->c_ipv4; am->c_addr = g_strdup(am->c_addr ? am->c_addr : "127.0.0.1"); - am->direction = JANUS_SDP_INACTIVE; /* We'll change this later */ + /* We reject the media line by default, but this can be changed later */ + am->port = 0; + am->direction = JANUS_SDP_INACTIVE; + am->ptypes = g_list_append(am->ptypes, GINT_TO_POINTER(0)); /* Append to the list of m-lines in the answer */ answer->m_lines = g_list_append(answer->m_lines, am); - /* Let's see what this is */ - if(m->type == JANUS_SDP_AUDIO) { - if(m->port > 0) { - audio++; - } - if(!do_audio || audio > 1) { - /* Reject */ - if(audio > 1) - am->port = 0; - temp = temp->next; - continue; - } - } else if(m->type == JANUS_SDP_VIDEO && m->port > 0) { - if(m->port > 0) { - video++; - } - if(!do_video || video > 1) { - /* Reject */ - if(video > 1) - am->port = 0; - temp = temp->next; - continue; - } - } else if(m->type == JANUS_SDP_APPLICATION && m->port > 0) { - if(m->port > 0) { - data++; - } - if(!do_data || data > 1) { - /* Reject */ - am->port = 0; - /* Add the format anyway, to keep Firefox happy */ - GList *fmt = m->fmts; - if(fmt) { - char *fmt_str = (char *)fmt->data; - if(fmt_str) - am->fmts = g_list_append(am->fmts, g_strdup(fmt_str)); - } - temp = temp->next; - continue; - } + temp = temp->next; + } + janus_refcount_decrease(&offer->ref); + + /* Done*/ + return answer; +} + +int janus_sdp_generate_answer_mline(janus_sdp *offer, janus_sdp *answer, janus_sdp_mline *offered, ...) { + if(answer == NULL || offered == NULL) + return -1; + + janus_refcount_increase(&offer->ref); + janus_refcount_increase(&answer->ref); + /* This method has a variable list of arguments, telling us how we should respond */ + va_list args; + va_start(args, offered); + + /* Let's see what we should do with the media */ + gboolean mline_enabled = TRUE; + janus_sdp_mtype type = JANUS_SDP_OTHER; + gboolean audio_dtmf = FALSE, video_rtcpfb = TRUE; + const char *codec = NULL, *fmtp = NULL, *vp9_profile = NULL, *h264_profile = NULL; + char *custom_audio_fmtp = NULL; + GList *extmaps = NULL; + janus_sdp_mdirection mdir = JANUS_SDP_DEFAULT; + int property = va_arg(args, int); + if(property != JANUS_SDP_OA_MLINE) { + /* The first attribute MUST be JANUS_SDP_OA_MLINE */ + JANUS_LOG(LOG_ERR, "First attribute is not JANUS_SDP_OA_MLINE\n"); + va_end(args); + janus_refcount_decrease(&offer->ref); + janus_refcount_decrease(&answer->ref); + return -2; + } + type = va_arg(args, int); + if(type != JANUS_SDP_AUDIO && type != JANUS_SDP_VIDEO && type != JANUS_SDP_APPLICATION) { + /* Unsupported m-line type */ + JANUS_LOG(LOG_ERR, "Invalid m-line type\n"); + va_end(args); + janus_refcount_decrease(&offer->ref); + janus_refcount_decrease(&answer->ref); + return -3; + } + + /* Let's see what we should do with the media to add */ + property = va_arg(args, int); + while(property != JANUS_SDP_OA_DONE) { + if(property == JANUS_SDP_OA_ENABLED) { + mline_enabled = va_arg(args, gboolean); + } else if(property == JANUS_SDP_OA_DIRECTION) { + mdir = va_arg(args, janus_sdp_mdirection); + } else if(property == JANUS_SDP_OA_CODEC) { + codec = va_arg(args, char *); + } else if(property == JANUS_SDP_OA_FMTP) { + fmtp = va_arg(args, char *); + } else if(property == JANUS_SDP_OA_VP9_PROFILE) { + vp9_profile = va_arg(args, char *); + } else if(property == JANUS_SDP_OA_H264_PROFILE) { + h264_profile = va_arg(args, char *); + } else if(property == JANUS_SDP_OA_AUDIO_DTMF) { + audio_dtmf = va_arg(args, gboolean); + } else if(property == JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS) { + video_rtcpfb = va_arg(args, gboolean); + } else if(property == JANUS_SDP_OA_ACCEPT_EXTMAP) { + const char *extension = va_arg(args, char *); + if(extension != NULL) + extmaps = g_list_append(extmaps, (char *)extension); + } else { + JANUS_LOG(LOG_WARN, "Unknown property %d for preparing SDP answer, ignoring...\n", property); + } + property = va_arg(args, int); + } + + /* Iterate on all m-lines to add, if any, to find the one with the same index */ + GList *temp = answer->m_lines; + while(temp) { + janus_sdp_mline *am = (janus_sdp_mline *)temp->data; + if(am->index != offered->index) { + temp = temp->next; + continue; } - if(m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) { - janus_sdp_mdirection target_dir = m->type == JANUS_SDP_AUDIO ? audio_dir : video_dir; + /* When answering, m-lines are disabled by default */ + am->direction = JANUS_SDP_INACTIVE; + g_list_free(am->ptypes); + am->ptypes = NULL; + g_list_free_full(am->fmts, (GDestroyNotify)g_free); + am->fmts = NULL; + g_list_free_full(am->attributes, (GDestroyNotify)janus_sdp_attribute_destroy); + am->attributes = NULL; + if(!mline_enabled) { + am->ptypes = g_list_append(am->ptypes, GINT_TO_POINTER(0)); + break; + } + am->port = 9; + if(am->type == JANUS_SDP_AUDIO || am->type == JANUS_SDP_VIDEO) { /* What is the direction we were offered? And how were we asked to react? * Adapt the direction in our answer accordingly */ - switch(m->direction) { + switch(offered->direction) { case JANUS_SDP_RECVONLY: - if(target_dir == JANUS_SDP_SENDRECV || target_dir == JANUS_SDP_SENDONLY) { + if(mdir == JANUS_SDP_SENDRECV || mdir == JANUS_SDP_DEFAULT || mdir == JANUS_SDP_SENDONLY) { /* Peer is recvonly, we'll only send */ am->direction = JANUS_SDP_SENDONLY; } else { /* Peer is recvonly, but we're not ok to send, so reply with inactive */ JANUS_LOG(LOG_WARN, "%s offered as '%s', but we need '%s' for us: using 'inactive'\n", - m->type == JANUS_SDP_AUDIO ? "Audio" : "Video", - janus_sdp_mdirection_str(m->direction), janus_sdp_mdirection_str(target_dir)); + am->type == JANUS_SDP_AUDIO ? "Audio" : "Video", + janus_sdp_mdirection_str(offered->direction), janus_sdp_mdirection_str(mdir)); am->direction = JANUS_SDP_INACTIVE; } break; case JANUS_SDP_SENDONLY: - if(target_dir == JANUS_SDP_SENDRECV || target_dir == JANUS_SDP_RECVONLY) { + if(mdir == JANUS_SDP_SENDRECV || mdir == JANUS_SDP_DEFAULT || mdir == JANUS_SDP_RECVONLY) { /* Peer is sendonly, we'll only receive */ am->direction = JANUS_SDP_RECVONLY; } else { /* Peer is sendonly, but we're not ok to receive, so reply with inactive */ JANUS_LOG(LOG_WARN, "%s offered as '%s', but we need '%s' for us: using 'inactive'\n", - m->type == JANUS_SDP_AUDIO ? "Audio" : "Video", - janus_sdp_mdirection_str(m->direction), janus_sdp_mdirection_str(target_dir)); + am->type == JANUS_SDP_AUDIO ? "Audio" : "Video", + janus_sdp_mdirection_str(offered->direction), janus_sdp_mdirection_str(mdir)); am->direction = JANUS_SDP_INACTIVE; } break; @@ -1544,11 +1818,10 @@ janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...) { case JANUS_SDP_SENDRECV: default: /* The peer is fine with everything, so use our constraint */ - am->direction = target_dir; + am->direction = mdir; break; } /* Look for the right codec and stick to that */ - const char *codec = m->type == JANUS_SDP_AUDIO ? audio_codec : video_codec; if(codec == NULL) { /* FIXME User didn't provide a codec to accept? Let's see if Opus (for audio) * of VP8 (for video) were negotiated: if so, use them, otherwise let's @@ -1556,25 +1829,25 @@ janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...) { * Notice that if it's not a codec we understand, we reject the medium, * as browsers would reject it anyway. If you need more flexibility you'll * have to generate an answer yourself, rather than automatically... */ - codec = m->type == JANUS_SDP_AUDIO ? "opus" : "vp8"; - if(janus_sdp_get_codec_pt(offer, codec) < 0) { + codec = am->type == JANUS_SDP_AUDIO ? "opus" : "vp8"; + if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { /* We couldn't find our preferred codec, let's try something else */ - if(m->type == JANUS_SDP_AUDIO) { + if(am->type == JANUS_SDP_AUDIO) { /* Opus not found, maybe mu-law? */ codec = "pcmu"; - if(janus_sdp_get_codec_pt(offer, codec) < 0) { + if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { /* mu-law not found, maybe a-law? */ codec = "pcma"; - if(janus_sdp_get_codec_pt(offer, codec) < 0) { + if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { /* a-law not found, maybe G.722? */ codec = "g722"; - if(janus_sdp_get_codec_pt(offer, codec) < 0) { + if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { /* G.722 not found, maybe isac32? */ codec = "isac32"; - if(janus_sdp_get_codec_pt(offer, codec) < 0) { + if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { /* isac32 not found, maybe isac16? */ codec = "isac16"; - if(janus_sdp_get_codec_pt(offer, codec) < 0) { + if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { /* isac16 not found, maybe multiopus? */ codec = "multiopus"; } @@ -1585,13 +1858,13 @@ janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...) { } else { /* VP8 not found, maybe VP9? */ codec = "vp9"; - if(janus_sdp_get_codec_pt_full(offer, codec, vp9_profile) < 0) { + if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { /* VP9 not found either, maybe H.264? */ codec = "h264"; - if(janus_sdp_get_codec_pt(offer, codec) < 0) { + if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { /* H.264 not found either, maybe AV1? */ codec = "av1"; - if(janus_sdp_get_codec_pt(offer, codec) < 0) { + if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { /* AV1 not found either, maybe H.265? */ codec = "h265"; } @@ -1605,20 +1878,20 @@ janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...) { video_profile = vp9_profile; else if(codec && !strcasecmp(codec, "h264")) video_profile = h264_profile; - int pt = janus_sdp_get_codec_pt_full(offer, codec, video_profile); + int pt = janus_sdp_get_codec_pt_full(offer, offered->index, codec, video_profile); if(pt < 0) { /* Reject */ JANUS_LOG(LOG_WARN, "Couldn't find codec we needed (%s) in the offer, rejecting %s\n", - codec, m->type == JANUS_SDP_AUDIO ? "audio" : "video"); + codec, am->type == JANUS_SDP_AUDIO ? "audio" : "video"); am->port = 0; am->direction = JANUS_SDP_INACTIVE; - temp = temp->next; - continue; + am->ptypes = g_list_append(am->ptypes, GINT_TO_POINTER(0)); + break; } - if(!strcasecmp(codec, "multiopus") && (audio_fmtp == NULL || - strstr(audio_fmtp, "channel_mapping") == NULL)) { + if(am->type == JANUS_SDP_AUDIO && !strcasecmp(codec, "multiopus") && + (fmtp == NULL || strstr(fmtp, "channel_mapping") == NULL)) { /* Missing channel mapping for the multiopus m-line, check the offer */ - GList *mo = m->attributes; + GList *mo = offered->attributes; while(mo) { janus_sdp_attribute *a = (janus_sdp_attribute *)mo->data; if(a->name && strstr(a->name, "fmtp") && a->value) { @@ -1635,7 +1908,7 @@ janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...) { } am->ptypes = g_list_append(am->ptypes, GINT_TO_POINTER(pt)); /* Add the related attributes */ - if(m->type == JANUS_SDP_AUDIO) { + if(am->type == JANUS_SDP_AUDIO) { /* Add rtpmap attribute */ const char *codec_rtpmap = janus_sdp_get_codec_rtpmap(codec); if(codec_rtpmap) { @@ -1643,7 +1916,7 @@ janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...) { am->attributes = g_list_append(am->attributes, a); /* Check if we need to add a payload type for DTMF tones (telephone-event/8000) */ if(audio_dtmf) { - int dtmf_pt = janus_sdp_get_codec_pt(offer, "dtmf"); + int dtmf_pt = janus_sdp_get_codec_pt(offer, am->index, "dtmf"); if(dtmf_pt >= 0) { /* We do */ am->ptypes = g_list_append(am->ptypes, GINT_TO_POINTER(dtmf_pt)); @@ -1653,9 +1926,9 @@ janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...) { } /* Check if there's a custom fmtp line to add for audio * FIXME We should actually check if it matches the offer */ - if(audio_fmtp || custom_audio_fmtp) { + if(fmtp || custom_audio_fmtp) { a = janus_sdp_attribute_create("fmtp", "%d %s", - pt, custom_audio_fmtp ? custom_audio_fmtp : audio_fmtp); + pt, custom_audio_fmtp ? custom_audio_fmtp : fmtp); am->attributes = g_list_append(am->attributes, a); } } @@ -1689,23 +1962,23 @@ janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...) { /* Add a profile-level-id fmtp attribute */ a = janus_sdp_attribute_create("fmtp", "%d profile-level-id=%s;packetization-mode=1", pt, h264_profile); am->attributes = g_list_append(am->attributes, a); - } else if(video_fmtp) { + } else if(fmtp) { /* There's a custom fmtp line to add for video * FIXME We should actually check if it matches the offer */ - a = janus_sdp_attribute_create("fmtp", "%d %s", pt, video_fmtp); + a = janus_sdp_attribute_create("fmtp", "%d %s", pt, fmtp); am->attributes = g_list_append(am->attributes, a); } } /* Add the extmap attributes, if needed */ if(extmaps != NULL) { - GList *ma = m->attributes; + GList *ma = offered->attributes; while(ma) { /* Iterate on all attributes, to see if there's an extension to accept */ janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data; if(a->name && strstr(a->name, "extmap") && a->value) { - GList *temp = extmaps; - while(temp != NULL) { - char *extension = (char *)temp->data; + GList *emtemp = extmaps; + while(emtemp != NULL) { + char *extension = (char *)emtemp->data; if(strstr(a->value, extension)) { /* Accept the extension */ int id = atoi(a->value); @@ -1731,13 +2004,14 @@ janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...) { "%d%s %s", id, direction, extension); janus_sdp_attribute_add_to_mline(am, a); } - temp = temp->next; + emtemp = emtemp->next; } - } else if(m->type == JANUS_SDP_VIDEO && a->name && strstr(a->name, "fmtp") && a->value && atoi(a->value) == pt) { + } else if(am->type == JANUS_SDP_VIDEO && a->name && strstr(a->name, "fmtp") && + a->value && atoi(a->value) == pt) { /* Check if we need to copy the fmtp attribute too */ - if(((!strcasecmp(codec, "vp8") && video_fmtp == NULL)) || - ((!strcasecmp(codec, "vp9") && vp9_profile == NULL && video_fmtp == NULL)) || - ((!strcasecmp(codec, "h264") && h264_profile == NULL && video_fmtp == NULL))) { + if(((!strcasecmp(codec, "vp8") && fmtp == NULL)) || + ((!strcasecmp(codec, "vp9") && vp9_profile == NULL && fmtp == NULL)) || + ((!strcasecmp(codec, "h264") && h264_profile == NULL && fmtp == NULL))) { /* FIXME Copy the fmtp attribute (we should check if we support it) */ a = janus_sdp_attribute_create("fmtp", "%s", a->value); janus_sdp_attribute_add_to_mline(am, a); @@ -1749,7 +2023,7 @@ janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...) { } else { /* This is for data, add formats and an sctpmap attribute */ am->direction = JANUS_SDP_DEFAULT; - GList *fmt = m->fmts; + GList *fmt = offered->fmts; while(fmt) { char *fmt_str = (char *)fmt->data; if(fmt_str) @@ -1757,14 +2031,16 @@ janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...) { fmt = fmt->next; } } - temp = temp->next; + /* Nothing else we need to do */ + break; } janus_refcount_decrease(&offer->ref); + janus_refcount_decrease(&answer->ref); /* Done */ g_list_free(extmaps); g_free(custom_audio_fmtp); va_end(args); - return answer; + return 0; } diff --git a/sdp-utils.h b/sdp-utils.h index 909fc38fb1..4e7195f830 100644 --- a/sdp-utils.h +++ b/sdp-utils.h @@ -99,24 +99,26 @@ janus_sdp_mdirection janus_sdp_parse_mdirection(const char *direction); * @returns The direction as a string, if valid, or NULL otherwise */ const char *janus_sdp_mdirection_str(janus_sdp_mdirection direction); -/*! \brief Helper method to return the preferred audio and video codecs in an SDP offer or answer, +/*! \brief Helper method to return the preferred audio or video codec in an SDP offer or answer, * (where by preferred we mean the codecs we prefer ourselves, and not the m-line SDP order) * as long as the m-line direction is not disabled (port=0 or direction=inactive) in the SDP - * \note The acodec and vcodec arguments are input/output, and they'll be set to a static value - * in janus_preferred_audio_codecs and janus_preferred_video_codecs, so don't free them. + * \note The codec argument is input/output, and it will be set to a static value + * in janus_preferred_audio_codecs or janus_preferred_video_codecs, so don't free it. * @param[in] sdp The Janus SDP object to parse - * @param[out] acodec The audio codec that was found - * @param[out] vcodec The video codec that was found */ -void janus_sdp_find_preferred_codecs(janus_sdp *sdp, const char **acodec, const char **vcodec); -/*! \brief Helper method to return the first audio and video codecs in an SDP offer or answer, + * @param[in] type Whether we're looking at an audio or video codec + * @param[in] index The m-line to refer to (use -1 for the first m-line that matches) + * @param[out] codec The audio or video codec that was found */ +void janus_sdp_find_preferred_codec(janus_sdp *sdp, janus_sdp_mtype type, int index, const char **codec); +/*! \brief Helper method to return the first audio or video codec in an SDP offer or answer, * (no matter whether we personally prefer them ourselves or not) * as long as the m-line direction is not disabled (port=0 or direction=inactive) in the SDP - * \note The acodec and vcodec arguments are input/output, and they'll be set to a static value - * in janus_preferred_audio_codecs and janus_preferred_video_codecs, so don't free them. + * \note The codec argument is input/output, and it will be set to a static value + * in janus_preferred_audio_codecs or janus_preferred_video_codecs, so don't free it. * @param[in] sdp The Janus SDP object to parse - * @param[out] acodec The audio codec that was found - * @param[out] vcodec The video codec that was found */ -void janus_sdp_find_first_codecs(janus_sdp *sdp, const char **acodec, const char **vcodec); + * @param[in] type Whether we're looking at an audio or video codec + * @param[in] index The m-line to refer to (use -1 for the first m-line that matches) + * @param[out] codec The audio or video codec that was found */ +void janus_sdp_find_first_codec(janus_sdp *sdp, janus_sdp_mtype type, int index, const char **codec); /*! \brief Helper method to match a codec to one of the preferred codecs * \note Don't free the returned value, as it's a constant value * @param[in] type The type of media to match @@ -126,6 +128,8 @@ const char *janus_sdp_match_preferred_codec(janus_sdp_mtype type, char *codec); /*! \brief SDP m-line representation */ typedef struct janus_sdp_mline { + /*! \brief Media index in the SDP */ + int index; /*! \brief Media type as a janus_sdp_mtype enumerator */ janus_sdp_mtype type; /*! \brief Media type (string) */ @@ -170,17 +174,20 @@ janus_sdp_mline *janus_sdp_mline_create(janus_sdp_mtype type, guint16 port, cons * @param[in] mline The janus_sdp_mline instance to free */ void janus_sdp_mline_destroy(janus_sdp_mline *mline); /*! \brief Helper method to get the janus_sdp_mline associated to a media type - * @note This currently returns the first m-line of the specified type it finds: in - * general, it shouldn't be an issue as we currently only support a single stream - * of the same type per session anyway... this will need to be fixed in the future. + * @note This currently returns the first m-line of the specified type it finds: as + * such, it's mostly here for making things easier for plugins not doing multistream. * @param[in] sdp The Janus SDP object to search * @param[in] type The type of media to search * @returns The janus_sdp_mline instance, if found, or NULL otherwise */ janus_sdp_mline *janus_sdp_mline_find(janus_sdp *sdp, janus_sdp_mtype type); +/*! \brief Helper method to get the janus_sdp_mline by its index + * @param[in] sdp The Janus SDP object to search + * @param[in] index The index of the m-line in the SDP + * @returns The janus_sdp_mline instance, if found, or NULL otherwise */ +janus_sdp_mline *janus_sdp_mline_find_by_index(janus_sdp *sdp, int index); /*! \brief Helper method to remove the janus_sdp_mline associated to a media type from the SDP - * @note This currently removes the first m-line of the specified type it finds: in - * general, it shouldn't be an issue as we currently only support a single stream - * of the same type per session anyway... this will need to be fixed in the future. + * @note This currently removes the first m-line of the specified type it finds: as + * such, it's mostly here for making things easier for plugins not doing multistream. * @param[in] sdp The Janus SDP object to modify * @param[in] type The type of media to remove * @returns 0 if successful, a negative integer otherwise */ @@ -224,9 +231,10 @@ janus_sdp *janus_sdp_parse(const char *sdp, char *error, size_t errlen); /*! \brief Helper method to quickly remove all traces (m-line, rtpmap, fmtp, etc.) of a payload type * @param[in] sdp The janus_sdp object to remove the payload type from + * @param[in] index The m-line to remove the payload type from (use -1 for the first m-line that matches) * @param[in] pt The payload type to remove * @returns 0 in case of success, a negative integer otherwise */ -int janus_sdp_remove_payload_type(janus_sdp *sdp, int pt); +int janus_sdp_remove_payload_type(janus_sdp *sdp, int index, int pt); /*! \brief Method to serialize a janus_sdp object to an SDP string * @param[in] sdp The janus_sdp object to serialize @@ -246,82 +254,95 @@ janus_sdp *janus_sdp_new(const char *name, const char *address); void janus_sdp_destroy(janus_sdp *sdp); typedef enum janus_sdp_oa_type { -/*! \brief When generating an offer or answer automatically, accept/reject audio if offered (depends on value that follows) */ -JANUS_SDP_OA_AUDIO = 1, -/*! \brief When generating an offer or answer automatically, accept/reject video if offered (depends on value that follows) */ -JANUS_SDP_OA_VIDEO, -/*! \brief When generating an offer or answer automatically, accept/reject datachannels if offered (depends on value that follows) */ -JANUS_SDP_OA_DATA, -/*! \brief When generating an offer or answer automatically, use this direction for audio (depends on value that follows) */ -JANUS_SDP_OA_AUDIO_DIRECTION, -/*! \brief When generating an offer or answer automatically, use this direction for video (depends on value that follows) */ -JANUS_SDP_OA_VIDEO_DIRECTION, -/*! \brief When generating an offer or answer automatically, use this codec for audio (depends on value that follows) */ -JANUS_SDP_OA_AUDIO_CODEC, -/*! \brief When generating an offer or answer automatically, use this codec for video (depends on value that follows) */ -JANUS_SDP_OA_VIDEO_CODEC, +/*! \brief Add a new m-line of the specific kind (used as a separator for audio, video and data details passed to janus_sdp_generate_offer) */ +JANUS_SDP_OA_MLINE = 1, +/*! \brief Whether we should enable a specific m-line when offering/answering (depends on what follows, true by default) */ +JANUS_SDP_OA_ENABLED, +/*! \brief When generating an offer or answer automatically, use this direction for media (depends on value that follows, sendrecv by default) */ +JANUS_SDP_OA_DIRECTION, +/*! \brief When generating an offer automatically, use this mid media (depends on value that follows, needs to be a string) */ +JANUS_SDP_OA_MID, +/*! \brief When generating an offer or answer automatically, use this codec (depends on value that follows, opus/vp8 by default) */ +JANUS_SDP_OA_CODEC, +/*! \brief When generating an offer (this is ignored for answers), negotiate this extension: needs two arguments, extmap value and extension ID (can be used multiple times) */ +JANUS_SDP_OA_EXTENSION, +/*! \brief When generating an offer (this is ignored for answers), negotiate these extensions: needs a hashtable with the mappings to a specific extmap + * @note This is only used internally, and will be ignored if provided; in plugins, you should stick to JANUS_SDP_OA_EXTENSION */ +JANUS_SDP_OA_EXTENSIONS, +/*! \brief When generating an answer (this is ignored for offers), accept this extension (by default, we reject them all; can be used multiple times) */ +JANUS_SDP_OA_ACCEPT_EXTMAP, +/*! \brief When generating an offer (this is ignored for answers), use this payload type (depends on value that follows) */ +JANUS_SDP_OA_PT, +/*! \brief When generating an offer or answer automatically, add this custom fmtp string + * @note When dealing with video, this property is ignored if JANUS_SDP_OA_VP9_PROFILE or JANUS_SDP_OA_H264_PROFILE is used on a compliant codec. */ +JANUS_SDP_OA_FMTP, +/*! \brief When generating an offer or answer automatically, do or do not negotiate telephone events (FIXME telephone-event/8000 only, true by default) */ +JANUS_SDP_OA_AUDIO_DTMF, /*! \brief When generating an offer or answer automatically, use this profile for VP9 (depends on value that follows) */ JANUS_SDP_OA_VP9_PROFILE, /*! \brief When generating an offer or answer automatically, use this profile for H.264 (depends on value that follows) */ JANUS_SDP_OA_H264_PROFILE, -/*! \brief When generating an offer (this is ignored for answers), use this payload type for audio (depends on value that follows) */ -JANUS_SDP_OA_AUDIO_PT, -/*! \brief When generating an offer (this is ignored for answers), use this payload type for video (depends on value that follows) */ -JANUS_SDP_OA_VIDEO_PT, -/*! \brief When generating an offer or answer automatically, do or do not negotiate telephone events (FIXME telephone-event/8000 only) */ -JANUS_SDP_OA_AUDIO_DTMF, -/*! \brief When generating an offer or answer automatically, add this custom fmtp string for audio */ -JANUS_SDP_OA_AUDIO_FMTP, -/*! \brief When generating an offer or answer automatically, add this custom fmtp string for video - * @note This property is ignored if JANUS_SDP_OA_VP9_PROFILE or JANUS_SDP_OA_H264_PROFILE is used on a compliant codec. */ -JANUS_SDP_OA_VIDEO_FMTP, -/*! \brief When generating an offer or answer automatically, do or do not add the rtcpfb attributes we typically negotiate (fir, nack, pli, remb) */ +/*! \brief When generating an offer or answer automatically, do or do not add the rtcpfb attributes we typically negotiate (fir, nack, pli, remb; true by defaukt) */ JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS, -/*! \brief When generating an offer (this is ignored for answers), use the old "DTLS/SCTP" instead of the new "UDP/DTLS/SCTP (default=TRUE for now, depends on what follows) */ +/*! \brief When generating an offer (this is ignored for answers), use the old "DTLS/SCTP" instead of the new "UDP/DTLS/SCTP (depends on what follows, false by default) */ JANUS_SDP_OA_DATA_LEGACY, -/*! \brief When generating an offer (this is ignored for answers), negotiate this audio extension: needs two arguments, extmap value and extension ID; can be used multiple times) */ -JANUS_SDP_OA_AUDIO_EXTENSION, -/*! \brief When generating an offer (this is ignored for answers), negotiate this video extension: needs two arguments, extmap value and extension ID; can be used multiple times) */ -JANUS_SDP_OA_VIDEO_EXTENSION, -/*! \brief When generating an answer (this is ignored for offers), accept this extension (by default, we reject them all; can be used multiple times) */ -JANUS_SDP_OA_ACCEPT_EXTMAP, -/*! \brief MUST be used as the last argument in janus_sdp_generate_offer and janus_sdp_generate_answer */ +/*! \brief MUST be used as the last argument in janus_sdp_generate_offer, janus_sdp_generate_offer_mline and janus_sdp_generate_answer */ JANUS_SDP_OA_DONE = 0 } janus_sdp_oa_type; +const char *janus_sdp_oa_type_str(janus_sdp_oa_type type); /*! \brief Method to generate a janus_sdp offer, using variable arguments to dictate * what to negotiate (e.g., in terms of media to offer, directions, etc.). Variable * arguments are in the form of a sequence of name-value terminated by a JANUS_SDP_OA_DONE, e.g.: \verbatim janus_sdp *offer = janus_sdp_generate_offer("My session", "127.0.0.1", - JANUS_SDP_OA_AUDIO, TRUE, - JANUS_SDP_OA_AUDIO_PT, 100, - JANUS_SDP_OA_AUDIO_DIRECTION, JANUS_SDP_SENDONLY, - JANUS_SDP_OA_AUDIO_CODEC, "opus", - JANUS_SDP_OA_VIDEO, FALSE, - JANUS_SDP_OA_DATA, FALSE, - JANUS_SDP_OA_DONE); + JANUS_SDP_OA_MLINE, JANUS_SDP_AUDIO, + JANUS_SDP_OA_PT, 100, + JANUS_SDP_OA_DIRECTION, JANUS_SDP_SENDONLY, + JANUS_SDP_OA_CODEC, "opus", + JANUS_SDP_OA_MLINE, JANUS_SDP_VIDEO, + JANUS_SDP_OA_PT, 101, + JANUS_SDP_OA_DIRECTION, JANUS_SDP_RECVONLY, + JANUS_SDP_OA_CODEC, "vp8", \endverbatim - * to only offer a \c sendonly Opus audio stream being offered with 100 as - * payload type, and avoid video and datachannels. Refer to the property names in - * the header file for a complete list of how you can drive the offer. - * The default, if not specified, is to offer everything, using Opus with pt=111 - * for audio, VP8 with pt=96 as video, and data channels, all as \c sendrecv. + * to offer a \c sendonly Opus audio stream being offered with 100 as + * payload type, and a \c recvonly VP8 video stream with 101 as payload type. + * Refer to the property names in the header file for a complete + * list of how you can drive the offer. Other media streams can be added, + * as long as you prefix/specify them with JANUS_SDP_OA_MLINE as done here. + * The default, if not specified, is to not offer anything, meaning it + * will be up to you to add m-lines subsequently. * @param[in] name The session name (if NULL, a default value will be set) * @param[in] address The IP to set in o= and c= fields (if NULL, a default value will be set) * @returns A pointer to a janus_sdp object, if successful, NULL otherwise */ janus_sdp *janus_sdp_generate_offer(const char *name, const char *address, ...); -/*! \brief Method to generate a janus_sdp answer to a provided janus_sdp offer, using variable arguments - * to dictate how to respond (e.g., in terms of media to accept, reject, directions, etc.). Variable - * arguments are in the form of a sequence of name-value terminated by a JANUS_SDP_OA_DONE, e.g.: +/*! \brief Method to add a single m-line to a new offer, using the same + * variable arguments janus_sdp_generate_offer supports. This is useful + * whenever you need to create a new offer, but don't know in advance + * how many m-lines you'll need, or it would be hard to do programmatically + * in a single call to janus_sdp_generate_offer. The first argument + * MUST be JANUS_SDP_OA_MLINE, specifying the type of the media. + * \note In case case you add audio and don't specify anything else, the + * default is to use Opus and payload type 111. For video, the default + * is VP8 and payload type 96. The default media direction is \c sendrecv. + * @param[in] offer The Janus SDP offer to add the new m-line to + * @returns 0 if successful, a negative integer othwerwise */ +int janus_sdp_generate_offer_mline(janus_sdp *offer, ...); +/*! \brief Method to generate a janus_sdp answer to a provided janus_sdp offer. + * Notice that this doesn't address the individual m-lines: it will just + * create an empty response, create the corresponding m-lines, but leave + * them all "rejected". To answer each m-line you'll have to iterate on + * the offer m-lines and call janus_sdp_generate_answer_mline instead, e.g.: \verbatim - janus_sdp *answer = janus_sdp_generate_answer(offer, - JANUS_SDP_OA_AUDIO, TRUE, - JANUS_SDP_OA_AUDIO_DIRECTION, JANUS_SDP_RECVONLY, - JANUS_SDP_OA_AUDIO_CODEC, "opus", - JANUS_SDP_OA_VIDEO, FALSE, - JANUS_SDP_OA_DATA, FALSE, - JANUS_SDP_OA_DONE); + janus_sdp *answer = janus_sdp_generate_answer(offer); + GList *temp = offer->m_lines; + while(temp) { + janus_sdp_mline *m = (janus_sdp_mline *)temp->data; + janus_sdp_generate_answer_mline(offer, answer, m, + [..] + JANUS_SDP_OA_DONE); + temp = temp->next; + } \endverbatim * to only accept the audio stream being offered, but as \c recvonly, use Opus * and reject both video and datachannels. Refer to the property names in @@ -329,44 +350,64 @@ janus_sdp *janus_sdp_generate_offer(const char *name, const char *address, ...); * The default, if not specified, is to accept everything as \c sendrecv. * @param[in] offer The Janus SDP offer to respond to * @returns A pointer to a janus_sdp object, if successful, NULL otherwise */ -janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...); +janus_sdp *janus_sdp_generate_answer(janus_sdp *offer); +/*! \brief Method to respond to a single m-line in an offer, using the same + * variable arguments janus_sdp_generate_offer_mline supports. The first + * argument MUST be JANUS_SDP_OA_MLINE, specifying the type of the media, e.g.: + \verbatim + janus_sdp_generate_answer_mline(offer, answer, offer_mline, + JANUS_SDP_OA_MLINE, JANUS_SDP_AUDIO, + JANUS_SDP_OA_CODEC, "opus", + JANUS_SDP_OA_DIRECTION, JANUS_SDP_RECVONLY, + JANUS_SDP_OA_DONE); + \endverbatim + * to respond to an offered m-line with recvonly audio and use Opus. + * @param[in] offer The Janus SDP offer + * @param[in] answer The Janus SDP answer to add the new m-line to + * @param[in] offered The Janus SDP m-line from the offer to respond to + * @returns 0 if successful, a negative integer othwerwise */ +int janus_sdp_generate_answer_mline(janus_sdp *offer, janus_sdp *answer, janus_sdp_mline *offered, ...); -/*! \brief Helper to get the payload type associated to a specific codec +/*! \brief Helper to get the payload type associated to a specific codec in an m-line * @note This version doesn't involve profiles, which means that in case * of multiple payload types associated to the same codec because of * different profiles (e.g., VP9 and H.264), this will simply return the * first payload type associated with it the codec itself. * @param sdp The Janus SDP instance to process + * @param index The m-line to refer to (use -1 for the first m-line that matches) * @param codec The codec to find, as a string * @returns The payload type, if found, or -1 otherwise */ -int janus_sdp_get_codec_pt(janus_sdp *sdp, const char *codec); +int janus_sdp_get_codec_pt(janus_sdp *sdp, int index, const char *codec); /*! \brief Helper to get the payload type associated to a specific codec, - * taking into account a codec profile as a hint as well + * in an m-line, taking into account a codec profile as a hint as well * @note The profile will only be used if the codec supports it, and the * core is aware of it: right now, this is only VP9 and H.264. If the codec * is there but the profile is not found, then no payload type is returned. * @param sdp The Janus SDP instance to process + * @param index The m-line to refer to (use -1 for the first m-line that matches) * @param codec The codec to find, as a string * @param profile The codec profile to use as a hint, as a string * @returns The payload type, if found, or -1 otherwise */ -int janus_sdp_get_codec_pt_full(janus_sdp *sdp, const char *codec, const char *profile); +int janus_sdp_get_codec_pt_full(janus_sdp *sdp, int index, const char *codec, const char *profile); -/*! \brief Helper to get the codec name associated to a specific payload type +/*! \brief Helper to get the codec name associated to a specific payload type in an m-line * @param sdp The Janus SDP instance to process + * @param index The m-line to refer to (use -1 for the first m-line that matches) * @param pt The payload type to find * @returns The codec name, if found, or NULL otherwise */ -const char *janus_sdp_get_codec_name(janus_sdp *sdp, int pt); +const char *janus_sdp_get_codec_name(janus_sdp *sdp, int index, int pt); /*! \brief Helper to get the rtpmap associated to a specific codec * @param codec The codec name, as a string (e.g., "opus") - * @returns The rtpmap value, if found (e.g., "opus/48000/2"), or -1 otherwise */ + * @returns The rtpmap value, if found (e.g., "opus/48000/2"), or NULL otherwise */ const char *janus_sdp_get_codec_rtpmap(const char *codec); /*! \brief Helper to get the fmtp associated to a specific payload type * @param sdp The Janus SDP instance to process + * @param index The m-line to refer to (use -1 for the first m-line that matches) * @param pt The payload type to find * @returns The fmtp content, if found, or NULL otherwise */ -const char *janus_sdp_get_fmtp(janus_sdp *sdp, int pt); +const char *janus_sdp_get_fmtp(janus_sdp *sdp, int index, int pt); #endif diff --git a/sdp.c b/sdp.c index a5d37864d4..671dc3db13 100644 --- a/sdp.c +++ b/sdp.c @@ -32,7 +32,7 @@ /* Pre-parse SDP: is this SDP valid? how many audio/video lines? any features to take into account? */ janus_sdp *janus_sdp_preparse(void *ice_handle, const char *jsep_sdp, char *error_str, size_t errlen, int *audio, int *video, int *data) { - if(!ice_handle || !jsep_sdp || !audio || !video || !data) { + if(!ice_handle || !jsep_sdp) { JANUS_LOG(LOG_ERR, " Can't preparse, invalid arguments\n"); return NULL; } @@ -47,10 +47,12 @@ janus_sdp *janus_sdp_preparse(void *ice_handle, const char *jsep_sdp, char *erro GList *temp = parsed_sdp->m_lines; while(temp) { janus_sdp_mline *m = (janus_sdp_mline *)temp->data; - if(m->type == JANUS_SDP_AUDIO) { - *audio = *audio + 1; - } else if(m->type == JANUS_SDP_VIDEO) { - *video = *video + 1; + if(audio && m->type == JANUS_SDP_AUDIO) { + *audio = *audio+1; + } else if(video && m->type == JANUS_SDP_VIDEO) { + *video = *video+1; + } else if(data && m->type == JANUS_SDP_APPLICATION && strstr(m->proto, "DTLS/SCTP")) { + *data = *data+1; } /* Preparse the mid as well, and check if bundle-only is used */ GList *tempA = m->attributes; @@ -67,28 +69,13 @@ janus_sdp *janus_sdp_preparse(void *ice_handle, const char *jsep_sdp, char *erro janus_sdp_destroy(parsed_sdp); return NULL; } - if(m->type == JANUS_SDP_AUDIO && m->port > 0) { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Audio mid: %s\n", handle->handle_id, a->value); + if((m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) && m->port > 0) { if(strlen(a->value) > 16) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] Audio mid too large: (%zu > 16)\n", handle->handle_id, strlen(a->value)); + JANUS_LOG(LOG_ERR, "[%"SCNu64"] mid on m-line #%d too large: (%zu > 16)\n", + handle->handle_id, m->index, strlen(a->value)); janus_sdp_destroy(parsed_sdp); return NULL; } - if(handle->audio_mid == NULL) - handle->audio_mid = g_strdup(a->value); - if(handle->stream_mid == NULL) - handle->stream_mid = handle->audio_mid; - } else if(m->type == JANUS_SDP_VIDEO && m->port > 0) { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Video mid: %s\n", handle->handle_id, a->value); - if(strlen(a->value) > 16) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] Video mid too large: (%zu > 16)\n", handle->handle_id, strlen(a->value)); - janus_sdp_destroy(parsed_sdp); - return NULL; - } - if(handle->video_mid == NULL) - handle->video_mid = g_strdup(a->value); - if(handle->stream_mid == NULL) - handle->stream_mid = handle->video_mid; } } } @@ -104,29 +91,20 @@ janus_sdp *janus_sdp_preparse(void *ice_handle, const char *jsep_sdp, char *erro } temp = temp->next; } -#ifdef HAVE_SCTP - *data = (strstr(jsep_sdp, "DTLS/SCTP") && !strstr(jsep_sdp, " 0 DTLS/SCTP") && - !strstr(jsep_sdp, " 0 UDP/DTLS/SCTP")) ? 1 : 0; /* FIXME This is a really hacky way of checking... */ -#else - *data = 0; -#endif return parsed_sdp; } -/* Parse SDP */ -int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml, gboolean update) { +/* Parse remote SDP */ +int janus_sdp_process_remote(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml, gboolean update) { if(!ice_handle || !remote_sdp) return -1; janus_ice_handle *handle = (janus_ice_handle *)ice_handle; - janus_ice_stream *stream = handle->stream; - if(!stream) + janus_ice_peerconnection *pc = handle->pc; + if(!pc) return -1; + janus_ice_peerconnection_medium *medium = NULL; gchar *ruser = NULL, *rpass = NULL, *rhashing = NULL, *rfingerprint = NULL; - int audio = 0, video = 0; -#ifdef HAVE_SCTP - int data = 0; -#endif gboolean rtx = FALSE; /* Ok, let's start with global attributes */ GList *temp = remote_sdp->attributes; @@ -157,123 +135,67 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml temp = temp->next; } /* Now go on with m-line and their attributes */ - int mlines = 0; temp = remote_sdp->m_lines; while(temp) { - mlines++; janus_sdp_mline *m = (janus_sdp_mline *)temp->data; - if(m->type == JANUS_SDP_AUDIO) { - if(handle->rtp_profile == NULL && m->proto != NULL) - handle->rtp_profile = g_strdup(m->proto); - audio++; - if(audio > 1) { - temp = temp->next; - continue; - } - if(m->port > 0) { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Parsing audio candidates (stream=%d)...\n", handle->handle_id, stream->stream_id); - if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AUDIO)) { - janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AUDIO); - stream->audio_ssrc = janus_random_uint32(); /* FIXME Should we look for conflicts? */ - if(stream->audio_rtcp_ctx == NULL) { - stream->audio_rtcp_ctx = g_malloc0(sizeof(rtcp_context)); - stream->audio_rtcp_ctx->tb = 48000; /* May change later */ - } - } - switch(m->direction) { - case JANUS_SDP_INACTIVE: - case JANUS_SDP_INVALID: - stream->audio_send = FALSE; - stream->audio_recv = FALSE; - break; - case JANUS_SDP_SENDONLY: - /* A sendonly peer means recvonly for Janus */ - stream->audio_send = FALSE; - stream->audio_recv = TRUE; - break; - case JANUS_SDP_RECVONLY: - /* A recvonly peer means sendonly for Janus */ - stream->audio_send = TRUE; - stream->audio_recv = FALSE; - break; - case JANUS_SDP_SENDRECV: - case JANUS_SDP_DEFAULT: - default: - stream->audio_send = TRUE; - stream->audio_recv = TRUE; - break; - } - if(m->ptypes != NULL) { - g_list_free(stream->audio_payload_types); - stream->audio_payload_types = g_list_copy(m->ptypes); - } - } else { - /* Audio rejected? */ - janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AUDIO); - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Audio rejected by peer...\n", handle->handle_id); - } - } else if(m->type == JANUS_SDP_VIDEO) { + if(m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) { + /* Audio/Video */ if(handle->rtp_profile == NULL && m->proto != NULL) handle->rtp_profile = g_strdup(m->proto); - video++; - if(video > 1) { - temp = temp->next; - continue; + /* Find the internal medium instance */ + medium = g_hash_table_lookup(pc->media, GINT_TO_POINTER(m->index)); + if(!medium) { + /* We don't have it, create one now */ + medium = janus_ice_peerconnection_medium_create(handle, + m->type == JANUS_SDP_VIDEO ? JANUS_MEDIA_VIDEO : JANUS_MEDIA_AUDIO); } if(m->port > 0) { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Parsing video candidates (stream=%d)...\n", handle->handle_id, stream->stream_id); - if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO)) { - janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO); - stream->video_ssrc = janus_random_uint32(); /* FIXME Should we look for conflicts? */ - if(stream->video_rtcp_ctx[0] == NULL) { - stream->video_rtcp_ctx[0] = g_malloc0(sizeof(rtcp_context)); - stream->video_rtcp_ctx[0]->tb = 90000; /* May change later */ - } - } + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Parsing m-line #%d...\n", handle->handle_id, m->index); switch(m->direction) { case JANUS_SDP_INACTIVE: case JANUS_SDP_INVALID: - stream->video_send = FALSE; - stream->video_recv = FALSE; + medium->send = FALSE; + medium->recv = FALSE; break; case JANUS_SDP_SENDONLY: /* A sendonly peer means recvonly for Janus */ - stream->video_send = FALSE; - stream->video_recv = TRUE; + medium->send = FALSE; + medium->recv = TRUE; break; case JANUS_SDP_RECVONLY: /* A recvonly peer means sendonly for Janus */ - stream->video_send = TRUE; - stream->video_recv = FALSE; + medium->send = TRUE; + medium->recv = FALSE; break; case JANUS_SDP_SENDRECV: case JANUS_SDP_DEFAULT: default: - stream->video_send = TRUE; - stream->video_recv = TRUE; + medium->send = TRUE; + medium->recv = TRUE; break; } if(m->ptypes != NULL) { - g_list_free(stream->video_payload_types); - stream->video_payload_types = g_list_copy(m->ptypes); + g_list_free(medium->payload_types); + medium->payload_types = g_list_copy(m->ptypes); } } else { - /* Video rejected? */ - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Video rejected by peer...\n", handle->handle_id); - janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO); + /* Medium rejected? */ + medium->send = FALSE; + medium->recv = FALSE; } -#ifdef HAVE_SCTP } else if(m->type == JANUS_SDP_APPLICATION) { + /* Find the internal medium instance */ + medium = g_hash_table_lookup(pc->media, GINT_TO_POINTER(m->index)); + if(!medium) { + /* We don't have it, create one now */ + medium = janus_ice_peerconnection_medium_create(handle, JANUS_MEDIA_DATA); + } /* Is this SCTP for DataChannels? */ if(!strcasecmp(m->proto, "DTLS/SCTP") || !strcasecmp(m->proto, "UDP/DTLS/SCTP")) { - data++; - if(data > 1) { - temp = temp->next; - continue; - } +#ifdef HAVE_SCTP if(m->port > 0) { /* Yep */ - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Parsing SCTP candidates (stream=%d)...\n", handle->handle_id, stream->stream_id); + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Parsing m-line #%d... (data channels)\n", handle->handle_id, m->index); if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_DATA_CHANNELS)) { janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_DATA_CHANNELS); } @@ -282,23 +204,39 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml } else { janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_NEW_DATACHAN_SDP); } + medium->send = TRUE; + medium->recv = TRUE; } else { /* Data channels rejected? */ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Data channels rejected by peer...\n", handle->handle_id); janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_DATA_CHANNELS); janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_NEW_DATACHAN_SDP); + medium->send = FALSE; + medium->recv = FALSE; } +#else + /* Data channels unsupported */ + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Data channels unsupported...\n", handle->handle_id); + janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_NEW_DATACHAN_SDP); + medium->send = FALSE; + medium->recv = FALSE; +#endif } else { /* Unsupported data channels format. */ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Data channels format %s unsupported, skipping\n", handle->handle_id, m->proto); janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_DATA_CHANNELS); janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_NEW_DATACHAN_SDP); } -#endif } else { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping disabled/unsupported media line...\n", handle->handle_id); + medium = g_hash_table_lookup(pc->media, GINT_TO_POINTER(m->index)); + if(!medium) { + /* We don't have it, create one now */ + medium = janus_ice_peerconnection_medium_create(handle, JANUS_MEDIA_UNKNOWN); + } } - if(stream == NULL) { + if(medium == NULL) { + /* No medium? Should never happen */ temp = temp->next; continue; } @@ -309,35 +247,20 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml if(a->name && a->value) { if(!strcasecmp(a->name, "mid")) { /* Found mid attribute */ - if(m->type == JANUS_SDP_AUDIO && m->port > 0) { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Audio mid: %s\n", handle->handle_id, a->value); - if(strlen(a->value) > 16) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] Audio mid too large: (%zu > 16)\n", handle->handle_id, strlen(a->value)); - return -2; - } - if(handle->audio_mid == NULL) - handle->audio_mid = g_strdup(a->value); - if(handle->stream_mid == NULL) - handle->stream_mid = handle->audio_mid; - } else if(m->type == JANUS_SDP_VIDEO && m->port > 0) { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Video mid: %s\n", handle->handle_id, a->value); - if(strlen(a->value) > 16) { - JANUS_LOG(LOG_ERR, "[%"SCNu64"] Video mid too large: (%zu > 16)\n", handle->handle_id, strlen(a->value)); - return -2; + if(strlen(a->value) > 16) { + JANUS_LOG(LOG_ERR, "[%"SCNu64"] mid on m-line #%d too large: (%zu > 16)\n", + handle->handle_id, m->index, strlen(a->value)); + return -2; + } + if(medium->mid == NULL) { + medium->mid = g_strdup(a->value); + if(!g_hash_table_lookup(pc->media_bymid, medium->mid)) { + g_hash_table_insert(pc->media_bymid, g_strdup(medium->mid), medium); + janus_refcount_increase(&medium->ref); } - if(handle->video_mid == NULL) - handle->video_mid = g_strdup(a->value); - if(handle->stream_mid == NULL) - handle->stream_mid = handle->video_mid; -#ifdef HAVE_SCTP - } else if(m->type == JANUS_SDP_APPLICATION) { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Data Channel mid: %s\n", handle->handle_id, a->value); - if(handle->data_mid == NULL) - handle->data_mid = g_strdup(a->value); - if(handle->stream_mid == NULL) - handle->stream_mid = handle->data_mid; -#endif } + if(handle->pc_mid == NULL) + handle->pc_mid = g_strdup(a->value); } else if(!strcasecmp(a->name, "fingerprint")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Fingerprint (local) : %s\n", handle->handle_id, a->value); if(strcasestr(a->value, "sha-256 ") == a->value) { @@ -360,13 +283,13 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml if(!update) { if(!strcasecmp(a->value, "actpass") || !strcasecmp(a->value, "passive")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Setting connect state (DTLS client)\n", handle->handle_id); - stream->dtls_role = JANUS_DTLS_ROLE_CLIENT; + pc->dtls_role = JANUS_DTLS_ROLE_CLIENT; } else if(!strcasecmp(a->value, "active")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Setting accept state (DTLS server)\n", handle->handle_id); - stream->dtls_role = JANUS_DTLS_ROLE_SERVER; + pc->dtls_role = JANUS_DTLS_ROLE_SERVER; } - if(stream->component && stream->component->dtls) - stream->component->dtls->dtls_role = stream->dtls_role; + if(pc->dtls) + pc->dtls->dtls_role = pc->dtls_role; } /* TODO Handle holdconn... */ } else if(!strcasecmp(a->name, "ice-ufrag")) { @@ -381,7 +304,7 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml } tempA = tempA->next; } - if(mlines == 1) { + if(m->index == 0) { if(!ruser || !rpass || (janus_is_webrtc_encryption_enabled() && (!rfingerprint || !rhashing))) { /* Missing mandatory information, failure... */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] SDP missing mandatory information\n", handle->handle_id); @@ -401,35 +324,35 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml return -2; } /* If we received the ICE credentials for the first time, enforce them */ - if(ruser && !stream->ruser && rpass && !stream->rpass) { + if(ruser && !pc->ruser && rpass && !pc->rpass) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Setting remote credentials...\n", handle->handle_id); if(!nice_agent_set_remote_credentials(handle->agent, handle->stream_id, ruser, rpass)) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to set remote credentials!\n", handle->handle_id); } } else /* If this is a renegotiation, check if this is an ICE restart */ - if((ruser && stream->ruser && strcmp(ruser, stream->ruser)) || - (rpass && stream->rpass && strcmp(rpass, stream->rpass))) { + if((ruser && pc->ruser && strcmp(ruser, pc->ruser)) || + (rpass && pc->rpass && strcmp(rpass, pc->rpass))) { JANUS_LOG(LOG_INFO, "[%"SCNu64"] ICE restart detected\n", handle->handle_id); janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALL_TRICKLES); janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ICE_RESTART); } /* Store fingerprint and hashing */ if(janus_is_webrtc_encryption_enabled()) { - g_free(stream->remote_hashing); - stream->remote_hashing = g_strdup(rhashing); - g_free(stream->remote_fingerprint); - stream->remote_fingerprint = g_strdup(rfingerprint); + g_free(pc->remote_hashing); + pc->remote_hashing = g_strdup(rhashing); + g_free(pc->remote_fingerprint); + pc->remote_fingerprint = g_strdup(rfingerprint); } /* Store the ICE username and password for this stream */ - g_free(stream->ruser); - stream->ruser = g_strdup(ruser); - g_free(stream->rpass); - stream->rpass = g_strdup(rpass); + g_free(pc->ruser); + pc->ruser = g_strdup(ruser); + g_free(pc->rpass); + pc->rpass = g_strdup(rpass); } /* Is simulcasting enabled, using rid? (we need to check this before parsing SSRCs) */ tempA = m->attributes; - stream->rids_hml = rids_hml; + medium->rids_hml = rids_hml; while(tempA) { janus_sdp_attribute *a = (janus_sdp_attribute *)tempA->data; if(a->name && !strcasecmp(a->name, "rid") && a->value) { @@ -439,47 +362,46 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse rid attribute...\n", handle->handle_id); } else { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Parsed rid: %s\n", handle->handle_id, rid); - if(stream->rid[rids_hml ? 2 : 0] == NULL) { - stream->rid[rids_hml ? 2 : 0] = g_strdup(rid); - } else if(stream->rid[1] == NULL) { - stream->rid[1] = g_strdup(rid); - } else if(stream->rid[rids_hml ? 0 : 2] == NULL) { - stream->rid[rids_hml ? 0 : 2] = g_strdup(rid); + if(medium->rid[rids_hml ? 2 : 0] == NULL) { + medium->rid[rids_hml ? 2 : 0] = g_strdup(rid); + } else if(medium->rid[1] == NULL) { + medium->rid[1] = g_strdup(rid); + } else if(medium->rid[rids_hml ? 0 : 2] == NULL) { + medium->rid[rids_hml ? 0 : 2] = g_strdup(rid); } else { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Too many RTP Stream IDs, ignoring '%s'...\n", handle->handle_id, rid); } } } else if(a->name && !strcasecmp(a->name, "simulcast") && a->value) { /* Firefox and Chrome signal simulcast support differently */ - stream->legacy_rid = strstr(a->value, "rid=") ? TRUE : FALSE; + medium->legacy_rid = strstr(a->value, "rid=") ? TRUE : FALSE; } tempA = tempA->next; } /* If rid is involved, check how many of them we have (it may be less than 3) */ - if(stream->rid[0] == NULL && stream->rid[2] != NULL) { - stream->rid[0] = stream->rid[1]; - stream->rid[1] = stream->rid[2]; - stream->rid[2] = NULL; + if(medium->rid[0] == NULL && medium->rid[2] != NULL) { + medium->rid[0] = medium->rid[1]; + medium->rid[1] = medium->rid[2]; + medium->rid[2] = NULL; } - if(stream->rid[0] == NULL && stream->rid[1] != NULL) { - stream->rid[0] = stream->rid[1]; - stream->rid[1] = NULL; + if(medium->rid[0] == NULL && medium->rid[1] != NULL) { + medium->rid[0] = medium->rid[1]; + medium->rid[1] = NULL; } /* Let's start figuring out the SSRCs, and any grouping that may be there */ - stream->audio_ssrc_peer_new = 0; - stream->video_ssrc_peer_new[0] = 0; - stream->video_ssrc_peer_new[1] = 0; - stream->video_ssrc_peer_new[2] = 0; - stream->video_ssrc_peer_rtx_new[0] = 0; - stream->video_ssrc_peer_rtx_new[1] = 0; - stream->video_ssrc_peer_rtx_new[2] = 0; + medium->ssrc_peer_new[0] = 0; + medium->ssrc_peer_new[1] = 0; + medium->ssrc_peer_new[2] = 0; + medium->ssrc_peer_rtx_new[0] = 0; + medium->ssrc_peer_rtx_new[1] = 0; + medium->ssrc_peer_rtx_new[2] = 0; /* Any SSRC SIM group? */ tempA = m->attributes; while(tempA) { janus_sdp_attribute *a = (janus_sdp_attribute *)tempA->data; if(a->name && a->value) { if(!strcasecmp(a->name, "ssrc-group") && strstr(a->value, "SIM")) { - int res = janus_sdp_parse_ssrc_group(stream, (const char *)a->value, m->type == JANUS_SDP_VIDEO); + int res = janus_sdp_parse_ssrc_group(medium, (const char *)a->value, m->type == JANUS_SDP_VIDEO); if(res != 0) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse SSRC SIM group attribute... (%d)\n", handle->handle_id, res); } @@ -493,7 +415,7 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml janus_sdp_attribute *a = (janus_sdp_attribute *)tempA->data; if(a->name && a->value) { if(!strcasecmp(a->name, "ssrc-group") && strstr(a->value, "FID")) { - int res = janus_sdp_parse_ssrc_group(stream, (const char *)a->value, m->type == JANUS_SDP_VIDEO); + int res = janus_sdp_parse_ssrc_group(medium, (const char *)a->value, m->type == JANUS_SDP_VIDEO); if(res != 0) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse SSRC FID group attribute... (%d)\n", handle->handle_id, res); } @@ -507,7 +429,7 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml janus_sdp_attribute *a = (janus_sdp_attribute *)tempA->data; if(a->name && a->value) { if(!strcasecmp(a->name, "ssrc")) { - int res = janus_sdp_parse_ssrc(stream, (const char *)a->value, m->type == JANUS_SDP_VIDEO); + int res = janus_sdp_parse_ssrc(medium, (const char *)a->value, m->type == JANUS_SDP_VIDEO); if(res != 0) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse SSRC attribute... (%d)\n", handle->handle_id, res); } @@ -521,29 +443,19 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml janus_sdp_attribute *a = (janus_sdp_attribute *)tempA->data; if(a->name) { if(!strcasecmp(a->name, "candidate")) { - if(m->type == JANUS_SDP_AUDIO && mlines > 1) { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] This is an audio candidate but we're bundling on another stream, ignoring...\n", handle->handle_id); - } else if(m->type == JANUS_SDP_VIDEO && mlines > 1) { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] This is a video candidate but we're bundling on another stream, ignoring...\n", handle->handle_id); -#ifdef HAVE_SCTP - } else if(m->type == JANUS_SDP_APPLICATION && mlines > 1) { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] This is a SCTP candidate but we're bundling on another stream, ignoring...\n", handle->handle_id); -#endif + if(m->index > 1) { + JANUS_LOG(LOG_VERB, "[%"SCNu64"] This is a %s candidate, but we're bundling on another stream, ignoring...\n", + handle->handle_id, janus_sdp_mtype_str(m->type)); } else { - int res = janus_sdp_parse_candidate(stream, (const char *)a->value, 0); + int res = janus_sdp_parse_candidate(pc, (const char *)a->value, 0); if(res != 0) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse candidate... (%d)\n", handle->handle_id, res); } } } else if(!strcasecmp(a->name, "rtcp-fb")) { - if(a->value && strstr(a->value, "nack") && stream->component) { - if(m->type == JANUS_SDP_AUDIO) { - /* Enable NACKs for audio */ - stream->component->do_audio_nacks = TRUE; - } else if(m->type == JANUS_SDP_VIDEO) { - /* Enable NACKs for video */ - stream->component->do_video_nacks = TRUE; - } + if(a->value && strstr(a->value, "nack") && medium) { + /* Enable NACKs */ + medium->do_nacks = TRUE; } } else if(!strcasecmp(a->name, "fmtp")) { if(a->value && strstr(a->value, "apt=")) { @@ -554,9 +466,9 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml } else { rtx = TRUE; janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX); - if(stream->rtx_payload_types == NULL) - stream->rtx_payload_types = g_hash_table_new(NULL, NULL); - g_hash_table_insert(stream->rtx_payload_types, GINT_TO_POINTER(ptype), GINT_TO_POINTER(rtx_ptype)); + if(medium->rtx_payload_types == NULL) + medium->rtx_payload_types = g_hash_table_new(NULL, NULL); + g_hash_table_insert(medium->rtx_payload_types, GINT_TO_POINTER(ptype), GINT_TO_POINTER(rtx_ptype)); } } } else if(!strcasecmp(a->name, "rtpmap")) { @@ -568,9 +480,9 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml cr++; uint32_t clock_rate = 0; if(janus_string_to_uint32(cr, &clock_rate) == 0) { - if(stream->clock_rates == NULL) - stream->clock_rates = g_hash_table_new(NULL, NULL); - g_hash_table_insert(stream->clock_rates, GINT_TO_POINTER(ptype), GUINT_TO_POINTER(clock_rate)); + if(medium->clock_rates == NULL) + medium->clock_rates = g_hash_table_new(NULL, NULL); + g_hash_table_insert(medium->clock_rates, GINT_TO_POINTER(ptype), GUINT_TO_POINTER(clock_rate)); } } } @@ -586,67 +498,61 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml tempA = tempA->next; } /* Any change in SSRCs we should be aware of? */ - if(m->type == JANUS_SDP_AUDIO) { - if(stream->audio_ssrc_peer_new > 0) { - if(stream->audio_ssrc_peer > 0 && stream->audio_ssrc_peer != stream->audio_ssrc_peer_new) { - JANUS_LOG(LOG_INFO, "[%"SCNu64"] Audio SSRC changed: %"SCNu32" --> %"SCNu32"\n", - handle->handle_id, stream->audio_ssrc_peer, stream->audio_ssrc_peer_new); - /* FIXME Reset the RTCP context */ - janus_ice_component *component = stream->component; - janus_mutex_lock(&component->mutex); - if(stream->audio_rtcp_ctx) { - memset(stream->audio_rtcp_ctx, 0, sizeof(*stream->audio_rtcp_ctx)); - stream->audio_rtcp_ctx->tb = 48000; /* May change later */ - } - if(component->last_seqs_audio) - janus_seq_list_free(&component->last_seqs_audio); - janus_mutex_unlock(&component->mutex); - } - stream->audio_ssrc_peer = stream->audio_ssrc_peer_new; - stream->audio_ssrc_peer_new = 0; - } - } else if(m->type == JANUS_SDP_VIDEO) { + if(m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) { int vindex = 0; - for(vindex=0; vindex<3; vindex++) { - if(stream->video_ssrc_peer_new[vindex] > 0) { - if(stream->video_ssrc_peer[vindex] > 0 && stream->video_ssrc_peer[vindex] != stream->video_ssrc_peer_new[vindex]) { - JANUS_LOG(LOG_INFO, "[%"SCNu64"] Video SSRC (#%d) changed: %"SCNu32" --> %"SCNu32"\n", - handle->handle_id, vindex, stream->video_ssrc_peer[vindex], stream->video_ssrc_peer_new[vindex]); + for(vindex=0; vindex<(m->type == JANUS_SDP_VIDEO ? 3 :1); vindex++) { + if(medium->ssrc_peer_new[vindex] > 0) { + if(medium->ssrc_peer[vindex] > 0 && medium->ssrc_peer[vindex] != medium->ssrc_peer_new[vindex]) { + JANUS_LOG(LOG_INFO, "[%"SCNu64"] %s SSRC (#%d) on mline #%d changed: %"SCNu32" --> %"SCNu32"\n", + handle->handle_id, m->type == JANUS_SDP_VIDEO ? "Video" : "Audio", + vindex, m->index, medium->ssrc_peer[vindex], medium->ssrc_peer_new[vindex]); /* FIXME Reset the RTCP context */ - janus_ice_component *component = stream->component; - if(component != NULL) { - janus_mutex_lock(&component->mutex); - if(stream->video_rtcp_ctx[vindex]) { - memset(stream->video_rtcp_ctx[vindex], 0, sizeof(*stream->video_rtcp_ctx[vindex])); - stream->video_rtcp_ctx[vindex]->tb = 90000; - } - if(component->last_seqs_video[vindex]) - janus_seq_list_free(&component->last_seqs_video[vindex]); - janus_mutex_unlock(&component->mutex); + janus_mutex_lock(&medium->mutex); + if(medium->rtcp_ctx[vindex]) { + memset(medium->rtcp_ctx[vindex], 0, sizeof(*medium->rtcp_ctx[vindex])); + medium->rtcp_ctx[vindex]->tb = (m->type == JANUS_SDP_VIDEO ? 90000 : 48000); /* May change later */; } + if(medium->last_seqs[vindex]) + janus_seq_list_free(&medium->last_seqs[vindex]); + janus_mutex_unlock(&medium->mutex); } - stream->video_ssrc_peer[vindex] = stream->video_ssrc_peer_new[vindex]; - stream->video_ssrc_peer_new[vindex] = 0; + medium->ssrc_peer[vindex] = medium->ssrc_peer_new[vindex]; + medium->ssrc_peer_new[vindex] = 0; + } + if(!g_hash_table_lookup(pc->media_byssrc, GINT_TO_POINTER(medium->ssrc_peer[vindex]))) { + g_hash_table_insert(pc->media_byssrc, GINT_TO_POINTER(medium->ssrc_peer[vindex]), medium); + janus_refcount_increase(&medium->ref); } /* Do the same with the related rtx SSRC, if any */ - if(stream->video_ssrc_peer_rtx_new[vindex] > 0) { - if(stream->video_ssrc_peer_rtx[vindex] > 0 && stream->video_ssrc_peer_rtx[vindex] != stream->video_ssrc_peer_rtx_new[vindex]) { - JANUS_LOG(LOG_INFO, "[%"SCNu64"] Video SSRC (#%d rtx) changed: %"SCNu32" --> %"SCNu32"\n", - handle->handle_id, vindex, stream->video_ssrc_peer_rtx[vindex], stream->video_ssrc_peer_rtx_new[vindex]); + if(medium->ssrc_peer_rtx_new[vindex] > 0) { + if(medium->ssrc_peer_rtx[vindex] > 0 && medium->ssrc_peer_rtx[vindex] != medium->ssrc_peer_rtx_new[vindex]) { + JANUS_LOG(LOG_INFO, "[%"SCNu64"] %s SSRC (#%d rtx) on mline #%d changed: %"SCNu32" --> %"SCNu32"\n", + handle->handle_id, m->type == JANUS_SDP_VIDEO ? "Video" : "Audio", + vindex, m->index, medium->ssrc_peer_rtx[vindex], medium->ssrc_peer_rtx_new[vindex]); + } + medium->ssrc_peer_rtx[vindex] = medium->ssrc_peer_rtx_new[vindex]; + medium->ssrc_peer_rtx_new[vindex] = 0; + if(medium->ssrc_rtx == 0) + medium->ssrc_rtx = janus_random_uint32(); /* FIXME Should we look for conflicts? */ + if(!g_hash_table_lookup(pc->media_byssrc, GINT_TO_POINTER(medium->ssrc_peer_rtx[vindex]))) { + g_hash_table_insert(pc->media_byssrc, GINT_TO_POINTER(medium->ssrc_peer_rtx[vindex]), medium); + janus_refcount_increase(&medium->ref); + } + if(!g_hash_table_lookup(pc->media_byssrc, GINT_TO_POINTER(medium->ssrc_rtx))) { + g_hash_table_insert(pc->media_byssrc, GINT_TO_POINTER(medium->ssrc_rtx), medium); + janus_refcount_increase(&medium->ref); } - stream->video_ssrc_peer_rtx[vindex] = stream->video_ssrc_peer_rtx_new[vindex]; - stream->video_ssrc_peer_rtx_new[vindex] = 0; - if(stream->video_ssrc_rtx == 0) - stream->video_ssrc_rtx = janus_random_uint32(); /* FIXME Should we look for conflicts? */ } } - if(stream->video_ssrc_peer[1] && stream->video_rtcp_ctx[1] == NULL) { - stream->video_rtcp_ctx[1] = g_malloc0(sizeof(rtcp_context)); - stream->video_rtcp_ctx[1]->tb = 90000; - } - if(stream->video_ssrc_peer[2] && stream->video_rtcp_ctx[2] == NULL) { - stream->video_rtcp_ctx[2] = g_malloc0(sizeof(rtcp_context)); - stream->video_rtcp_ctx[2]->tb = 90000; + if(m->type == JANUS_SDP_VIDEO) { + if(medium->ssrc_peer[1] && medium->rtcp_ctx[1] == NULL) { + medium->rtcp_ctx[1] = g_malloc0(sizeof(rtcp_context)); + medium->rtcp_ctx[1]->tb = 90000; + } + if(medium->ssrc_peer[2] && medium->rtcp_ctx[2] == NULL) { + medium->rtcp_ctx[2] = g_malloc0(sizeof(rtcp_context)); + medium->rtcp_ctx[2]->tb = 90000; + } } } temp = temp->next; @@ -654,7 +560,15 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml /* Disable RFC4588 if the peer didn't negotiate it */ if(!rtx) { janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX); - stream->video_ssrc_rtx = 0; + /* Iterate on all media */ + uint mi=0; + for(mi=0; mimedia); mi++) { + medium = g_hash_table_lookup(pc->media, GUINT_TO_POINTER(mi)); + if(medium) { + g_hash_table_remove(pc->media_byssrc, GINT_TO_POINTER(medium->ssrc_rtx)); + medium->ssrc_rtx = 0; + } + } } /* Cleanup */ g_free(ruser); @@ -665,6 +579,105 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean rids_hml return 0; /* FIXME Handle errors better */ } +/* Parse local SDP */ +int janus_sdp_process_local(void *ice_handle, janus_sdp *remote_sdp, gboolean update) { + if(!ice_handle || !remote_sdp) + return -1; + janus_ice_handle *handle = (janus_ice_handle *)ice_handle; + janus_ice_peerconnection *pc = handle->pc; + if(!pc) + return -1; + janus_ice_peerconnection_medium *medium = NULL; + /* We only go through m-lines to setup medium instances accordingly */ + GList *temp = remote_sdp->m_lines; + while(temp) { + janus_sdp_mline *m = (janus_sdp_mline *)temp->data; + /* Find the internal medium instance */ + medium = g_hash_table_lookup(pc->media, GINT_TO_POINTER(m->index)); + if(!medium) { + /* We don't have it, create one now */ + if(m->type == JANUS_SDP_AUDIO) + medium = janus_ice_peerconnection_medium_create(handle, JANUS_MEDIA_AUDIO); + else if(m->type == JANUS_SDP_VIDEO) + medium = janus_ice_peerconnection_medium_create(handle, JANUS_MEDIA_VIDEO); + else if(m->type == JANUS_SDP_APPLICATION && strstr(m->proto, "DTLS/SCTP")) + medium = janus_ice_peerconnection_medium_create(handle, JANUS_MEDIA_DATA); + else + medium = janus_ice_peerconnection_medium_create(handle, JANUS_MEDIA_UNKNOWN); + } + /* Check if the offer contributed an mid */ + GList *tempA = m->attributes; + while(tempA) { + janus_sdp_attribute *a = (janus_sdp_attribute *)tempA->data; + if(a->name) { + if(!strcasecmp(a->name, "mid")) { + /* Found mid attribute */ + if(strlen(a->value) > 16) { + JANUS_LOG(LOG_ERR, "[%"SCNu64"] mid on m-line #%d too large: (%zu > 16)\n", + handle->handle_id, m->index, strlen(a->value)); + return -2; + } + if(medium->mid == NULL) { + medium->mid = g_strdup(a->value); + if(!g_hash_table_lookup(pc->media_bymid, medium->mid)) { + g_hash_table_insert(pc->media_bymid, g_strdup(medium->mid), medium); + janus_refcount_increase(&medium->ref); + } + } + if(handle->pc_mid == NULL) + handle->pc_mid = g_strdup(a->value); + } + } + tempA = tempA->next; + } + if(medium->mid == NULL) { + /* No mid provided, generate one now */ + char mid[5]; + memset(mid, 0, sizeof(mid)); + g_snprintf(mid, sizeof(mid), "%d", m->index); + medium->mid = g_strdup(mid); + if(!g_hash_table_lookup(pc->media_bymid, medium->mid)) { + g_hash_table_insert(pc->media_bymid, g_strdup(medium->mid), medium); + janus_refcount_increase(&medium->ref); + } + } + if(m->direction == JANUS_SDP_INACTIVE) { + /* FIXME Reset the local SSRCs and RTCP context */ + if(medium->ssrc != 0) + g_hash_table_remove(pc->media_byssrc, GINT_TO_POINTER(medium->ssrc)); + medium->ssrc = 0; + if(medium->ssrc_rtx != 0) + g_hash_table_remove(pc->media_byssrc, GINT_TO_POINTER(medium->ssrc_rtx)); + medium->ssrc_rtx = 0; + int vindex = 0; + for(vindex=0; vindex<3; vindex++) { + if(medium->rtcp_ctx[vindex]) { + int tb = medium->rtcp_ctx[vindex]->tb; + memset(medium->rtcp_ctx[vindex], 0, sizeof(janus_rtcp_context)); + medium->rtcp_ctx[vindex]->tb = tb; + } + } + } else if(m->type != JANUS_SDP_APPLICATION) { + if(medium->ssrc == 0) { + medium->ssrc = janus_random_uint32(); /* FIXME Should we look for conflicts? */ + if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX)) { + /* Create an SSRC for RFC4588 as well */ + medium->ssrc_rtx = janus_random_uint32(); /* FIXME Should we look for conflicts? */ + } + /* Update the SSRC-indexed map */ + g_hash_table_insert(pc->media_byssrc, GINT_TO_POINTER(medium->ssrc), medium); + janus_refcount_increase(&medium->ref); + if(medium->ssrc_rtx > 0) { + g_hash_table_insert(pc->media_byssrc, GINT_TO_POINTER(medium->ssrc_rtx), medium); + janus_refcount_increase(&medium->ref); + } + } + } + temp = temp->next; + } + return 0; /* FIXME Handle errors better */ +} + typedef struct janus_sdp_mdns_candidate { janus_ice_handle *handle; char *candidate, *local; @@ -692,14 +705,14 @@ static void janus_sdp_mdns_resolved(GObject *source_object, GAsyncResult *res, g } g_resolver_free_addresses(list); g_object_unref(resolver); - if(resolved != NULL && mc->handle->stream && mc->handle->app_handle && + if(resolved != NULL && mc->handle->pc && mc->handle->app_handle && !g_atomic_int_get(&mc->handle->app_handle->stopped) && !g_atomic_int_get(&mc->handle->destroyed)) { /* Replace the .local address with the resolved one in the candidate string */ mc->candidate = janus_string_replace(mc->candidate, mc->local, resolved); /* Parse the candidate again */ janus_mutex_lock(&mc->handle->mutex); - (void)janus_sdp_parse_candidate(mc->handle->stream, mc->candidate, 1); + (void)janus_sdp_parse_candidate(mc->handle->pc, mc->candidate, 1); janus_mutex_unlock(&mc->handle->mutex); } g_free(resolved); @@ -710,14 +723,13 @@ static void janus_sdp_mdns_resolved(GObject *source_object, GAsyncResult *res, g g_free(mc); } -int janus_sdp_parse_candidate(void *ice_stream, const char *candidate, int trickle) { - if(ice_stream == NULL || candidate == NULL) +int janus_sdp_parse_candidate(void *ice_pc, const char *candidate, int trickle) { + if(ice_pc == NULL || candidate == NULL) return -1; - janus_ice_stream *stream = (janus_ice_stream *)ice_stream; - janus_ice_handle *handle = stream->handle; + janus_ice_peerconnection *pc = (janus_ice_peerconnection *)ice_pc; + janus_ice_handle *handle = pc->handle; if(handle == NULL) return -2; - janus_ice_component *component = NULL; if(strlen(candidate) == 0 || strstr(candidate, "end-of-candidates")) { /* FIXME Should we do something with this? */ JANUS_LOG(LOG_VERB, "[%"SCNu64"] end-of-candidates received\n", handle->handle_id); @@ -764,23 +776,21 @@ int janus_sdp_parse_candidate(void *ice_stream, const char *candidate, int trick return 0; } /* Add remote candidate */ - component = stream->component; - if(component == NULL || rcomponent > 1) { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Skipping component %d in stream %d (rtcp-muxing)\n", handle->handle_id, rcomponent, stream->stream_id); + if(rcomponent > 1) { + JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Skipping component %d in stream %d (rtcp-muxing)\n", handle->handle_id, rcomponent, pc->stream_id); } else { //~ if(trickle) { - //~ if(component->dtls != NULL) { + //~ if(pc->dtls != NULL) { //~ /* This component is already ready, ignore this further candidate */ //~ JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Ignoring this candidate, the component is already ready\n", handle->handle_id); //~ return 0; //~ } //~ } - component->component_id = rcomponent; - component->stream_id = stream->stream_id; + pc->component_id = rcomponent; NiceCandidate *c = NULL; if(!strcasecmp(rtype, "host")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Adding remote candidate component:%d stream:%d type:host %s:%d\n", - handle->handle_id, rcomponent, stream->stream_id, rip, rport); + handle->handle_id, rcomponent, pc->stream_id, rip, rport); /* Unless this is libnice >= 0.1.8, we only support UDP... */ if(!strcasecmp(rtransport, "udp")) { c = nice_candidate_new(NICE_CANDIDATE_TYPE_HOST); @@ -793,7 +803,7 @@ int janus_sdp_parse_candidate(void *ice_stream, const char *candidate, int trick } } else if(!strcasecmp(rtype, "srflx")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Adding remote candidate component:%d stream:%d type:srflx %s:%d --> %s:%d \n", - handle->handle_id, rcomponent, stream->stream_id, rrelip, rrelport, rip, rport); + handle->handle_id, rcomponent, pc->stream_id, rrelip, rrelport, rip, rport); /* Unless this is libnice >= 0.1.8, we only support UDP... */ if(!strcasecmp(rtransport, "udp")) { c = nice_candidate_new(NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE); @@ -806,7 +816,7 @@ int janus_sdp_parse_candidate(void *ice_stream, const char *candidate, int trick } } else if(!strcasecmp(rtype, "prflx")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Adding remote candidate component:%d stream:%d type:prflx %s:%d --> %s:%d\n", - handle->handle_id, rcomponent, stream->stream_id, rrelip, rrelport, rip, rport); + handle->handle_id, rcomponent, pc->stream_id, rrelip, rrelport, rip, rport); /* Unless this is libnice >= 0.1.8, we only support UDP... */ if(!strcasecmp(rtransport, "udp")) { c = nice_candidate_new(NICE_CANDIDATE_TYPE_PEER_REFLEXIVE); @@ -819,7 +829,7 @@ int janus_sdp_parse_candidate(void *ice_stream, const char *candidate, int trick } } else if(!strcasecmp(rtype, "relay")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Adding remote candidate component:%d stream:%d type:relay %s:%d --> %s:%d\n", - handle->handle_id, rcomponent, stream->stream_id, rrelip, rrelport, rip, rport); + handle->handle_id, rcomponent, pc->stream_id, rrelip, rrelport, rip, rport); /* We only support UDP/TCP/TLS... */ if(strcasecmp(rtransport, "udp") && strcasecmp(rtransport, "tcp") && strcasecmp(rtransport, "tls")) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Skipping unsupported transport '%s' for media\n", handle->handle_id, rtransport); @@ -829,11 +839,11 @@ int janus_sdp_parse_candidate(void *ice_stream, const char *candidate, int trick } else { /* FIXME What now? */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Unknown remote candidate type:%s for component:%d stream:%d!\n", - handle->handle_id, rtype, rcomponent, stream->stream_id); + handle->handle_id, rtype, rcomponent, pc->stream_id); } if(c != NULL) { c->component_id = rcomponent; - c->stream_id = stream->stream_id; + c->stream_id = pc->stream_id; #ifndef HAVE_LIBNICE_TCP c->transport = NICE_CANDIDATE_TRANSPORT_UDP; #else @@ -887,18 +897,18 @@ int janus_sdp_parse_candidate(void *ice_stream, const char *candidate, int trick nice_candidate_free(c); return 0; } - component->candidates = g_slist_append(component->candidates, c); + pc->candidates = g_slist_append(pc->candidates, c); JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Candidate added to the list! (%u elements for %d/%d)\n", handle->handle_id, - g_slist_length(component->candidates), stream->stream_id, component->component_id); + g_slist_length(pc->candidates), pc->stream_id, pc->component_id); /* Save for the summary, in case we need it */ - component->remote_candidates = g_slist_append(component->remote_candidates, g_strdup(candidate)); + pc->remote_candidates = g_slist_append(pc->remote_candidates, g_strdup(candidate)); /* Notify event handlers */ if(janus_events_is_enabled()) { janus_session *session = (janus_session *)handle->session; json_t *info = json_object(); json_object_set_new(info, "remote-candidate", json_string(candidate)); - json_object_set_new(info, "stream_id", json_integer(stream->stream_id)); - json_object_set_new(info, "component_id", json_integer(component->component_id)); + json_object_set_new(info, "stream_id", json_integer(pc->stream_id)); + json_object_set_new(info, "component_id", json_integer(pc->component_id)); janus_events_notify_handlers(JANUS_EVENT_TYPE_WEBRTC, JANUS_EVENT_SUBTYPE_WEBRTC_RCAND, session->session_id, handle->handle_id, handle->opaque_id, info); } @@ -906,10 +916,10 @@ int janus_sdp_parse_candidate(void *ice_stream, const char *candidate, int trick if(trickle) { if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_START)) { /* This is a trickle candidate and ICE has started, we should process it right away */ - if(!component->process_started) { + if(!pc->process_started) { /* Actually, ICE has JUST started for this component, take care of the candidates we've added so far */ JANUS_LOG(LOG_VERB, "[%"SCNu64"] ICE already started for this component, setting candidates we have up to now\n", handle->handle_id); - janus_ice_setup_remote_candidates(handle, component->stream_id, component->component_id); + janus_ice_setup_remote_candidates(handle, pc->stream_id, pc->component_id); } else { /* Queue the candidate, we'll process it in the loop */ janus_ice_add_remote_candidate(handle, c); @@ -919,10 +929,10 @@ int janus_sdp_parse_candidate(void *ice_stream, const char *candidate, int trick if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_PROCESSING_OFFER)) { janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_START); /* This is a trickle candidate and ICE has started, we should process it right away */ - if(!component->process_started) { + if(!pc->process_started) { /* Actually, ICE has JUST started for this component, take care of the candidates we've added so far */ JANUS_LOG(LOG_VERB, "[%"SCNu64"] SDP processed but ICE not started yet for this component, setting candidates we have up to now\n", handle->handle_id); - janus_ice_setup_remote_candidates(handle, component->stream_id, component->component_id); + janus_ice_setup_remote_candidates(handle, pc->stream_id, pc->component_id); } else { /* Queue the candidate, we'll process it in the loop */ janus_ice_add_remote_candidate(handle, c); @@ -942,16 +952,17 @@ int janus_sdp_parse_candidate(void *ice_stream, const char *candidate, int trick return 0; } -int janus_sdp_parse_ssrc_group(void *ice_stream, const char *group_attr, int video) { - if(ice_stream == NULL || group_attr == NULL) +int janus_sdp_parse_ssrc_group(void *m, const char *group_attr, int video) { + if(m == NULL || group_attr == NULL) return -1; - janus_ice_stream *stream = (janus_ice_stream *)ice_stream; - janus_ice_handle *handle = stream->handle; + janus_ice_peerconnection_medium *medium = (janus_ice_peerconnection_medium *)m; + janus_ice_peerconnection *pc = medium->pc; + janus_ice_handle *handle = pc->handle; if(handle == NULL) return -2; if(!video) /* We only do rtx for video, return */ return 0; - if(stream->rid[0] != NULL) { + if(medium->rid[0] != NULL) { /* Simulcasting is rid-based, don't parse SSRCs for now */ return 0; } @@ -969,23 +980,23 @@ int janus_sdp_parse_ssrc_group(void *ice_stream, const char *group_attr, int vid switch(i) { case 1: first_ssrc = ssrc; - if(stream->video_ssrc_peer_new[0] == ssrc || stream->video_ssrc_peer_new[1] == ssrc - || stream->video_ssrc_peer_new[2] == ssrc) { + if(medium->ssrc_peer_new[0] == ssrc || medium->ssrc_peer_new[1] == ssrc + || medium->ssrc_peer_new[2] == ssrc) { JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Already parsed this SSRC: %"SCNu64" (%s group)\n", handle->handle_id, ssrc, (fid ? "FID" : (sim ? "SIM" : "??"))); } else { - if(stream->video_ssrc_peer_new[0] == 0) { - stream->video_ssrc_peer_new[0] = ssrc; - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC: %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_new[0]); + if(medium->ssrc_peer_new[0] == 0) { + medium->ssrc_peer_new[0] = ssrc; + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC: %"SCNu32"\n", handle->handle_id, medium->ssrc_peer_new[0]); } else { /* We already have a video SSRC: check if rid is involved, and we'll keep track of this for simulcasting */ - if(stream->rid[0]) { - if(stream->video_ssrc_peer_new[1] == 0) { - stream->video_ssrc_peer_new[1] = ssrc; - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-1): %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_new[1]); - } else if(stream->video_ssrc_peer_new[2] == 0) { - stream->video_ssrc_peer_new[2] = ssrc; - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-2): %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_new[2]); + if(medium->rid[0]) { + if(medium->ssrc_peer_new[1] == 0) { + medium->ssrc_peer_new[1] = ssrc; + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-1): %"SCNu32"\n", handle->handle_id, medium->ssrc_peer_new[1]); + } else if(medium->ssrc_peer_new[2] == 0) { + medium->ssrc_peer_new[2] = ssrc; + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-2): %"SCNu32"\n", handle->handle_id, medium->ssrc_peer_new[2]); } else { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Don't know what to do with video SSRC: %"SCNu64"\n", handle->handle_id, ssrc); } @@ -995,21 +1006,21 @@ int janus_sdp_parse_ssrc_group(void *ice_stream, const char *group_attr, int vid break; case 2: if(fid) { - if(stream->video_ssrc_peer_new[0] == first_ssrc && stream->video_ssrc_peer_rtx_new[0] == 0) { - stream->video_ssrc_peer_rtx_new[0] = ssrc; - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (rtx): %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_rtx_new[0]); - } else if(stream->video_ssrc_peer_new[1] == first_ssrc && stream->video_ssrc_peer_rtx_new[1] == 0) { - stream->video_ssrc_peer_rtx_new[1] = ssrc; - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-1 rtx): %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_rtx_new[1]); - } else if(stream->video_ssrc_peer_new[2] == first_ssrc && stream->video_ssrc_peer_rtx_new[2] == 0) { - stream->video_ssrc_peer_rtx_new[2] = ssrc; - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-2 rtx): %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_rtx_new[2]); + if(medium->ssrc_peer_new[0] == first_ssrc && medium->ssrc_peer_rtx_new[0] == 0) { + medium->ssrc_peer_rtx_new[0] = ssrc; + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (rtx): %"SCNu32"\n", handle->handle_id, medium->ssrc_peer_rtx_new[0]); + } else if(medium->ssrc_peer_new[1] == first_ssrc && medium->ssrc_peer_rtx_new[1] == 0) { + medium->ssrc_peer_rtx_new[1] = ssrc; + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-1 rtx): %"SCNu32"\n", handle->handle_id, medium->ssrc_peer_rtx_new[1]); + } else if(medium->ssrc_peer_new[2] == first_ssrc && medium->ssrc_peer_rtx_new[2] == 0) { + medium->ssrc_peer_rtx_new[2] = ssrc; + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-2 rtx): %"SCNu32"\n", handle->handle_id, medium->ssrc_peer_rtx_new[2]); } else { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Don't know what to do with rtx SSRC: %"SCNu64"\n", handle->handle_id, ssrc); } } else if(sim) { - stream->video_ssrc_peer_new[1] = ssrc; - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-1): %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_new[1]); + medium->ssrc_peer_new[1] = ssrc; + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-1): %"SCNu32"\n", handle->handle_id, medium->ssrc_peer_new[1]); } else { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Don't know what to do with SSRC: %"SCNu64"\n", handle->handle_id, ssrc); } @@ -1018,8 +1029,8 @@ int janus_sdp_parse_ssrc_group(void *ice_stream, const char *group_attr, int vid if(fid) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Found one too many retransmission SSRC (rtx): %"SCNu64"\n", handle->handle_id, ssrc); } else if(sim) { - stream->video_ssrc_peer_new[2] = ssrc; - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-2): %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_new[2]); + medium->ssrc_peer_new[2] = ssrc; + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-2): %"SCNu32"\n", handle->handle_id, medium->ssrc_peer_new[2]); } else { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Don't know what to do with SSRC: %"SCNu64"\n", handle->handle_id, ssrc); } @@ -1037,34 +1048,29 @@ int janus_sdp_parse_ssrc_group(void *ice_stream, const char *group_attr, int vid return 0; } -int janus_sdp_parse_ssrc(void *ice_stream, const char *ssrc_attr, int video) { - if(ice_stream == NULL || ssrc_attr == NULL) +int janus_sdp_parse_ssrc(void *m, const char *ssrc_attr, int video) { + if(m == NULL || ssrc_attr == NULL) return -1; - janus_ice_stream *stream = (janus_ice_stream *)ice_stream; - janus_ice_handle *handle = stream->handle; + janus_ice_peerconnection_medium *medium = (janus_ice_peerconnection_medium *)m; + janus_ice_peerconnection *pc = medium->pc; + janus_ice_handle *handle = pc->handle; if(handle == NULL) return -2; guint64 ssrc = g_ascii_strtoull(ssrc_attr, NULL, 0); if(ssrc == 0 || ssrc > G_MAXUINT32) return -3; - if(video) { - if(stream->rid[0] != NULL) { - /* Simulcasting is rid-based, only keep track of a single SSRC for fallback */ - if(stream->video_ssrc_peer_temp == 0) { - stream->video_ssrc_peer_temp = ssrc; - JANUS_LOG(LOG_WARN, "[%"SCNu64"] Peer video fallback SSRC: %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_temp); - } - return 0; - } - if(stream->video_ssrc_peer_new[0] == 0) { - stream->video_ssrc_peer_new[0] = ssrc; - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC: %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_new[0]); - } - } else { - if(stream->audio_ssrc_peer_new == 0) { - stream->audio_ssrc_peer_new = ssrc; - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer audio SSRC: %"SCNu32"\n", handle->handle_id, stream->audio_ssrc_peer_new); + if(medium->rid[0] != NULL) { + /* Simulcasting is rid-based, only keep track of a single SSRC for fallback */ + if(medium->ssrc_peer_temp == 0) { + medium->ssrc_peer_temp = ssrc; + JANUS_LOG(LOG_WARN, "[%"SCNu64"] Peer video fallback SSRC: %"SCNu32"\n", handle->handle_id, medium->ssrc_peer_temp); } + return 0; + } + if(medium->ssrc_peer_new[0] == 0) { + medium->ssrc_peer_new[0] = ssrc; + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer %s SSRC: %"SCNu32"\n", + handle->handle_id, video ? "video" : "audio", medium->ssrc_peer_new[0]); } return 0; } @@ -1105,10 +1111,10 @@ int janus_sdp_anonymize(janus_sdp *anon) { janus_sdp_mline *m = (janus_sdp_mline *)temp->data; if(m->type == JANUS_SDP_AUDIO && m->port > 0) { audio++; - m->port = audio == 1 ? 9 : 0; + m->port = 9; } else if(m->type == JANUS_SDP_VIDEO && m->port > 0) { video++; - m->port = video == 1 ? 9 : 0; + m->port = 9; } else if(m->type == JANUS_SDP_APPLICATION && m->port > 0) { if(m->proto != NULL && (!strcasecmp(m->proto, "DTLS/SCTP") || !strcasecmp(m->proto, "UDP/DTLS/SCTP"))) { data++; @@ -1196,7 +1202,7 @@ int janus_sdp_anonymize(janus_sdp *anon) { tempA = purged_ptypes; while(tempA) { int ptype = GPOINTER_TO_INT(tempA->data); - janus_sdp_remove_payload_type(anon, ptype); + janus_sdp_remove_payload_type(anon, m->index, ptype); tempA = tempA->next; } g_list_free(purged_ptypes); @@ -1216,9 +1222,10 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) { if(ice_handle == NULL || anon == NULL) return NULL; janus_ice_handle *handle = (janus_ice_handle *)ice_handle; - janus_ice_stream *stream = handle->stream; - if(stream == NULL) + janus_ice_peerconnection *pc = handle->pc; + if(pc == NULL) return NULL; + janus_ice_peerconnection_medium *medium = NULL; char *rtp_profile = handle->rtp_profile ? handle->rtp_profile : (char *)"UDP/TLS/RTP/SAVPF"; if(!janus_is_webrtc_encryption_enabled()) rtp_profile = (char *)"RTP/AVPF"; @@ -1241,43 +1248,22 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) { g_free(anon->c_addr); anon->c_addr = NULL; /* bundle: add new global attribute */ - char buffer[2048], buffer_part[512]; + char buffer[JANUS_BUFSIZE], buffer_part[512]; buffer[0] = '\0'; buffer_part[0] = '\0'; g_snprintf(buffer, sizeof(buffer), "BUNDLE"); /* Iterate on available media */ - int audio = 0; - int video = 0; #ifdef HAVE_SCTP int data = 0; #endif GList *temp = anon->m_lines; while(temp) { janus_sdp_mline *m = (janus_sdp_mline *)temp->data; - if(m->type == JANUS_SDP_AUDIO) { - audio++; - if(audio == 1 && m->port > 0) { - g_snprintf(buffer_part, sizeof(buffer_part), - " %s", handle->audio_mid ? handle->audio_mid : "audio"); - g_strlcat(buffer, buffer_part, sizeof(buffer)); - } - } else if(m->type == JANUS_SDP_VIDEO) { - video++; - if(video == 1 && m->port > 0) { - g_snprintf(buffer_part, sizeof(buffer_part), - " %s", handle->video_mid ? handle->video_mid : "video"); - g_strlcat(buffer, buffer_part, sizeof(buffer)); - } -#ifdef HAVE_SCTP - } else if(m->type == JANUS_SDP_APPLICATION) { - if(m->proto && (!strcasecmp(m->proto, "DTLS/SCTP") || !strcasecmp(m->proto, "UDP/DTLS/SCTP"))) - data++; - if(data == 1 && m->port > 0) { - g_snprintf(buffer_part, sizeof(buffer_part), - " %s", handle->data_mid ? handle->data_mid : "data"); - g_strlcat(buffer, buffer_part, sizeof(buffer)); - } -#endif + /* Find the internal medium instance */ + medium = g_hash_table_lookup(pc->media, GINT_TO_POINTER(m->index)); + if(medium && m->port > 0) { + g_snprintf(buffer_part, sizeof(buffer_part), " %s", medium->mid); + g_strlcat(buffer, buffer_part, sizeof(buffer)); } temp = temp->next; } @@ -1285,6 +1271,14 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) { GList *first = anon->attributes; janus_sdp_attribute *a = janus_sdp_attribute_create("group", "%s", buffer); anon->attributes = g_list_insert_before(anon->attributes, first, a); + /* Advertise trickle support */ + a = janus_sdp_attribute_create("ice-options", "trickle"); + anon->attributes = g_list_insert_before(anon->attributes, first, a); + if(janus_is_webrtc_encryption_enabled()) { + /* We put the fingerprint in the global attributes */ + a = janus_sdp_attribute_create("fingerprint", "sha-256 %s", janus_dtls_get_local_fingerprint()); + anon->attributes = g_list_insert_before(anon->attributes, first, a); + } /* msid-semantic: add new global attribute */ a = janus_sdp_attribute_create("msid-semantic", " WMS janus"); anon->attributes = g_list_insert_before(anon->attributes, first, a); @@ -1295,8 +1289,6 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) { anon->attributes = g_list_insert_before(anon->attributes, first, a); } /* Media lines now */ - audio = 0; - video = 0; #ifdef HAVE_SCTP data = 0; #endif @@ -1304,6 +1296,14 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) { while(temp) { janus_sdp_mline *m = (janus_sdp_mline *)temp->data; first = m->attributes; + /* Find the internal medium instance */ + medium = g_hash_table_lookup(pc->media, GINT_TO_POINTER(m->index)); + if(!medium) { + /* TODO We don't have it, which should never happen! */ + JANUS_LOG(LOG_WARN, "[%"SCNu64"] No medium? Expect trouble!\n", handle->handle_id); + temp = temp->next; + continue; + } /* Overwrite RTP profile for audio and video */ if(m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) { g_free(m->proto); @@ -1314,94 +1314,54 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) { m->c_ipv4 = ipv4; m->c_addr = g_strdup(janus_get_public_ip(0)); /* Check if we need to refuse the media or not */ - if(m->type == JANUS_SDP_AUDIO) { - audio++; - /* Audio */ - if(audio > 1) { - JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping audio line (we have one already)\n", handle->handle_id); - m->port = 0; - } + if(m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) { + /* Audio/Video */ if(m->port == 0) { m->direction = JANUS_SDP_INACTIVE; - stream->audio_ssrc = 0; + medium->ssrc = 0; } - if(audio == 1) { - switch(m->direction) { - case JANUS_SDP_INACTIVE: - stream->audio_send = FALSE; - stream->audio_recv = FALSE; - break; - case JANUS_SDP_SENDONLY: - stream->audio_send = TRUE; - stream->audio_recv = FALSE; - break; - case JANUS_SDP_RECVONLY: - stream->audio_send = FALSE; - stream->audio_recv = TRUE; - break; - case JANUS_SDP_SENDRECV: - case JANUS_SDP_DEFAULT: - default: - stream->audio_send = TRUE; - stream->audio_recv = TRUE; - break; - } - } - } else if(m->type == JANUS_SDP_VIDEO) { - video++; - /* Video */ - if(video > 1) { - JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping video line (we have one already)\n", handle->handle_id); - m->port = 0; - } - if(m->port == 0) { - m->direction = JANUS_SDP_INACTIVE; - stream->video_ssrc = 0; + switch(m->direction) { + case JANUS_SDP_INACTIVE: + medium->send = FALSE; + medium->recv = FALSE; + break; + case JANUS_SDP_SENDONLY: + medium->send = TRUE; + medium->recv = FALSE; + break; + case JANUS_SDP_RECVONLY: + medium->send = FALSE; + medium->recv = TRUE; + break; + case JANUS_SDP_SENDRECV: + case JANUS_SDP_DEFAULT: + default: + medium->send = TRUE; + medium->recv = TRUE; + break; } - if(video == 1) { - switch(m->direction) { - case JANUS_SDP_INACTIVE: - stream->video_send = FALSE; - stream->video_recv = FALSE; - break; - case JANUS_SDP_SENDONLY: - stream->video_send = TRUE; - stream->video_recv = FALSE; - break; - case JANUS_SDP_RECVONLY: - stream->video_send = FALSE; - stream->video_recv = TRUE; - break; - case JANUS_SDP_SENDRECV: - case JANUS_SDP_DEFAULT: - default: - stream->video_send = TRUE; - stream->video_recv = TRUE; - break; - } - if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX)) { - /* Add RFC4588 stuff */ - if(stream->rtx_payload_types && g_hash_table_size(stream->rtx_payload_types) > 0) { - janus_sdp_attribute *a = NULL; - GList *ptypes = g_list_copy(m->ptypes), *tempP = ptypes; - while(tempP) { - int ptype = GPOINTER_TO_INT(tempP->data); - int rtx_ptype = GPOINTER_TO_INT(g_hash_table_lookup(stream->rtx_payload_types, GINT_TO_POINTER(ptype))); - if(rtx_ptype > 0) { - m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(rtx_ptype)); - a = janus_sdp_attribute_create("rtpmap", "%d rtx/90000", rtx_ptype); - m->attributes = g_list_append(m->attributes, a); - a = janus_sdp_attribute_create("fmtp", "%d apt=%d", rtx_ptype, ptype); - m->attributes = g_list_append(m->attributes, a); - } - tempP = tempP->next; + if(medium->do_nacks && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX)) { + /* Add RFC4588 stuff */ + if(medium->rtx_payload_types && g_hash_table_size(medium->rtx_payload_types) > 0) { + janus_sdp_attribute *a = NULL; + GList *ptypes = g_list_copy(m->ptypes), *tempP = ptypes; + while(tempP) { + int ptype = GPOINTER_TO_INT(tempP->data); + int rtx_ptype = GPOINTER_TO_INT(g_hash_table_lookup(medium->rtx_payload_types, GINT_TO_POINTER(ptype))); + if(rtx_ptype > 0) { + m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(rtx_ptype)); + a = janus_sdp_attribute_create("rtpmap", "%d rtx/90000", rtx_ptype); + m->attributes = g_list_append(m->attributes, a); + a = janus_sdp_attribute_create("fmtp", "%d apt=%d", rtx_ptype, ptype); + m->attributes = g_list_append(m->attributes, a); } - g_list_free(ptypes); + tempP = tempP->next; } + g_list_free(ptypes); } } -#ifdef HAVE_SCTP } else if(m->type == JANUS_SDP_APPLICATION) { +#ifdef HAVE_SCTP /* Is this SCTP for DataChannels? */ if(m->port > 0 && (!strcasecmp(m->proto, "DTLS/SCTP") || !strcasecmp(m->proto, "UDP/DTLS/SCTP"))) { /* Yep */ @@ -1420,6 +1380,12 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) { temp = temp->next; continue; } +#else + JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping unsupported application media line...\n", handle->handle_id); + m->port = 0; + m->direction = JANUS_SDP_INACTIVE; + temp = temp->next; + continue; #endif } else { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping disabled/unsupported media line...\n", handle->handle_id); @@ -1428,15 +1394,12 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) { temp = temp->next; continue; } - /* a=mid:(audio|video|data) */ - if(m->type == JANUS_SDP_AUDIO && audio == 1) { - a = janus_sdp_attribute_create("mid", "%s", handle->audio_mid); - m->attributes = g_list_insert_before(m->attributes, first, a); - } else if(m->type == JANUS_SDP_VIDEO && video == 1) { - a = janus_sdp_attribute_create("mid", "%s", handle->video_mid); + /* a=mid */ + if(medium->mid) { + a = janus_sdp_attribute_create("mid", "%s", medium->mid); m->attributes = g_list_insert_before(m->attributes, first, a); -#ifdef HAVE_SCTP - } else if(m->type == JANUS_SDP_APPLICATION && data == 1) { + } + if(m->type == JANUS_SDP_APPLICATION) { if(!strcasecmp(m->proto, "UDP/DTLS/SCTP")) janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_NEW_DATACHAN_SDP); if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_NEW_DATACHAN_SDP)) { @@ -1446,18 +1409,14 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) { a = janus_sdp_attribute_create("sctp-port", "5000"); m->attributes = g_list_insert_before(m->attributes, first, a); } - a = janus_sdp_attribute_create("mid", "%s", handle->data_mid); - m->attributes = g_list_insert_before(m->attributes, first, a); -#endif - } - if(m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) { + } else if(m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) { a = janus_sdp_attribute_create("rtcp-mux", NULL); m->attributes = g_list_insert_before(m->attributes, first, a); } /* ICE ufrag and pwd, DTLS fingerprint setup and connection a= */ gchar *ufrag = NULL; gchar *password = NULL; - nice_agent_get_local_credentials(handle->agent, stream->stream_id, &ufrag, &password); + nice_agent_get_local_credentials(handle->agent, pc->stream_id, &ufrag, &password); a = janus_sdp_attribute_create("ice-ufrag", "%s", ufrag); m->attributes = g_list_insert_before(m->attributes, first, a); a = janus_sdp_attribute_create("ice-pwd", "%s", password); @@ -1467,71 +1426,48 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) { a = janus_sdp_attribute_create("ice-options", "trickle"); m->attributes = g_list_insert_before(m->attributes, first, a); if(janus_is_webrtc_encryption_enabled()) { - a = janus_sdp_attribute_create("fingerprint", "sha-256 %s", janus_dtls_get_local_fingerprint()); - m->attributes = g_list_insert_before(m->attributes, first, a); - a = janus_sdp_attribute_create("setup", "%s", janus_get_dtls_srtp_role(offer ? JANUS_DTLS_ROLE_ACTPASS : stream->dtls_role)); + a = janus_sdp_attribute_create("setup", "%s", janus_get_dtls_srtp_role(offer ? JANUS_DTLS_ROLE_ACTPASS : pc->dtls_role)); m->attributes = g_list_insert_before(m->attributes, first, a); } /* Add last attributes, rtcp and ssrc (msid) */ - if(m->type == JANUS_SDP_VIDEO && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX) && + if(medium->ssrc_rtx > 0 && m->type == JANUS_SDP_VIDEO && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX) && (m->direction == JANUS_SDP_DEFAULT || m->direction == JANUS_SDP_SENDRECV || m->direction == JANUS_SDP_SENDONLY)) { /* Add FID group to negotiate the RFC4588 stuff */ - a = janus_sdp_attribute_create("ssrc-group", "FID %"SCNu32" %"SCNu32, stream->video_ssrc, stream->video_ssrc_rtx); + a = janus_sdp_attribute_create("ssrc-group", "FID %"SCNu32" %"SCNu32, medium->ssrc, medium->ssrc_rtx); m->attributes = g_list_append(m->attributes, a); } - if(m->type == JANUS_SDP_AUDIO) { - a = janus_sdp_attribute_create("msid", "janus janusa0"); - m->attributes = g_list_append(m->attributes, a); - a = janus_sdp_attribute_create("ssrc", "%"SCNu32" cname:janus", stream->audio_ssrc); - m->attributes = g_list_append(m->attributes, a); - a = janus_sdp_attribute_create("ssrc", "%"SCNu32" msid:janus janusa0", stream->audio_ssrc); - m->attributes = g_list_append(m->attributes, a); - a = janus_sdp_attribute_create("ssrc", "%"SCNu32" mslabel:janus", stream->audio_ssrc); - m->attributes = g_list_append(m->attributes, a); - a = janus_sdp_attribute_create("ssrc", "%"SCNu32" label:janusa0", stream->audio_ssrc); - m->attributes = g_list_append(m->attributes, a); - } else if(m->type == JANUS_SDP_VIDEO) { - a = janus_sdp_attribute_create("msid", "janus janusv0"); - m->attributes = g_list_append(m->attributes, a); - a = janus_sdp_attribute_create("ssrc", "%"SCNu32" cname:janus", stream->video_ssrc); - m->attributes = g_list_append(m->attributes, a); - a = janus_sdp_attribute_create("ssrc", "%"SCNu32" msid:janus janusv0", stream->video_ssrc); - m->attributes = g_list_append(m->attributes, a); - a = janus_sdp_attribute_create("ssrc", "%"SCNu32" mslabel:janus", stream->video_ssrc); - m->attributes = g_list_append(m->attributes, a); - a = janus_sdp_attribute_create("ssrc", "%"SCNu32" label:janusv0", stream->video_ssrc); + if(m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) { + a = janus_sdp_attribute_create("msid", "janus janus%s", medium->mid); m->attributes = g_list_append(m->attributes, a); - if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX)) { - /* Add rtx SSRC group to negotiate the RFC4588 stuff */ - a = janus_sdp_attribute_create("ssrc", "%"SCNu32" cname:janus", stream->video_ssrc_rtx); - m->attributes = g_list_append(m->attributes, a); - a = janus_sdp_attribute_create("ssrc", "%"SCNu32" msid:janus janusv0", stream->video_ssrc_rtx); - m->attributes = g_list_append(m->attributes, a); - a = janus_sdp_attribute_create("ssrc", "%"SCNu32" mslabel:janus", stream->video_ssrc_rtx); - m->attributes = g_list_append(m->attributes, a); - a = janus_sdp_attribute_create("ssrc", "%"SCNu32" label:janusv0", stream->video_ssrc_rtx); + if(medium->ssrc > 0) { + a = janus_sdp_attribute_create("ssrc", "%"SCNu32" cname:janus", medium->ssrc); m->attributes = g_list_append(m->attributes, a); + if(m->type == JANUS_SDP_VIDEO && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX)) { + /* Add rtx SSRC group to negotiate the RFC4588 stuff */ + a = janus_sdp_attribute_create("ssrc", "%"SCNu32" cname:janus", medium->ssrc_rtx); + m->attributes = g_list_append(m->attributes, a); + } } } /* FIXME If the peer is Firefox and is negotiating simulcasting, add the rid attributes */ - if(m->type == JANUS_SDP_VIDEO && stream->rid[0] != NULL) { + if(m->type == JANUS_SDP_VIDEO && medium->rid[0] != NULL) { char rids[50]; rids[0] = '\0'; int i=0, index=0; for(i=2; i>=0; i--) { - index = (stream->rids_hml ? i : (2-i)); - if(stream->rid[index] == NULL) + index = (medium->rids_hml ? i : (2-i)); + if(medium->rid[index] == NULL) continue; - a = janus_sdp_attribute_create("rid", "%s recv", stream->rid[index]); + a = janus_sdp_attribute_create("rid", "%s recv", medium->rid[index]); m->attributes = g_list_append(m->attributes, a); if(strlen(rids) == 0) { - g_strlcat(rids, stream->rid[index], sizeof(rids)); + g_strlcat(rids, medium->rid[index], sizeof(rids)); } else { g_strlcat(rids, ";", sizeof(rids)); - g_strlcat(rids, stream->rid[index], sizeof(rids)); + g_strlcat(rids, medium->rid[index], sizeof(rids)); } } - if(stream->legacy_rid) { + if(medium->legacy_rid) { a = janus_sdp_attribute_create("simulcast", " recv rid=%s", rids); } else { a = janus_sdp_attribute_create("simulcast", "recv %s", rids); @@ -1540,7 +1476,7 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) { } if(!janus_ice_is_full_trickle_enabled()) { /* And now the candidates (but only if we're half-trickling) */ - janus_ice_candidates_to_sdp(handle, m, stream->stream_id, 1); + janus_ice_candidates_to_sdp(handle, m, pc->stream_id, 1); /* Since we're half-trickling, we need to notify the peer that these are all the * candidates we have for this media stream, via an end-of-candidates attribute: * https://tools.ietf.org/html/draft-ietf-mmusic-trickle-ice-02#section-4.1 */ diff --git a/sdp.h b/sdp.h index 817af19bf6..f34818eccd 100644 --- a/sdp.h +++ b/sdp.h @@ -45,42 +45,46 @@ * @returns The Janus SDP object in case of success, NULL in case the SDP is invalid */ janus_sdp *janus_sdp_preparse(void *handle, const char *jsep_sdp, char *error_str, size_t errlen, int *audio, int *video, int *data); -/*! \brief Method to process a parsed session description +/*! \brief Method to process a remote parsed session description * \details This method will process a session description coming from a peer, and set up the ICE candidates accordingly - * \note While this method can handle SDP updates, renegotiations are currently - * limited to updates to the media direction of existing media streams - * (e.g., sendrecv to recvonly) and ICE restarts. Adding/removing streams - * and supporting multiple streams in the same PeerConnection are still WIP. * @param[in] handle Opaque pointer to the ICE handle this session description will modify * @param[in] sdp The Janus SDP object to process * @param[in] rids_hml Whether the order of rids in the SDP, if present, will be h-m-l (TRUE) or l-m-h (FALSE) * @param[in] update Whether this SDP is an update to an existing session or not * @returns 0 in case of success, -1 in case of an error */ -int janus_sdp_process(void *handle, janus_sdp *sdp, gboolean rids_hml, gboolean update); +int janus_sdp_process_remote(void *handle, janus_sdp *sdp, gboolean rids_hml, gboolean update); + +/*! \brief Method to process a local parsed session description + * \details This method will process a session description coming from a plugin, and set up the ICE candidates accordingly + * @param[in] handle Opaque pointer to the ICE handle this session description will modify + * @param[in] sdp The Janus SDP object to process + * @param[in] update Whether this SDP is an update to an existing session or not + * @returns 0 in case of success, -1 in case of an error */ +int janus_sdp_process_local(void *handle, janus_sdp *sdp, gboolean update); /*! \brief Method to parse a single candidate * \details This method will parse a single remote candidate provided by a peer, whether it is trickling or not - * @param[in] stream Opaque pointer to the ICE stream this candidate refers to + * @param[in] pc Opaque pointer to the WebRTC PeerConnection this candidate refers to * @param[in] candidate The remote candidate to process * @param[in] trickle Whether this is a trickle candidate, or coming from the SDP * @returns 0 in case of success, a non-zero integer in case of an error */ -int janus_sdp_parse_candidate(void *stream, const char *candidate, int trickle); +int janus_sdp_parse_candidate(void *pc, const char *candidate, int trickle); /*! \brief Method to parse a SSRC group attribute * \details This method will parse a SSRC group attribute, and set the parsed values for the peer - * @param[in] stream Opaque pointer to the ICE stream this candidate refers to + * @param[in] medium Opaque pointer to the medium this candidate refers to * @param[in] group_attr The SSRC group attribute value to parse * @param[in] video Whether this is video-related or not * @returns 0 in case of success, a non-zero integer in case of an error */ -int janus_sdp_parse_ssrc_group(void *stream, const char *group_attr, int video); +int janus_sdp_parse_ssrc_group(void *medium, const char *group_attr, int video); /*! \brief Method to parse a SSRC attribute * \details This method will parse a SSRC attribute, and set it for the peer - * @param[in] stream Opaque pointer to the ICE stream this candidate refers to + * @param[in] medium Opaque pointer to the medium this candidate refers to * @param[in] ssrc_attr The SSRC attribute value to parse * @param[in] video Whether this is a video SSRC or not * @returns 0 in case of success, a non-zero integer in case of an error */ -int janus_sdp_parse_ssrc(void *stream, const char *ssrc_attr, int video); +int janus_sdp_parse_ssrc(void *medium, const char *ssrc_attr, int video); /*! \brief Method to strip/anonymize a session description * @param[in,out] sdp The Janus SDP description object to strip/anonymize From b0525de2938d27df9d03689a6a63192464e2a469 Mon Sep 17 00:00:00 2001 From: Lorenzo Miniero Date: Fri, 5 Jun 2020 11:03:50 +0200 Subject: [PATCH 02/82] Aligned to latest changes in master --- Makefile.am | 4 +- conf/janus.plugin.streaming.jcfg.sample.in | 49 + plugins/janus_nosip.c | 15 +- plugins/janus_recordplay.c | 25 + plugins/janus_sip.c | 15 +- plugins/janus_streaming.c | 4574 ++++++++++------- plugins/janus_videocall.c | 21 +- plugins/janus_videoroom.c | 29 +- ...test_gstreamer_1.sh => test_gstreamer1.sh} | 0 .../streams/test_gstreamer1_multistream.sh | 16 + plugins/streams/test_gstreamer_multistream.sh | 28 + utils.h | 1 + 12 files changed, 2772 insertions(+), 2005 deletions(-) rename plugins/streams/{test_gstreamer_1.sh => test_gstreamer1.sh} (100%) create mode 100755 plugins/streams/test_gstreamer1_multistream.sh create mode 100755 plugins/streams/test_gstreamer_multistream.sh diff --git a/Makefile.am b/Makefile.am index aab90bd297..7b8a3f68e7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -482,7 +482,9 @@ stream_DATA += \ plugins/streams/music.mulaw \ plugins/streams/radio.alaw \ plugins/streams/test_gstreamer.sh \ - plugins/streams/test_gstreamer_1.sh + plugins/streams/test_gstreamer1.sh \ + plugins/streams/test_gstreamer_multistream.sh \ + plugins/streams/test_gstreamer1_multistream.sh EXTRA_DIST += \ conf/janus.plugin.streaming.jcfg.sample.in \ $(stream_DATA) diff --git a/conf/janus.plugin.streaming.jcfg.sample.in b/conf/janus.plugin.streaming.jcfg.sample.in index 7eb058bea5..f31d1b396a 100644 --- a/conf/janus.plugin.streaming.jcfg.sample.in +++ b/conf/janus.plugin.streaming.jcfg.sample.in @@ -130,6 +130,55 @@ rtp-sample: { secret = "adminpwd" } +# +# This is a better example that uses the new settings to configure a live +# mountpoint to send multiple streams of the same type at the same time: +# that is, not simulcasting, but different streams (e.g., two audio +# streams and two video streams). To do so, you don't set the audio, +# video and data properties inline, but use an array of properties instead, +# each identifying a single stream to add, that will then translate to +# a dedicated m-line in the SDP. For each stream, you specify the type, +# a unique ID (mid), and can provide a short description (label) so that +# the client side can know what's what when rendering the streams. Notice +# how the port/pt/rtpmap/fmtp/etc. stuff is called just like that, without +# any audio/video/data prefix: in fact, each media stream can be configured +# the same way, and it's the type that allows us to differentiate them. +# As such, you can use the same approach for creating regular mountpoints +# as well (e.g., 1 audio and 1 video) in a much clearer, and cleaner, way. +# +multistream-test: { + type = "rtp" + id = 123 + description = "Multistream test (1 audio, 2 video)" + media = ( + { + type = "audio" + mid = "a" + label = "Audio stream" + port = 5102 + pt = 111 + rtpmap = "opus/48000/2" + }, + { + type = "video" + mid = "v1" + label = "Video stream #1" + port = 5104 + pt = 100 + rtpmap = "VP8/90000" + }, + { + type = "video" + mid = "v2" + label = "Video stream #2" + port = 5106 + pt = 100 + rtpmap = "VP8/90000" + } + ) + secret = "adminpwd" +} + # # This is a sample of the file-based streaming support. Specifically, # this simulates a radio broadcast by streaming (in a loop) raw a-Law diff --git a/plugins/janus_nosip.c b/plugins/janus_nosip.c index 73074b13cf..06031d430f 100644 --- a/plugins/janus_nosip.c +++ b/plugins/janus_nosip.c @@ -1471,11 +1471,18 @@ static void *janus_nosip_handler(void *data) { } /* If the user negotiated simulcasting, just stick with the base substream */ json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast"); - if(msg_simulcast) { + if(msg_simulcast && json_array_size(msg_simulcast) > 0) { JANUS_LOG(LOG_WARN, "Client negotiated simulcasting which we don't do here, falling back to base substream...\n"); - json_t *s = json_object_get(msg_simulcast, "ssrcs"); - if(s && json_array_size(s) > 0) - session->media.simulcast_ssrc = json_integer_value(json_array_get(s, 0)); + size_t i = 0; + for(i=0; i 0) + session->media.simulcast_ssrc = json_integer_value(json_array_get(s, 0)); + session->media.simulcast_ssrc = json_integer_value(json_object_get(s, "ssrc-0")); + /* FIXME We're stopping at the first item, there may be more */ + break; + } } /* Send the barebone SDP back */ result = json_object(); diff --git a/plugins/janus_recordplay.c b/plugins/janus_recordplay.c index 38faacc2e9..132b9e458f 100644 --- a/plugins/janus_recordplay.c +++ b/plugins/janus_recordplay.c @@ -1729,6 +1729,31 @@ static void *janus_recordplay_handler(void *data) { JANUS_LOG(LOG_VERB, "Going to answer this SDP:\n%s\n", sdp); /* If the user negotiated simulcasting, prepare it accordingly */ json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast"); + if(msg_simulcast && json_array_size(msg_simulcast) > 0) { + size_t i = 0; + for(i=0; issrc, session->rid); + session->sim_context.rid_ext_id = rid_ext_id; + session->sim_context.framemarking_ext_id = framemarking_ext_id; + session->sim_context.substream_target = 2; /* Let's aim for the highest quality */ + session->sim_context.templayer_target = 2; /* Let's aim for all temporal layers */ + if(rec->vcodec != JANUS_VIDEOCODEC_VP8 && rec->vcodec != JANUS_VIDEOCODEC_H264) { + /* VP8 r H.264 were not negotiated, if simulcasting was enabled then disable it here */ + int i=0; + for(i=0; i<3; i++) { + session->ssrc[i] = 0; + g_free(session->rid[i]); + session->rid[i] = NULL; + } + } + /* FIXME We're stopping at the first item, there may be more */ + break; + } + } if(msg_simulcast) { JANUS_LOG(LOG_VERB, "Recording client negotiated simulcasting\n"); int rid_ext_id = -1, framemarking_ext_id = -1; diff --git a/plugins/janus_sip.c b/plugins/janus_sip.c index 93014194e5..49c423ee8b 100644 --- a/plugins/janus_sip.c +++ b/plugins/janus_sip.c @@ -3468,11 +3468,18 @@ static void *janus_sip_handler(void *data) { } /* If the user negotiated simulcasting, just stick with the base substream */ json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast"); - if(msg_simulcast) { + if(msg_simulcast && json_array_size(msg_simulcast) > 0) { JANUS_LOG(LOG_WARN, "Client negotiated simulcasting which we don't do here, falling back to base substream...\n"); - json_t *s = json_object_get(msg_simulcast, "ssrcs"); - if(s && json_array_size(s) > 0) - session->media.simulcast_ssrc = json_integer_value(json_array_get(s, 0)); + size_t i = 0; + for(i=0; i 0) + session->media.simulcast_ssrc = json_integer_value(json_array_get(s, 0)); + session->media.simulcast_ssrc = json_integer_value(json_object_get(s, "ssrc-0")); + /* FIXME We're stopping at the first item, there may be more */ + break; + } } /* Check if there are new credentials to authenticate the INVITE */ if(authuser) { diff --git a/plugins/janus_streaming.c b/plugins/janus_streaming.c index 2102b74f7a..5881fc3137 100644 --- a/plugins/janus_streaming.c +++ b/plugins/janus_streaming.c @@ -26,7 +26,7 @@ * as well. * * For what concerns type 3., instead, the plugin is configured - * to listen on a couple of ports for RTP: this means that the plugin + * to listen on a few ports for RTP: this means that the plugin * is implemented to receive RTP on those ports and relay them to all * peers attached to that stream. Any tool that can generate audio/video * RTP streams and specify a destination is good for the purpose: the @@ -44,7 +44,7 @@ * * Streams to make available are listed in the plugin configuration file. * A pre-filled configuration file is provided in \c conf/janus.plugin.streaming.jcfg - * and includes a stream of every type. + * and includes some examples you can start from. * * To add more streams or modify the existing ones, you can use the following * syntax: @@ -108,6 +108,7 @@ collision = in case of collision (more than one SSRC hitting the same port), the will discard incoming RTP packets with a new SSRC unless this many milliseconds passed, which would then change the current SSRC (0=disabled) dataport = local port for receiving data messages to relay +datamcast = multicast group for receiving data messages, if any dataiface = network interface or IP address to bind to, if any (binds to all otherwise) datatype = text|binary (type of data this mountpoint will relay, default=text) databuffermsg = true|false (whether the plugin should store the latest @@ -141,6 +142,64 @@ rtsp_pwd = RTSP authorization password, if needed rtsp_failcheck = whether an error should be returned if connecting to the RTSP server fails (default=true) rtspiface = network interface IP address or device name to listen on when receiving RTSP streams \endverbatim + * + * Notice that attributes like \c audioport or \c videopt only make sense + * when you're creating a mountpoint with a single audio and/or video stream, + * as the plugin in that case assumes that limitation is fine by you. In + * case you're interested in creating multistream mountpoints, that is + * mountpoints that can contain more than one audio and/or video stream + * at the same time, you HAVE to use a different syntax. Specifically, + * you'll need to use a \c media array/list, containing the different + * streams, in the right order, that you want to make available: each + * stream will then need to contain the related info, e.g., port to bind + * to, type of media, rtpmap and so on. An example is provided below: + * +\verbatim +multistream-test: { + type = "rtp" + id = 123 + description = "Multistream test (1 audio, 2 video)" + media = ( + { + type = "audio" + mid = "a" + label = "Audio stream" + port = 5102 + pt = 111 + rtpmap = "opus/48000/2" + }, + { + type = "video" + mid = "v1" + label = "Video stream #1" + port = 5104 + pt = 100 + rtpmap = "VP8/90000" + }, + { + type = "video" + mid = "v2" + label = "Video stream #2" + port = 5106 + pt = 100 + rtpmap = "VP8/90000" + } + ) +} +\endverbatim + * + * In the above example, we're creating a mountpoint with a single audio + * stream and two different video streams: each stream has a unique \c mid + * (that you MUST provide) which is what will be used for the SDP offer + * to send to viewers, and their unique configuration properties. As you + * can see, it's much cleaner in the way you create and configure + * mountpoints: there's no hardcoded audio/video prefix for the name of + * properties, you configure media streams the same way and just add them + * to a list. Notice that of course this also works with the simple one + * audio/one video mountpoints you've used so far, and that has been + * documented before: as such, you're encouraged to start using this + * new approach as soon as possible, since in the next versions we + * might deprecate the old one. * * \section streamapi Streaming API * @@ -206,16 +265,24 @@ rtspiface = network interface IP address or device name to listen on when receiv "description" : "", "metadata" : "", "enabled" : , - "audio_age_ms" : , - "video_age_ms" : + "media" : [ + { + "mid" : "", + "label" : "", + "type" : ", + "age_ms" : , + }, + { + // Other streams, if available + } + ] }, { "id" : , "type" : "", "description" : "", "metadata" : "", - "audio_age_ms" : , - "video_age_ms" : + "media" : [..] }, ... ] @@ -223,7 +290,7 @@ rtspiface = network interface IP address or device name to listen on when receiv \endverbatim * * As you can see, the \c list request only returns very generic info on - * each mounpoint. In case you're interested in learning more details about + * each mountpoint. In case you're interested in learning more details about * a specific mountpoint, you can use the \c info request instead, which * returns more information, or all of it if the mountpoint secret is * provided in the request. An \c info request must be formatted like this: @@ -252,15 +319,23 @@ rtspiface = network interface IP address or device name to listen on when receiv "is_private" : , "viewers" : , "enabled" : , - "audio" : , - "audiopt" :