[Pkg-voip-commits] [janus] 02/282: Several new helper methods for SDP utilities

Jonas Smedegaard dr at jones.dk
Wed Dec 20 21:53:21 UTC 2017


This is an automated email from the git hooks/post-receive script.

js pushed a commit to annotated tag debian/0.2.6-1
in repository janus.

commit b310383cea8d706ced5daa131fe2e74e20bc57a5
Author: Lorenzo Miniero <lminiero at gmail.com>
Date:   Tue Feb 14 17:39:51 2017 +0100

    Several new helper methods for SDP utilities
---
 plugins/janus_audiobridge.c |   87 ++--
 plugins/janus_echotest.c    |   48 +-
 plugins/janus_videoroom.c   | 1160 +++++++------------------------------------
 sdp-utils.c                 |  728 ++++++++++++++++++++++++---
 sdp-utils.h                 |  141 +++++-
 utils.c                     |    9 +
 6 files changed, 1051 insertions(+), 1122 deletions(-)

diff --git a/plugins/janus_audiobridge.c b/plugins/janus_audiobridge.c
index 6c8ece3..c6fd498 100644
--- a/plugins/janus_audiobridge.c
+++ b/plugins/janus_audiobridge.c
@@ -543,6 +543,7 @@ record_file =	/path/to/recording.wav (where to save the recording)
 #include "../rtp.h"
 #include "../rtcp.h"
 #include "../record.h"
+#include "../sdp-utils.h"
 #include "../utils.h"
 
 
@@ -859,18 +860,6 @@ static gint janus_audiobridge_rtp_sort(gconstpointer a, gconstpointer b) {
 	return 0;
 }
 
-/* SDP offer/answer template */
-#define sdp_template \
-		"v=0\r\n" \
-		"o=- %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n"	/* We need current time here */ \
-		"s=%s\r\n"							/* Audio bridge name */ \
-		"t=0 0\r\n" \
-		"m=audio 1 RTP/SAVPF %d\r\n"		/* Opus payload type */ \
-		"c=IN IP4 1.1.1.1\r\n" \
-		"a=rtpmap:%d opus/48000/2\r\n"		/* Opus payload type */ \
-		"a=fmtp:%d maxplaybackrate=%"SCNu32"; stereo=0; sprop-stereo=0; useinbandfec=0\r\n" /* Opus payload type and room sampling rate */ \
-		"%s"								/* extmap(s), if any */
-
 /* Helper struct to generate and parse WAVE headers */
 typedef struct wav_header {
 	char riff[4];
@@ -915,6 +904,7 @@ typedef struct wav_header {
 #define JANUS_AUDIOBRIDGE_ERROR_ID_EXISTS		490
 #define JANUS_AUDIOBRIDGE_ERROR_ALREADY_JOINED	491
 #define JANUS_AUDIOBRIDGE_ERROR_NO_SUCH_USER	492
+#define JANUS_AUDIOBRIDGE_ERROR_INVALID_SDP		493
 
 
 /* AudioBridge watchdog/garbage collector (sort of) */
@@ -3238,54 +3228,55 @@ static void *janus_audiobridge_handler(void *data) {
 			json_decref(event);
 		} else {
 			JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg_sdp_type, msg_sdp);
-			const char *type = NULL;
-			if(!strcasecmp(msg_sdp_type, "offer"))
-				type = "answer";
-			if(!strcasecmp(msg_sdp_type, "answer"))
-				type = "offer";
-			/* Fill the SDP template and use that as our answer */
-			janus_audiobridge_participant *participant = (janus_audiobridge_participant *)session->participant;
-			char sdp[1024];
+			/* Prepare an SDP answer */
+			const char *type = "answer";
+			char error_str[512];
+			janus_sdp *offer = janus_sdp_parse(msg_sdp, error_str, sizeof(error_str));
+			if(offer == NULL) {
+				json_decref(event);
+				JANUS_LOG(LOG_ERR, "Error parsing offer: %s\n", error_str);
+				error_code = JANUS_AUDIOBRIDGE_ERROR_INVALID_SDP;
+				g_snprintf(error_cause, 512, "Error parsing offer: %s", error_str);
+				goto error;
+			}
 			/* What is the Opus payload type? */
-			participant->opus_pt = janus_get_codec_pt(msg_sdp, "opus");
+			janus_audiobridge_participant *participant = (janus_audiobridge_participant *)session->participant;
+			participant->opus_pt = janus_sdp_get_codec_pt(offer, "opus");
+			if(participant->opus_pt < 0) {
+				/* TODO Handle this case */
+				JANUS_LOG(LOG_ERR, "Offer doesn't contain Opus..?\n");
+			}
 			JANUS_LOG(LOG_VERB, "Opus payload type is %d\n", participant->opus_pt);
+			janus_sdp *answer = janus_sdp_generate_answer(offer,
+				/* Reject video and data channels, if offered */
+				JANUS_SDP_OA_VIDEO, FALSE,
+				JANUS_SDP_OA_DATA, FALSE,
+				JANUS_SDP_OA_DONE);
+			/* Replace the session name */
+			g_free(answer->s_name);
+			answer->s_name = g_strdup(participant->room->room_name);
+			/* Add a fmtp attribute */
+			janus_sdp_attribute *a = janus_sdp_attribute_create("fmtp",
+				"%d maxplaybackrate=%"SCNu32"; stereo=0; sprop-stereo=0; useinbandfec=0\r\n",
+					participant->opus_pt, participant->room->sampling_rate);
+			janus_sdp_attribute_add_to_mline(janus_sdp_mline_find(answer, JANUS_SDP_AUDIO), a);
 			/* Is the audio level extension negotiated? */
 			participant->extmap_id = 0;
 			participant->dBov_level = 0;
 			int extmap_id = -1;
-			char audio_level_extmap[100];
 			if(participant->room && participant->room->audiolevel_ext)
 				extmap_id = janus_rtp_header_extension_get_id(msg_sdp, JANUS_RTP_EXTMAP_AUDIO_LEVEL);
 			if(extmap_id > -1) {
+				/* Add an extmap attribute too */
 				participant->extmap_id = extmap_id;
-				g_snprintf(audio_level_extmap, sizeof(audio_level_extmap),
-					"a=extmap:%d %s\r\n", extmap_id, JANUS_RTP_EXTMAP_AUDIO_LEVEL);
+				janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
+					"%d %s\r\n", extmap_id, JANUS_RTP_EXTMAP_AUDIO_LEVEL);
+				janus_sdp_attribute_add_to_mline(janus_sdp_mline_find(answer, JANUS_SDP_AUDIO), a);
 			}
 			/* Prepare the response */
-			g_snprintf(sdp, 1024, sdp_template,
-				janus_get_real_time(),			/* We need current time here */
-				janus_get_real_time(),			/* We need current time here */
-				participant->room->room_name,	/* Audio bridge name */
-				participant->opus_pt,			/* Opus payload type */
-				participant->opus_pt,			/* Opus payload type */
-				participant->opus_pt, 			/* Opus payload type and room sampling rate */
-				participant->room->sampling_rate,
-				extmap_id > -1 ? audio_level_extmap : "");
-			/* Is the peer recvonly? */
-			if(strstr(msg_sdp, "a=recvonly") != NULL) {
-				/* If so, use sendonly here */
-				g_strlcat(sdp, "a=sendonly\r\n", 1024);
-			}
-			/* Did the peer negotiate video? */
-			if(strstr(msg_sdp, "m=video") != NULL) {
-				/* If so, reject it */
-				g_strlcat(sdp, "m=video 0 RTP/SAVPF 0\r\n", 1024);				
-			}
-			/* Did the peer negotiate data channels? */
-			if(strstr(msg_sdp, "DTLS/SCTP") != NULL) {
-				/* If so, reject them */
-				g_strlcat(sdp, "m=application 0 DTLS/SCTP 0\r\n", 1024);
-			}
+			char *sdp = janus_sdp_write(answer);
+			janus_sdp_free(offer);
+			janus_sdp_free(answer);
 			json_t *jsep = json_pack("{ssss}", "type", type, "sdp", sdp);
 			/* How long will the gateway take to push the event? */
 			g_atomic_int_set(&session->hangingup, 0);
diff --git a/plugins/janus_echotest.c b/plugins/janus_echotest.c
index 0497864..5fed114 100644
--- a/plugins/janus_echotest.c
+++ b/plugins/janus_echotest.c
@@ -93,6 +93,7 @@
 #include "../mutex.h"
 #include "../record.h"
 #include "../rtcp.h"
+#include "../sdp-utils.h"
 #include "../utils.h"
 
 
@@ -219,6 +220,7 @@ static void janus_echotest_message_free(janus_echotest_message *msg) {
 #define JANUS_ECHOTEST_ERROR_NO_MESSAGE			411
 #define JANUS_ECHOTEST_ERROR_INVALID_JSON		412
 #define JANUS_ECHOTEST_ERROR_INVALID_ELEMENT	413
+#define JANUS_ECHOTEST_ERROR_INVALID_SDP		414
 
 
 /* EchoTest watchdog/garbage collector (sort of) */
@@ -890,39 +892,21 @@ static void *janus_echotest_handler(void *data) {
 			JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
 			json_decref(event);
 		} else {
-			/* Forward the same offer to the gateway, to start the echo test */
-			const char *type = NULL;
-			if(!strcasecmp(msg_sdp_type, "offer"))
-				type = "answer";
-			if(!strcasecmp(msg_sdp_type, "answer"))
-				type = "offer";
-			/* Any media direction that needs to be fixed? */
-			char *sdp = g_strdup(msg_sdp);
-			if(strstr(sdp, "a=recvonly")) {
-				/* Turn recvonly to inactive, as we simply bounce media back */
-				sdp = janus_string_replace(sdp, "a=recvonly", "a=inactive");
-			} else if(strstr(sdp, "a=sendonly")) {
-				/* Turn sendonly to recvonly */
-				sdp = janus_string_replace(sdp, "a=sendonly", "a=recvonly");
-				/* FIXME We should also actually not echo this media back, though... */
-			}
-			/* Make also sure we get rid of ULPfec, red, etc. */
-			if(strstr(sdp, "ulpfec")) {
-				/* FIXME This really needs some better code */
-				sdp = janus_string_replace(sdp, "a=rtpmap:116 red/90000\r\n", "");
-				sdp = janus_string_replace(sdp, "a=rtpmap:117 ulpfec/90000\r\n", "");
-				sdp = janus_string_replace(sdp, "a=rtpmap:96 rtx/90000\r\n", "");
-				sdp = janus_string_replace(sdp, "a=fmtp:96 apt=100\r\n", "");
-				sdp = janus_string_replace(sdp, "a=rtpmap:97 rtx/90000\r\n", "");
-				sdp = janus_string_replace(sdp, "a=fmtp:97 apt=101\r\n", "");
-				sdp = janus_string_replace(sdp, "a=rtpmap:98 rtx/90000\r\n", "");
-				sdp = janus_string_replace(sdp, "a=fmtp:98 apt=116\r\n", "");
-				sdp = janus_string_replace(sdp, " 116", "");
-				sdp = janus_string_replace(sdp, " 117", "");
-				sdp = janus_string_replace(sdp, " 96", "");
-				sdp = janus_string_replace(sdp, " 97", "");
-				sdp = janus_string_replace(sdp, " 98", "");
+			/* Answer the offer and send it to the gateway, to start the echo test */
+			const char *type = "answer";
+			char error_str[512];
+			janus_sdp *offer = janus_sdp_parse(msg_sdp, error_str, sizeof(error_str));
+			if(offer == NULL) {
+				json_decref(event);
+				JANUS_LOG(LOG_ERR, "Error parsing offer: %s\n", error_str);
+				error_code = JANUS_ECHOTEST_ERROR_INVALID_SDP;
+				g_snprintf(error_cause, 512, "Error parsing offer: %s", error_str);
+				goto error;
 			}
+			janus_sdp *answer = janus_sdp_generate_answer(offer, JANUS_SDP_OA_DONE);
+			char *sdp = janus_sdp_write(answer);
+			janus_sdp_free(offer);
+			janus_sdp_free(answer);
 			json_t *jsep = json_pack("{ssss}", "type", type, "sdp", sdp);
 			/* How long will the gateway take to push the event? */
 			g_atomic_int_set(&session->hangingup, 0);
diff --git a/plugins/janus_videoroom.c b/plugins/janus_videoroom.c
index a08b760..ed55f6d 100644
--- a/plugins/janus_videoroom.c
+++ b/plugins/janus_videoroom.c
@@ -62,7 +62,7 @@ publishers = <max number of concurrent senders> (e.g., 6 for a video
              conference or 1 for a webinar)
 bitrate = <max video bitrate for senders> (e.g., 128000)
 fir_freq = <send a FIR to publishers every fir_freq seconds> (0=disable)
-audiocodec = opus|isac32|isac16|pcmu|pcma (audio codec to force on publishers, default=opus)
+audiocodec = opus|isac32|isac16|pcmu|pcma|g722 (audio codec to force on publishers, default=opus)
 videocodec = vp8|vp9|h264 (video codec to force on publishers, default=vp8)
 audiolevel_ext = yes|no (whether the ssrc-audio-level RTP extension must be
 	negotiated/used or not for new publishers, default=yes)
@@ -110,10 +110,8 @@ rec_dir = <folder where recordings should be stored, when enabled>
  * the \c switch request can be used to change the source of the media
  * flowing over a specific PeerConnection (e.g., I was watching Alice,
  * I want to watch Bob now) without having to create a new handle for
- * that; \c stop interrupts a viewer instance; \c add and \c remove
- * are just used when involving "Plan B", and are used to add or remove
- * publishers to be muxed in the single viewer PeerConnection; finally,
- * \c leave allows you to leave a video room for good.
+ * that; \c stop interrupts a viewer instance; finally, \c leave allows
+ * you to leave a video room for good.
  * 
  * Notice that, in general, all users can create rooms. If you want to
  * limit this functionality, you can configure an admin \c admin_key in
@@ -302,9 +300,6 @@ static struct janus_json_parameter listener_parameters[] = {
 	{"video", JANUS_JSON_BOOL, 0},
 	{"data", JANUS_JSON_BOOL, 0}
 };
-static struct janus_json_parameter feeds_parameters[] = {
-	{"feeds", JSON_ARRAY, JANUS_JSON_PARAM_NONEMPTY}
-};
 
 /* Static configuration instance */
 static janus_config *config = NULL;
@@ -324,7 +319,6 @@ static void janus_videoroom_relay_data_packet(gpointer data, gpointer user_data)
 typedef enum janus_videoroom_p_type {
 	janus_videoroom_p_type_none = 0,
 	janus_videoroom_p_type_subscriber,			/* Generic listener/subscriber */
-	janus_videoroom_p_type_subscriber_muxed,	/* Multiplexed listener/subscriber */
 	janus_videoroom_p_type_publisher,			/* Participant/publisher */
 } janus_videoroom_p_type;
 
@@ -355,12 +349,24 @@ static void janus_videoroom_message_free(janus_videoroom_message *msg) {
 	g_free(msg);
 }
 
+/* Payload types we'll offer internally */
+#define OPUS_PT		111
+#define ISAC32_PT	104
+#define ISAC16_PT	103
+#define PCMU_PT		0
+#define PCMA_PT		8
+#define G722_PT		9
+#define VP8_PT		96
+#define VP9_PT		101
+#define H264_PT		107
+
 typedef enum janus_videoroom_audiocodec {
 	JANUS_VIDEOROOM_OPUS,		/* Publishers will have to use OPUS 	*/
 	JANUS_VIDEOROOM_ISAC_32K,	/* Publishers will have to use ISAC 32K */
 	JANUS_VIDEOROOM_ISAC_16K,	/* Publishers will have to use ISAC 16K */
 	JANUS_VIDEOROOM_PCMU,		/* Publishers will have to use PCMU 8K 	*/
-	JANUS_VIDEOROOM_PCMA		/* Publishers will have to use PCMA 8K 	*/
+	JANUS_VIDEOROOM_PCMA,		/* Publishers will have to use PCMA 8K 	*/
+	JANUS_VIDEOROOM_G722		/* Publishers will have to use G.722 	*/
 } janus_videoroom_audiocodec;
 static const char *janus_videoroom_audiocodec_name(janus_videoroom_audiocodec acodec) {
 	switch(acodec) {
@@ -374,11 +380,32 @@ static const char *janus_videoroom_audiocodec_name(janus_videoroom_audiocodec ac
 			return "pcmu";
 		case JANUS_VIDEOROOM_PCMA:
 			return "pcma";
+		case JANUS_VIDEOROOM_G722:
+			return "g722";
 		default:
 			/* Shouldn't happen */
 			return "opus";
 	}
 }
+static int janus_videoroom_audiocodec_pt(janus_videoroom_audiocodec acodec) {
+	switch(acodec) {
+		case JANUS_VIDEOROOM_OPUS:
+			return OPUS_PT;
+		case JANUS_VIDEOROOM_ISAC_32K:
+			return ISAC32_PT;
+		case JANUS_VIDEOROOM_ISAC_16K:
+			return ISAC16_PT;
+		case JANUS_VIDEOROOM_PCMU:
+			return PCMU_PT;
+		case JANUS_VIDEOROOM_PCMA:
+			return PCMA_PT;
+		case JANUS_VIDEOROOM_G722:
+			return G722_PT;
+		default:
+			/* Shouldn't happen */
+			return OPUS_PT;
+	}
+}
 
 typedef enum janus_videoroom_videocodec {
 	JANUS_VIDEOROOM_VP8,	/* Publishers will have to use VP8 */
@@ -398,6 +425,19 @@ static const char *janus_videoroom_videocodec_name(janus_videoroom_videocodec vc
 			return "vp8";
 	}
 }
+static int janus_videoroom_videocodec_pt(janus_videoroom_videocodec vcodec) {
+	switch(vcodec) {
+		case JANUS_VIDEOROOM_VP8:
+			return VP8_PT;
+		case JANUS_VIDEOROOM_VP9:
+			return VP9_PT;
+		case JANUS_VIDEOROOM_H264:
+			return H264_PT;
+		default:
+			/* Shouldn't happen */
+			return VP8_PT;
+	}
+}
 
 typedef struct janus_videoroom {
 	guint64 room_id;			/* Unique room ID */
@@ -506,19 +546,10 @@ typedef struct janus_videoroom_listener {
 	guint32 pvt_id;		/* Private ID of the participant that is subscribing (if available/provided) */
 	janus_videoroom_listener_context context;	/* Needed in case there are publisher switches on this listener */
 	gboolean audio, video, data;		/* Whether audio, video and/or data must be sent to this publisher */
-	struct janus_videoroom_listener_muxed *parent;	/* Overall subscriber, if this is a sub-listener in a Multiplexed one */
 	gboolean paused;
 } janus_videoroom_listener;
 static void janus_videoroom_listener_free(janus_videoroom_listener *l);
 
-typedef struct janus_videoroom_listener_muxed {
-	janus_videoroom_session *session;
-	janus_videoroom *room;	/* Room */
-	GSList *listeners;	/* List of listeners (as a Multiplexed listener can be subscribed to more publishers at the same time) */
-	janus_mutex listeners_mutex;
-} janus_videoroom_listener_muxed;
-static void janus_videoroom_muxed_listener_free(janus_videoroom_listener_muxed *l);
-
 typedef struct janus_videoroom_rtp_relay_packet {
 	rtp_header *data;
 	gint length;
@@ -527,96 +558,6 @@ typedef struct janus_videoroom_rtp_relay_packet {
 	uint16_t seq_number;
 } janus_videoroom_rtp_relay_packet;
 
-/* SDP offer/answer templates */
-#define OPUS_PT	111
-#define ISAC32_PT	104
-#define ISAC16_PT	103
-#define PCMU_PT	0
-#define PCMA_PT	8
-#define VP8_PT		100
-#define VP9_PT		101
-#define H264_PT	107
-#define sdp_template \
-		"v=0\r\n" \
-		"o=- %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n"	/* We need current time here */ \
-		"s=%s\r\n"							/* Video room name */ \
-		"t=0 0\r\n" \
-		"%s%s%s"				/* Audio, video and/or data channel m-lines */
-#define sdp_a_template_opus \
-		"m=audio 1 RTP/SAVPF %d\r\n"		/* Opus payload type */ \
-		"c=IN IP4 1.1.1.1\r\n" \
-		"a=%s\r\n"							/* Media direction */ \
-		"a=rtpmap:%d opus/48000/2\r\n"		/* Opus payload type */ \
-		"%s"								/* extmap(s), if any */
-#define sdp_a_template_isac32 \
-		"m=audio 1 RTP/SAVPF %d\r\n"		/* ISAC32_PT payload type */ \
-		"c=IN IP4 1.1.1.1\r\n" \
-		"a=%s\r\n"							/* Media direction */ \
-		"a=rtpmap:%d ISAC/32000\r\n"		/* ISAC32_PT payload type */ \
-		"%s"								/* extmap(s), if any */
-#define sdp_a_template_isac16 \
-		"m=audio 1 RTP/SAVPF %d\r\n"		/* ISAC16_PT payload type */ \
-		"c=IN IP4 1.1.1.1\r\n" \
-		"a=%s\r\n"							/* Media direction */ \
-		"a=rtpmap:%d ISAC/16000\r\n"		/* ISAC16_PT payload type */ \
-		"%s"								/* extmap(s), if any */
-#define sdp_a_template_pcmu \
-		"m=audio 1 RTP/SAVPF %d\r\n"		/* PCMU_PT payload type */ \
-		"c=IN IP4 1.1.1.1\r\n" \
-		"a=%s\r\n"							/* Media direction */ \
-		"a=rtpmap:%d PCMU/8000\r\n"		    /* PCMU_PT payload type */ \
-		"%s"								/* extmap(s), if any */
-#define sdp_a_template_pcma \
-		"m=audio 1 RTP/SAVPF %d\r\n"		/* PCMA_PT payload type */ \
-		"c=IN IP4 1.1.1.1\r\n" \
-		"a=%s\r\n"							/* Media direction */ \
-		"a=rtpmap:%d PCMA/8000\r\n"		    /* PCMA_PT payload type */ \
-		"%s"								/* extmap(s), if any */
-#define sdp_v_template_vp8 \
-		"m=video 1 RTP/SAVPF %d\r\n"		/* VP8 payload type */ \
-		"c=IN IP4 1.1.1.1\r\n" \
-		"b=AS:%d\r\n"						/* Bandwidth */ \
-		"a=%s\r\n"							/* Media direction */ \
-		"a=rtpmap:%d VP8/90000\r\n"			/* VP8 payload type */ \
-		"a=rtcp-fb:%d ccm fir\r\n"			/* VP8 payload type */ \
-		"a=rtcp-fb:%d nack\r\n"				/* VP8 payload type */ \
-		"a=rtcp-fb:%d nack pli\r\n"			/* VP8 payload type */ \
-		"a=rtcp-fb:%d goog-remb\r\n"		/* VP8 payload type */ \
-		"%s"								/* extmap(s), if any */
-#define sdp_v_template_vp9 \
-		"m=video 1 RTP/SAVPF %d\r\n"		/* VP9 payload type */ \
-		"c=IN IP4 1.1.1.1\r\n" \
-		"b=AS:%d\r\n"						/* Bandwidth */ \
-		"a=%s\r\n"							/* Media direction */ \
-		"a=rtpmap:%d VP9/90000\r\n"			/* VP9 payload type */ \
-		"a=rtcp-fb:%d ccm fir\r\n"			/* VP9 payload type */ \
-		"a=rtcp-fb:%d nack\r\n"				/* VP9 payload type */ \
-		"a=rtcp-fb:%d nack pli\r\n"			/* VP9 payload type */ \
-		"a=rtcp-fb:%d goog-remb\r\n"		/* VP9 payload type */ \
-		"%s"								/* extmap(s), if any */
-#define sdp_v_template_h264 \
-		"m=video 1 RTP/SAVPF %d\r\n"		/* H264 payload type */ \
-		"c=IN IP4 1.1.1.1\r\n" \
-		"b=AS:%d\r\n"						/* Bandwidth */ \
-		"a=%s\r\n"							/* Media direction */ \
-		"a=rtpmap:%d H264/90000\r\n"		/* H264 payload type */ \
-		"a=fmtp:%d profile-level-id=42e01f;packetization-mode=1\r\n" \
-		"a=rtcp-fb:%d ccm fir\r\n"			/* H264 payload type */ \
-		"a=rtcp-fb:%d nack\r\n"				/* H264 payload type */ \
-		"a=rtcp-fb:%d nack pli\r\n"			/* H264 payload type */ \
-		"a=rtcp-fb:%d goog-remb\r\n"		/* H264 payload type */ \
-		"%s"								/* extmap(s), if any */
-#ifdef HAVE_SCTP
-#define sdp_d_template \
-		"m=application 1 DTLS/SCTP 5000\r\n" \
-		"c=IN IP4 1.1.1.1\r\n" \
-		"a=sctpmap:5000 webrtc-datachannel 16\r\n"
-#else
-#define sdp_d_template \
-		"m=application 0 DTLS/SCTP 0\r\n" \
-		"c=IN IP4 1.1.1.1\r\n"
-#endif
-
 
 /* Error codes */
 #define JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR		499
@@ -639,11 +580,6 @@ typedef struct janus_videoroom_rtp_relay_packet {
 #define JANUS_VIDEOROOM_ERROR_INVALID_SDP		437
 
 
-/* Multiplexing helpers */
-int janus_videoroom_muxed_subscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction);
-int janus_videoroom_muxed_unsubscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction);
-int janus_videoroom_muxed_offer(janus_videoroom_listener_muxed *muxed_listener, char *transaction, json_t *event);
-
 static guint32 janus_videoroom_rtp_forwarder_add_helper(janus_videoroom_participant *p,
 		const gchar* host, int port, int pt, uint32_t ssrc, gboolean is_video, gboolean is_data) {
 	if(!p || !host) {
@@ -681,9 +617,6 @@ static void session_free(gpointer data) {
 		case janus_videoroom_p_type_subscriber:
 			janus_videoroom_listener_free(session->participant);
 			break;
-		case janus_videoroom_p_type_subscriber_muxed:
-			janus_videoroom_muxed_listener_free(session->participant);
-			break;
 		default:
 			break;
 		}
@@ -886,6 +819,8 @@ int janus_videoroom_init(janus_callbacks *callback, const char *config_path) {
 					videoroom->acodec = JANUS_VIDEOROOM_PCMU;
 				else if(!strcasecmp(audiocodec->value, "pcma"))
 					videoroom->acodec = JANUS_VIDEOROOM_PCMA;
+				else if(!strcasecmp(audiocodec->value, "g722"))
+					videoroom->acodec = JANUS_VIDEOROOM_G722;
 				else {
 					JANUS_LOG(LOG_WARN, "Unsupported audio codec '%s', falling back to OPUS\n", audiocodec->value);
 					videoroom->acodec = JANUS_VIDEOROOM_OPUS;
@@ -1138,8 +1073,6 @@ void janus_videoroom_destroy_session(janus_plugin_session *handle, int *error) {
 			janus_videoroom_leave_or_unpublish(participant, TRUE);
 		} else if(session->participant_type == janus_videoroom_p_type_subscriber) {
 			/* Detaching this listener from its publisher is already done by hangup_media */
-		} else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
-			/* Detaching this listener from its publishers is already done by hangup_media */
 		}
 	}
 	janus_mutex_unlock(&sessions_mutex);
@@ -1209,9 +1142,6 @@ json_t *janus_videoroom_query_session(janus_plugin_session *handle) {
 				json_object_set_new(media, "data", json_integer(participant->data));
 				json_object_set_new(info, "media", media);
 			}
-		} else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
-			json_object_set_new(info, "type", json_string("muxed-listener"));
-			/* TODO */
 		}
 	}
 	json_object_set_new(info, "destroyed", json_integer(session->destroyed));
@@ -1335,10 +1265,12 @@ struct janus_plugin_result *janus_videoroom_handle_message(janus_plugin_session
 		json_t *audiocodec = json_object_get(root, "audiocodec");
 		if(audiocodec) {
 			const char *audiocodec_value = json_string_value(audiocodec);
-			if(!strcasecmp(audiocodec_value, "opus") && !strcasecmp(audiocodec_value, "isac32") && !strcasecmp(audiocodec_value, "isac16") && !strcasecmp(audiocodec_value, "pcmu") && !strcasecmp(audiocodec_value, "pcma")) {
-				JANUS_LOG(LOG_ERR, "Invalid element (audiocodec can only be opus, isac32, isac16, pcmu, or pcma)\n");
+			if(!strcasecmp(audiocodec_value, "opus") && !strcasecmp(audiocodec_value, "g722") &&
+					!strcasecmp(audiocodec_value, "isac32") && !strcasecmp(audiocodec_value, "isac16") &&
+					!strcasecmp(audiocodec_value, "pcmu") && !strcasecmp(audiocodec_value, "pcma")) {
+				JANUS_LOG(LOG_ERR, "Invalid element (audiocodec can only be opus, isac32, isac16, pcmu, pcma or g722)\n");
 				error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
-				g_snprintf(error_cause, 512, "Invalid element (audiocodec can only be opus, isac32, isac16, pcmu, or pcma)");
+				g_snprintf(error_cause, 512, "Invalid element (audiocodec can only be opus, isac32, isac16, pcmu, pcma or g722)");
 				goto plugin_response;
 			}
 		}
@@ -1458,6 +1390,8 @@ struct janus_plugin_result *janus_videoroom_handle_message(janus_plugin_session
 				videoroom->acodec = JANUS_VIDEOROOM_PCMU;
 			else if(!strcasecmp(audiocodec_value, "pcma"))
 				videoroom->acodec = JANUS_VIDEOROOM_PCMA;
+			else if(!strcasecmp(audiocodec_value, "g722"))
+				videoroom->acodec = JANUS_VIDEOROOM_G722;
 			else {
 				JANUS_LOG(LOG_WARN, "Unsupported audio codec '%s', falling back to OPUS\n", audiocodec_value);
 				videoroom->acodec = JANUS_VIDEOROOM_OPUS;
@@ -2260,32 +2194,6 @@ void janus_videoroom_setup_media(janus_plugin_session *handle) {
 					}
 				}
 			}
-		} else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
-			/* Do the same, but for all feeds */
-			janus_videoroom_listener_muxed *listener = (janus_videoroom_listener_muxed *)session->participant;
-			if(listener == NULL)
-				return;
-			GSList *ps = listener->listeners;
-			while(ps) {
-				janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
-				if(l && l->feed) {
-					janus_videoroom_participant *p = l->feed;
-					if(p && p->session) {
-						/* Send a FIR */
-						char buf[20];
-						memset(buf, 0, 20);
-						janus_rtcp_fir((char *)&buf, 20, &p->fir_seq);
-						JANUS_LOG(LOG_VERB, "New Multiplexed listener available, sending FIR to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
-						gateway->relay_rtcp(p->session->handle, 1, buf, 20);
-						/* Send a PLI too, just in case... */
-						memset(buf, 0, 12);
-						janus_rtcp_pli((char *)&buf, 12);
-						JANUS_LOG(LOG_VERB, "New Multiplexed listener available, sending PLI to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
-						gateway->relay_rtcp(p->session->handle, 1, buf, 12);
-					}
-				}
-				ps = ps->next;
-			}
 		}
 	}
 }
@@ -2511,8 +2419,6 @@ void janus_videoroom_slow_link(janus_plugin_session *handle, int uplink, int vid
 		} else {
 			JANUS_LOG(LOG_WARN, "Got a slow downlink on a VideoRoom viewer? Weird, because it doesn't send media...\n");
 		}
-	} else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
-		/* TBD. */
 	}
 }
 
@@ -2678,136 +2584,6 @@ void janus_videoroom_hangup_media(janus_plugin_session *handle) {
 			}
 		}
 		/* TODO Should we close the handle as well? */
-	} else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
-		/* Do the same, but for all sub-listener */
-		janus_videoroom_listener_muxed *listener = (janus_videoroom_listener_muxed *)session->participant;
-		GSList *ps = listener->listeners;
-		while(ps) {
-			janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
-			if(l) {
-				l->paused = TRUE;
-				janus_videoroom_participant *publisher = l->feed;
-				if(publisher != NULL) {
-					janus_mutex_lock(&publisher->listeners_mutex);
-					publisher->listeners = g_slist_remove(publisher->listeners, l);
-					janus_mutex_unlock(&publisher->listeners_mutex);
-					l->feed = NULL;
-				}
-			}
-			/* TODO Should we close the handle as well? */
-			ps = ps->next;
-		}
-		/* TODO Should we close the handle as well? */
-	}
-}
-
-static void janus_videoroom_sdp_a_format(char *mline, int mline_size, janus_videoroom_audiocodec acodec, int pt, const char *audio_mode, gboolean extmap, int extmap_id) {
-	char audio_level_extmap[100];
-	if(extmap) {
-		/* We only negotiate support (if required) for a single audio extension, audio levels */
-		g_snprintf(audio_level_extmap, sizeof(audio_level_extmap),
-			"a=extmap:%d %s\r\n", extmap_id, JANUS_RTP_EXTMAP_AUDIO_LEVEL);
-	}
-	switch(acodec) {
-		case JANUS_VIDEOROOM_OPUS:
-			g_snprintf(mline, mline_size, sdp_a_template_opus,
-				pt,						/* Opus payload type */
-				audio_mode,
-				pt,						/* Opus payload type */
-				extmap ? audio_level_extmap : "");
-			break;
-		case JANUS_VIDEOROOM_ISAC_32K:
-			g_snprintf(mline, mline_size, sdp_a_template_isac32,
-				pt,						/* ISAC 32K payload type */
-				audio_mode,
-				pt, 					/* ISAC 32K payload type */
-				extmap ? audio_level_extmap : "");
-			break;
-		case JANUS_VIDEOROOM_ISAC_16K:
-			g_snprintf(mline, mline_size, sdp_a_template_isac16,
-				pt,						/* ISAC 16K payload type */
-				audio_mode,				/* The publisher gets a recvonly or inactive back */
-				pt,						/* ISAC 16K payload type */
-				extmap ? audio_level_extmap : "");
-			break;
-		case JANUS_VIDEOROOM_PCMU:
-			g_snprintf(mline, mline_size, sdp_a_template_pcmu,
-				pt,						/* PCMU payload type */
-				audio_mode,				/* The publisher gets a recvonly or inactive back */
-				pt,						/* PCMU payload type */
-				extmap ? audio_level_extmap : "");
-			break;
-		case JANUS_VIDEOROOM_PCMA:
-			g_snprintf(mline, mline_size, sdp_a_template_pcma,
-				pt,						/* PCMA payload type */
-				audio_mode,				/* The publisher gets a recvonly or inactive back */
-				pt,						/* PCMA payload type */
-				extmap ? audio_level_extmap : "");
-			break;
-		default:
-			/* Shouldn't happen */
-			mline[0] = '\0';
-			break;
-	}
-}
-
-static void janus_videoroom_sdp_v_format(char *mline, int mline_size, janus_videoroom_videocodec vcodec, int pt, int b, const char *video_mode,
-		gboolean vo_extmap, int vo_extmap_id, gboolean pd_extmap, int pd_extmap_id) {
-	char extmaps[200], temp[100];
-	memset(extmaps, 0, sizeof(extmaps));
-	memset(temp, 0, sizeof(temp));
-	if(vo_extmap) {
-		g_snprintf(temp, sizeof(temp),
-			"a=extmap:%d %s\r\n", vo_extmap_id, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION);
-		g_strlcat(extmaps, temp, sizeof(extmaps));
-	}
-	if(pd_extmap) {
-		g_snprintf(temp, sizeof(temp),
-			"a=extmap:%d %s\r\n", pd_extmap_id, JANUS_RTP_EXTMAP_PLAYOUT_DELAY);
-		g_strlcat(extmaps, temp, sizeof(extmaps));
-	}
-	switch(vcodec) {
-		case JANUS_VIDEOROOM_VP8:
-			g_snprintf(mline, mline_size, sdp_v_template_vp8,
-				pt,							/* payload type */
-				b,							/* Bandwidth */
-				video_mode,					/* The publisher gets a recvonly or inactive back */
-				pt, 						/* payload type */
-				pt, 						/* payload type */
-				pt, 						/* payload type */
-				pt, 						/* payload type */
-				pt, 						/* payload type */
-				(vo_extmap || pd_extmap) ? extmaps : "");
-			break;
-		case JANUS_VIDEOROOM_VP9:
-			g_snprintf(mline, mline_size, sdp_v_template_vp9,
-				pt,							/* payload type */
-				b,							/* Bandwidth */
-				video_mode,					/* The publisher gets a recvonly or inactive back */
-				pt, 						/* payload type */
-				pt, 						/* payload type */
-				pt, 						/* payload type */
-				pt, 						/* payload type */
-				pt, 						/* payload type */
-				(vo_extmap || pd_extmap) ? extmaps : "");
-			break;
-		case JANUS_VIDEOROOM_H264:
-			g_snprintf(mline, mline_size, sdp_v_template_h264,
-				pt,							/* payload type */
-				b,							/* Bandwidth */
-				video_mode,					/* The publisher gets a recvonly or inactive back */
-				pt, 						/* payload type */
-				pt, 						/* payload type */
-				pt, 						/* payload type */
-				pt, 						/* payload type */
-				pt, 						/* payload type */
-				pt, 						/* payload type */
-				(vo_extmap || pd_extmap) ? extmaps : "");
-			break;
-		default:
-			/* Shouldn't happen */
-			mline[0] = '\0';
-			break;
 	}
 }
 
@@ -2982,6 +2758,9 @@ static void *janus_videoroom_handler(void *data) {
 					case JANUS_VIDEOROOM_PCMA:
 						publisher->audio_pt = PCMA_PT;
 						break;
+					case JANUS_VIDEOROOM_G722:
+						publisher->audio_pt = G722_PT;
+						break;
 					default:
 						/* Shouldn't happen */
 						publisher->audio_pt = OPUS_PT;
@@ -3145,7 +2924,6 @@ static void *janus_videoroom_handler(void *data) {
 					if(!publisher->data)
 						listener->data = FALSE;	/* ... unless the publisher isn't sending any data */
 					listener->paused = TRUE;	/* We need an explicit start from the listener */
-					listener->parent = NULL;
 					session->participant = listener;
 					janus_mutex_lock(&publisher->listeners_mutex);
 					publisher->listeners = g_slist_append(publisher->listeners, listener);
@@ -3181,84 +2959,6 @@ static void *janus_videoroom_handler(void *data) {
 						continue;
 					}
 				}
-			} else if(!strcasecmp(ptype_text, "muxed-listener")) {
-				/* This is a new Multiplexed listener */
-				JANUS_LOG(LOG_INFO, "Configuring new Multiplexed listener\n");
-				/* Any feed we want to attach to already? */
-				GList *list = NULL;
-				JANUS_VALIDATE_JSON_OBJECT(root, feeds_parameters,
-					error_code, error_cause, TRUE,
-					JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
-				if(error_code != 0)
-					goto error;
-				json_t *feeds = json_object_get(root, "feeds");
-				if(feeds && json_array_size(feeds) > 0) {
-					unsigned int i = 0;
-					int problem = 0;
-					for(i=0; i<json_array_size(feeds); i++) {
-						if(videoroom->destroyed) {
-							problem = 1;
-							JANUS_LOG(LOG_ERR, "Room destroyed");
-							error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
-							g_snprintf(error_cause, 512, "Room destroyed");
-							break;
-						}
-						json_t *feed = json_array_get(feeds, i);
-						if(!feed || !json_is_integer(feed)) {
-							problem = 1;
-							JANUS_LOG(LOG_ERR, "Invalid element (feeds in the array must be integers)\n");
-							error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
-							g_snprintf(error_cause, 512, "Invalid element (feeds in the array must be integers)");
-							break;
-						}
-						uint64_t feed_id = json_integer_value(feed);
-						janus_mutex_lock(&videoroom->participants_mutex);
-						janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, &feed_id);
-						janus_mutex_unlock(&videoroom->participants_mutex);
-						if(publisher == NULL) { //~ || publisher->sdp == NULL) {
-							/* FIXME For muxed listeners, we accept subscriptions to existing participants who haven't published yet */
-							problem = 1;
-							JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
-							error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
-							g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", feed_id);
-							break;
-						}
-						list = g_list_prepend(list, GUINT_TO_POINTER(feed_id));
-						JANUS_LOG(LOG_INFO, "  -- Subscribing to feed %"SCNu64"\n", feed_id);
-					}
-					if(problem) {
-						goto error;
-					}
-				}
-				/* Allocate listener */
-				janus_videoroom_listener_muxed *listener = g_malloc0(sizeof(janus_videoroom_listener_muxed));
-				listener->session = session;
-				listener->room = videoroom;
-				session->participant_type = janus_videoroom_p_type_subscriber_muxed;
-				session->participant = listener;
-				/* Ack that we created the listener */
-				event = json_object();
-				json_object_set_new(event, "videoroom", json_string("muxed-created"));
-				json_object_set_new(event, "room", json_integer(videoroom->room_id));
-				JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
-				/* How long will the gateway take to push the event? */
-				gint64 start = janus_get_monotonic_time();
-				int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, NULL);
-				JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
-				json_decref(event);
-				/* Attach to feeds if needed */
-				if(list != NULL) {
-					JANUS_LOG(LOG_INFO, "Subscribing to %d feeds\n", g_list_length(list));
-					list = g_list_reverse(list);
-					if(videoroom->destroyed || janus_videoroom_muxed_subscribe(listener, list, msg->transaction) < 0) {
-						JANUS_LOG(LOG_ERR, "Error subscribing!\n");
-						error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;	/* FIXME */
-						g_snprintf(error_cause, 512, "Error subscribing!");
-						goto error;
-					}
-				}
-				janus_videoroom_message_free(msg);
-				continue;
 			} else {
 				JANUS_LOG(LOG_ERR, "Invalid element (ptype)\n");
 				error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
@@ -3635,164 +3335,6 @@ static void *janus_videoroom_handler(void *data) {
 				g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
 				goto error;
 			}
-		} else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
-			/* Handle this Multiplexed listener */
-			janus_videoroom_listener_muxed *listener = (janus_videoroom_listener_muxed *)session->participant;
-			if(listener == NULL) {
-				JANUS_LOG(LOG_ERR, "Invalid Multiplexed listener instance\n");
-				error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
-				g_snprintf(error_cause, 512, "Invalid Multiplexed listener instance");
-				goto error;
-			}
-			if(!strcasecmp(request_text, "join")) {
-				JANUS_LOG(LOG_ERR, "Already in as a Multiplexed listener on this handle\n");
-				error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED;
-				g_snprintf(error_cause, 512, "Already in as a Multiplexed listener on this handle");
-				goto error;
-			} else if(!strcasecmp(request_text, "add")) {
-				/* Add new streams to subscribe to */
-				GList *list = NULL;
-				JANUS_VALIDATE_JSON_OBJECT(root, feeds_parameters,
-					error_code, error_cause, TRUE,
-					JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
-				if(error_code != 0)
-					goto error;
-				json_t *feeds = json_object_get(root, "feeds");
-				unsigned int i = 0;
-				int problem = 0;
-				if(!listener->room) {
-					JANUS_LOG(LOG_ERR, "Room Destroyed ");
-					error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
-					g_snprintf(error_cause, 512, "No such room ");
-					goto error;
-				}
-				if(listener->room->destroyed) {
-					JANUS_LOG(LOG_ERR, "Room Destroyed (%"SCNu64")", listener->room->room_id);
-					error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
-					g_snprintf(error_cause, 512, "No such room (%"SCNu64")", listener->room->room_id);
-					goto error;
-				}
-				for(i=0; i<json_array_size(feeds); i++) {
-					json_t *feed = json_array_get(feeds, i);
-					if(listener->room->destroyed) {
-						problem = 1;
-						JANUS_LOG(LOG_ERR, "Room destroyed");
-						error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
-						g_snprintf(error_cause, 512, "Room destroyed");
-						break;
-					}
-					if(!feed || !json_is_integer(feed)) {
-						problem = 1;
-						JANUS_LOG(LOG_ERR, "Invalid element (feeds in the array must be integers)\n");
-						error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
-						g_snprintf(error_cause, 512, "Invalid element (feeds in the array must be integers)");
-						break;
-					}
-					uint64_t feed_id = json_integer_value(feed);
-					janus_mutex_lock(&listener->room->participants_mutex);
-					janus_videoroom_participant *publisher = g_hash_table_lookup(listener->room->participants, &feed_id);
-					janus_mutex_unlock(&listener->room->participants_mutex);
-					if(publisher == NULL) { //~ || publisher->sdp == NULL) {
-						/* FIXME For muxed listeners, we accept subscriptions to existing participants who haven't published yet */
-						problem = 1;
-						JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
-						error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
-						g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", feed_id);
-						break;
-					}
-					list = g_list_prepend(list, GUINT_TO_POINTER(feed_id));
-				}
-				if(problem) {
-					goto error;
-				}
-				list = g_list_reverse(list);
-				if(janus_videoroom_muxed_subscribe(listener, list, msg->transaction) < 0) {
-					JANUS_LOG(LOG_ERR, "Error subscribing!\n");
-					error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;	/* FIXME */
-					g_snprintf(error_cause, 512, "Error subscribing!");
-					goto error;
-				}
-				janus_videoroom_message_free(msg);
-				continue;
-			} else if(!strcasecmp(request_text, "remove")) {
-				/* Remove subscribed streams */
-				GList *list = NULL;
-				JANUS_VALIDATE_JSON_OBJECT(root, feeds_parameters,
-					error_code, error_cause, TRUE,
-					JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
-				if(error_code != 0)
-					goto error;
-				json_t *feeds = json_object_get(root, "feeds");
-				unsigned int i = 0;
-				int error = 0;
-				for(i=0; i<json_array_size(feeds); i++) {
-					json_t *feed = json_array_get(feeds, i);
-					if(!feed || !json_is_integer(feed)) {
-						error = 1;
-						break;
-					}
-					list = g_list_prepend(list, GUINT_TO_POINTER(json_integer_value(feed)));
-				}
-				if(error) {
-					JANUS_LOG(LOG_ERR, "Invalid element (feeds in the array must be integers)\n");
-					error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
-					g_snprintf(error_cause, 512, "Invalid element (feeds in the array must be integers)");
-					goto error;
-				}
-				list = g_list_reverse(list);
-				
-				if(!listener->room) {
-					JANUS_LOG(LOG_ERR, "Error unsubscribing!\n");
-					error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;	/* FIXME */
-					g_snprintf(error_cause, 512, "Error unsubscribing!");
-					goto error;
-				}
-				if(janus_videoroom_muxed_unsubscribe(listener, list, msg->transaction) < 0) {
-					JANUS_LOG(LOG_ERR, "Error unsubscribing!\n");
-					error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;	/* FIXME */
-					g_snprintf(error_cause, 512, "Error unsubscribing!");
-					goto error;
-				}
-				janus_videoroom_message_free(msg);
-				continue;
-			} else if(!strcasecmp(request_text, "start")) {
-				/* Start/restart receiving the publishers streams */
-				/* TODO */
-				event = json_object();
-				json_object_set_new(event, "videoroom", json_string("event"));
-				json_object_set_new(event, "room", json_integer(listener->room->room_id));
-				json_object_set_new(event, "started", json_string("ok"));
-				//~ /* Send a FIR */
-				//~ char buf[20];
-				//~ memset(buf, 0, 20);
-				//~ janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
-				//~ JANUS_LOG(LOG_VERB, "Resuming publisher, sending FIR to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
-				//~ gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
-				//~ /* Send a PLI too, just in case... */
-				//~ memset(buf, 0, 12);
-				//~ janus_rtcp_pli((char *)&buf, 12);
-				//~ JANUS_LOG(LOG_VERB, "Resuming publisher, sending PLI to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
-				//~ gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
-			} else if(!strcasecmp(request_text, "pause")) {
-				/* Stop receiving the publishers streams for a while */
-				/* TODO */
-				event = json_object();
-				json_object_set_new(event, "videoroom", json_string("event"));
-				json_object_set_new(event, "room", json_integer(listener->room->room_id));
-				json_object_set_new(event, "paused", json_string("ok"));
-			} else if(!strcasecmp(request_text, "leave")) {
-				/* TODO */
-				event = json_object();
-				json_object_set_new(event, "videoroom", json_string("event"));
-				json_object_set_new(event, "room", json_integer(listener->room->room_id));
-				json_object_set_new(event, "left", json_string("ok"));
-				session->started = FALSE;
-			} else {
-				JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
-				error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
-				g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
-				goto error;
-			}
 		}
 
 		/* Prepare JSON event */
@@ -3867,67 +3409,29 @@ static void *janus_videoroom_handler(void *data) {
 				if(strstr(msg_sdp, "Mozilla")) {
 					participant->firefox = TRUE;
 				}
-				/* Which media are available? */
-				int audio = 0, video = 0, data = 0;
-				const char *audio_mode = NULL, *video_mode = NULL;
-				gboolean audio_level_extmap = FALSE, video_orient_extmap = FALSE, playout_delay_extmap = FALSE;
-				char error_str[100];
-				janus_sdp *parsed_sdp = janus_sdp_parse(msg_sdp, error_str, sizeof(error_str));
-				if(!parsed_sdp) {
-					/* Invalid SDP */
-					JANUS_LOG(LOG_ERR, "Error parsing SDP: %s\n", error_str);
-					error_code = JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL;
-					g_snprintf(error_cause, 512, "Error parsing SDP: %s", error_str);
+				/* Start by parsing the offer */
+				char error_str[512];
+				janus_sdp *offer = janus_sdp_parse(msg_sdp, error_str, sizeof(error_str));
+				if(offer == NULL) {
+					json_decref(event);
+					JANUS_LOG(LOG_ERR, "Error parsing offer: %s\n", error_str);
+					error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP;
+					g_snprintf(error_cause, 512, "Error parsing offer: %s", error_str);
 					goto error;
 				}
-				GList *temp = parsed_sdp->m_lines;
+				gboolean audio_level_extmap = FALSE, video_orient_extmap = FALSE, playout_delay_extmap = FALSE;
+				GList *temp = offer->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) {
-						audio++;
 						participant->audio = TRUE;
-						if(audio > 1) {
-							temp = temp->next;
-							continue;
-						}
 					} else if(m->type == JANUS_SDP_VIDEO && m->port > 0) {
-						video++;
 						participant->video = TRUE;
-						if(video > 1) {
-							temp = temp->next;
-							continue;
-						}
 					} else if(m->type == JANUS_SDP_APPLICATION && m->port > 0) {
-						data++;
 						participant->data = TRUE;
-						if(data > 1) {
-							temp = temp->next;
-							continue;
-						}
 					}
 					if(m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) {
-						/* What is the direction? */
-						switch(m->direction) {
-							case JANUS_SDP_RECVONLY:
-								/* If we're getting a 'recvonly' publisher, we're going to answer with 'inactive' */
-							case JANUS_SDP_INACTIVE:
-								if(m->type == JANUS_SDP_AUDIO) {
-									audio_mode = "inactive";
-								} else {
-									video_mode = "inactive";
-								}
-								break;
-							case JANUS_SDP_SENDONLY:
-								/* What we expect, turn this into 'recvonly' */
-							case JANUS_SDP_SENDRECV:
-							default:
-								if(m->type == JANUS_SDP_AUDIO) {
-									audio_mode = "recvonly";
-								} else {
-									video_mode = "recvonly";
-								}
-								break;
-						}
 						/* Are the extmaps we care about there? */
 						GList *ma = m->attributes;
 						while(ma) {
@@ -3949,185 +3453,123 @@ static void *janus_videoroom_handler(void *data) {
 					}
 					temp = temp->next;
 				}
-				janus_sdp_free(parsed_sdp);
-				JANUS_LOG(LOG_VERB, "The publisher %s going to send an audio stream\n", audio ? "is" : "is NOT");
-				int opus_pt = 0, isac32_pt = 0, isac16_pt = 0, pcmu_pt = 0, pcma_pt = 0,
-					vp8_pt = 0, vp9_pt = 0, h264_pt = 0;
-				if(audio) {
-					JANUS_LOG(LOG_VERB, "  -- Will answer with media direction '%s'\n", audio_mode);
-					opus_pt = janus_get_codec_pt(msg_sdp, "opus");
-					if(opus_pt > 0) {
-						JANUS_LOG(LOG_VERB, "  -- -- Opus payload type is %d\n", opus_pt);
-					}
-					isac32_pt = janus_get_codec_pt(msg_sdp, "isac32");
-					if(isac32_pt > 0) {
-						JANUS_LOG(LOG_VERB, "  -- -- ISAC 32K payload type is %d\n", isac32_pt);
-					}
-					isac16_pt = janus_get_codec_pt(msg_sdp, "isac16");
-					if(isac16_pt > 0) {
-						JANUS_LOG(LOG_VERB, "  -- -- ISAC 16K payload type is %d\n", isac16_pt);
-					}
-					pcmu_pt = janus_get_codec_pt(msg_sdp, "pcmu");
-					if(pcmu_pt > 0) {
-						JANUS_LOG(LOG_VERB, "  -- -- PCMU payload type is %d\n", pcmu_pt);
-					}
-					pcma_pt = janus_get_codec_pt(msg_sdp, "pcma");
-					if(pcma_pt > 0) {
-						JANUS_LOG(LOG_VERB, "  -- -- PCMA payload type is %d\n", pcma_pt);
-					}
-				}
-				JANUS_LOG(LOG_VERB, "The publisher %s going to send a video stream\n", video ? "is" : "is NOT");
-				if(video) {
-					JANUS_LOG(LOG_VERB, "  -- Will answer with media direction '%s'\n", video_mode);
-					vp8_pt = janus_get_codec_pt(msg_sdp, "vp8");
-					if(vp8_pt > 0) {
-						JANUS_LOG(LOG_VERB, "  -- -- VP8 payload type is %d\n", vp8_pt);
-					}
-					vp9_pt = janus_get_codec_pt(msg_sdp, "vp9");
-					if(vp9_pt > 0) {
-						JANUS_LOG(LOG_VERB, "  -- -- VP9 payload type is %d\n", vp9_pt);
-					}
-					h264_pt = janus_get_codec_pt(msg_sdp, "h264");
-					if(h264_pt > 0) {
-						JANUS_LOG(LOG_VERB, "  -- -- H264 payload type is %d\n", h264_pt);
+				/* Prepare an answer now: force the room codecs and recvonly on the Janus side */
+				JANUS_LOG(LOG_VERB, "The publisher %s going to send an audio stream\n", participant->audio ? "is" : "is NOT");
+				JANUS_LOG(LOG_VERB, "The publisher %s going to send a video stream\n", participant->video ? "is" : "is NOT");
+				JANUS_LOG(LOG_VERB, "The publisher %s going to open a data channel\n", participant->data ? "is" : "is NOT");
+				janus_sdp *answer = janus_sdp_generate_answer(offer,
+					JANUS_SDP_OA_AUDIO_CODEC, janus_videoroom_audiocodec_name(videoroom->acodec),
+					JANUS_SDP_OA_AUDIO_DIRECTION, JANUS_SDP_RECVONLY,
+					JANUS_SDP_OA_VIDEO_CODEC, janus_videoroom_videocodec_name(videoroom->vcodec),
+					JANUS_SDP_OA_VIDEO_DIRECTION, JANUS_SDP_RECVONLY,
+					JANUS_SDP_OA_DONE);
+				janus_sdp_free(offer);
+				/* Replace the session name */
+				g_free(answer->s_name);
+				answer->s_name = g_strdup(videoroom->room_name);
+				/* Which media are REALLY available? (some may have been rejected) */
+				participant->audio = FALSE;
+				participant->video = FALSE;
+				participant->data = FALSE;
+				temp = answer->m_lines;
+				while(temp) {
+					janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
+					if(m->type == JANUS_SDP_AUDIO && m->port > 0) {
+						participant->audio = TRUE;
+					} else if(m->type == JANUS_SDP_VIDEO && m->port > 0) {
+						participant->video = TRUE;
+					} else if(m->type == JANUS_SDP_APPLICATION && m->port > 0) {
+						participant->data = TRUE;
 					}
+					temp = temp->next;
 				}
-				JANUS_LOG(LOG_VERB, "The publisher %s going to open a data channel\n", data ? "is" : "is NOT");
+				JANUS_LOG(LOG_VERB, "Per the answer, the publisher %s going to send an audio stream\n", participant->audio ? "is" : "is NOT");
+				JANUS_LOG(LOG_VERB, "Per the answer, the publisher %s going to send a video stream\n", participant->video ? "is" : "is NOT");
+				JANUS_LOG(LOG_VERB, "Per the answer, the publisher %s going to open a data channel\n", participant->data ? "is" : "is NOT");
 				/* Also add a bandwidth SDP attribute if we're capping the bitrate in the room */
-				int b = 0;
-				if(participant->firefox)	/* Don't add any b=AS attribute for Chrome */
-					b = (int)(videoroom->bitrate/1000);
-				char sdp[1280], audio_mline[256], video_mline[512], data_mline[256];
-				char *newsdp = NULL;
-				int res = 0;
-				int pass = 0;
-				for(pass = 1; pass <= 2; pass++) {
-					if(pass == 2) {
-						/* Now turn the SDP into what we'll send subscribers, using the static payload types for making switching easier */
-						if(audio_mode && strcmp(audio_mode, "inactive"))
-							/* The publisher gets a recvonly or inactive back */
-							/* Subscribers gets a sendonly or inactive back */
-							audio_mode = "sendonly";
-						if(video_mode && strcmp(video_mode, "inactive"))
-							video_mode = "sendonly";
-					}
-					audio_mline[0] = '\0';
-					if(audio) {
-						int pt = -1;
-						switch(videoroom->acodec) {
-							case JANUS_VIDEOROOM_OPUS:
-								if(opus_pt >= 0)
-									pt = (pass == 1 ? opus_pt : OPUS_PT);
-								break;
-							case JANUS_VIDEOROOM_ISAC_32K:
-								if(isac32_pt >= 0)
-									pt = (pass == 1 ? isac32_pt : ISAC32_PT);
-								break;
-							case JANUS_VIDEOROOM_ISAC_16K:
-								if(isac16_pt >= 0)
-									pt = (pass == 1 ? isac16_pt : ISAC16_PT);
-								break;
-							case JANUS_VIDEOROOM_PCMU:
-								if(pcmu_pt >= 0)
-									pt = (pass == 1 ? pcmu_pt : PCMU_PT);
-								break;
-							case JANUS_VIDEOROOM_PCMA:
-								if(pcma_pt >= 0)
-									pt = (pass == 1 ? pcma_pt : PCMA_PT);
-								break;
-							default:
-								/* Shouldn't happen */
-								break;
-						}
-						if(pass == 1 && pt < 0)
-							JANUS_LOG(LOG_WARN, "Videoroom is forcing %s, but publisher didn't offer any... rejecting audio\n", janus_videoroom_audiocodec_name(videoroom->acodec));
-						if(pt >= 0) {
-							janus_videoroom_sdp_a_format(audio_mline, 256, videoroom->acodec, pt, audio_mode,
-								audio_level_extmap, participant->audio_level_extmap_id);
-						}
-						if(audio_mline[0] == '\0' && pass == 1) {
-							/* Remove "pass == 1" if the listener also should get a line with port=0. */
-							g_snprintf(audio_mline, 256, "m=audio 0 RTP/SAVPF 0\r\n");
-						}
-					}
-					video_mline[0] = '\0';
-					if(video) {
-						int pt = -1;
-						switch(videoroom->vcodec) {
-							case JANUS_VIDEOROOM_VP8:
-								if(vp8_pt >= 0)
-									pt = (pass == 1 ? vp8_pt : VP8_PT);
-								break;
-							case JANUS_VIDEOROOM_VP9:
-								if(vp9_pt >= 0)
-									pt = (pass == 1 ? vp9_pt : VP9_PT);
-								break;
-							case JANUS_VIDEOROOM_H264:
-								if(h264_pt >= 0)
-									pt = (pass == 1 ? h264_pt : H264_PT);
-								break;
-							default:
-								/* Shouldn't happen */
-								break;
-						}
-						if(pass == 1 && pt < 0)
-							JANUS_LOG(LOG_WARN, "Videoroom is forcing %s, but publisher didn't offer any... rejecting video\n", janus_videoroom_videocodec_name(videoroom->vcodec));
-						if(pt >= 0) {
-							janus_videoroom_sdp_v_format(video_mline, 512, videoroom->vcodec, pt, b,video_mode,
-								video_orient_extmap, participant->video_orient_extmap_id,
-								playout_delay_extmap, participant->playout_delay_extmap_id);
-						}
-						if(video_mline[0] == '\0' && pass == 1) {
-							/* Remove "pass == 1" if the listener also should get a line with port=0. */
-							g_snprintf(video_mline, 512, "m=video 0 RTP/SAVPF 0\r\n");
-						}
-					}
-					if(data) {
-						g_snprintf(data_mline, 256, sdp_d_template);
-					} else {
-						data_mline[0] = '\0';
-					}
-					g_snprintf(sdp, 1280, sdp_template,
-						janus_get_real_time(),			/* We need current time here */
-						janus_get_real_time(),			/* We need current time here */
-						participant->room->room_name,	/* Video room name */
-						audio_mline,					/* Audio m-line, if any */
-						video_mline,					/* Video m-line, if any */
-						data_mline);					/* Data channel m-line, if any */
-					newsdp = g_strdup(sdp);
-					if(video && b == 0) {
-						/* Remove useless bandwidth attribute */
-						newsdp = janus_string_replace(newsdp, "b=AS:0\r\n", "");
-					}
-					if(pass == 2)
-						break;
-					/* Is this room recorded? */
-					janus_mutex_lock(&participant->rec_mutex);
-					if(videoroom->record || participant->recording_active) {
-						janus_videoroom_recorder_create(participant, audio, video, data);
+				if(participant->firefox) {	/* Don't add any b=AS attribute for Chrome */
+					janus_sdp_mline *m = janus_sdp_mline_find(answer, JANUS_SDP_VIDEO);
+					if(m != NULL && videoroom->bitrate > 0) {
+						m->b_name = g_strdup("AS");
+						m->b_value = (int)(videoroom->bitrate/1000);
 					}
-					janus_mutex_unlock(&participant->rec_mutex);
-
-					JANUS_LOG(LOG_VERB, "Handling publisher: turned this into an '%s':\n%s\n", type, newsdp);
-					json_t *jsep = json_pack("{ssss}", "type", type, "sdp", newsdp);
-					/* How long will the gateway take to push the event? */
-					g_atomic_int_set(&session->hangingup, 0);
-					gint64 start = janus_get_monotonic_time();
-					res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, jsep);
-					JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
-					json_decref(event);
-					json_decref(jsep);
-					g_free(newsdp);
 				}
+				/* Add the extmap attributes, if needed */
+				if(audio_level_extmap) {
+					janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
+						"%d %s\r\n", participant->audio_level_extmap_id, JANUS_RTP_EXTMAP_AUDIO_LEVEL);
+					janus_sdp_attribute_add_to_mline(janus_sdp_mline_find(answer, JANUS_SDP_AUDIO), a);
+				}
+				if(video_orient_extmap) {
+					janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
+						"%d %s\r\n", participant->video_orient_extmap_id, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION);
+					janus_sdp_attribute_add_to_mline(janus_sdp_mline_find(answer, JANUS_SDP_VIDEO), a);
+				}
+				if(playout_delay_extmap) {
+					janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
+						"%d %s\r\n", participant->playout_delay_extmap_id, JANUS_RTP_EXTMAP_PLAYOUT_DELAY);
+					janus_sdp_attribute_add_to_mline(janus_sdp_mline_find(answer, JANUS_SDP_VIDEO), a);
+				}
+				/* Generate an SDP string we can send back to the publisher */
+				char *answer_sdp = janus_sdp_write(answer);
+				/* Now turn the SDP into what we'll send subscribers, using the static payload types for making switching easier */
+				offer = janus_sdp_generate_offer(videoroom->room_name, answer->c_addr,
+					JANUS_SDP_OA_AUDIO, participant->audio,
+					JANUS_SDP_OA_AUDIO_CODEC, janus_videoroom_audiocodec_name(videoroom->acodec),
+					JANUS_SDP_OA_AUDIO_PT, janus_videoroom_audiocodec_pt(videoroom->acodec),
+					JANUS_SDP_OA_AUDIO_DIRECTION, JANUS_SDP_SENDONLY,
+					JANUS_SDP_OA_VIDEO, participant->video,
+					JANUS_SDP_OA_VIDEO_CODEC, janus_videoroom_videocodec_name(videoroom->vcodec),
+					JANUS_SDP_OA_VIDEO_PT, janus_videoroom_videocodec_pt(videoroom->vcodec),
+					JANUS_SDP_OA_VIDEO_DIRECTION, JANUS_SDP_SENDONLY,
+					JANUS_SDP_OA_DATA, participant->data,
+					JANUS_SDP_OA_DONE);
+				/* Add the extmap attributes, if needed */
+				if(audio_level_extmap) {
+					janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
+						"%d %s\r\n", participant->audio_level_extmap_id, JANUS_RTP_EXTMAP_AUDIO_LEVEL);
+					janus_sdp_attribute_add_to_mline(janus_sdp_mline_find(offer, JANUS_SDP_AUDIO), a);
+				}
+				if(video_orient_extmap) {
+					janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
+						"%d %s\r\n", participant->video_orient_extmap_id, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION);
+					janus_sdp_attribute_add_to_mline(janus_sdp_mline_find(offer, JANUS_SDP_VIDEO), a);
+				}
+				if(playout_delay_extmap) {
+					janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
+						"%d %s\r\n", participant->playout_delay_extmap_id, JANUS_RTP_EXTMAP_PLAYOUT_DELAY);
+					janus_sdp_attribute_add_to_mline(janus_sdp_mline_find(offer, JANUS_SDP_VIDEO), a);
+				}
+				/* Generate an SDP string we can offer subscribers later on */
+				char *offer_sdp = janus_sdp_write(offer);
+				janus_sdp_free(offer);
+				janus_sdp_free(answer);
+				/* Is this room recorded? */
+				janus_mutex_lock(&participant->rec_mutex);
+				if(videoroom->record || participant->recording_active) {
+					janus_videoroom_recorder_create(participant, participant->audio, participant->video, participant->data);
+				}
+				janus_mutex_unlock(&participant->rec_mutex);
+				/* Send the answer back to the publisher */
+				JANUS_LOG(LOG_VERB, "Handling publisher: turned this into an '%s':\n%s\n", type, answer_sdp);
+				json_t *jsep = json_pack("{ssss}", "type", type, "sdp", answer_sdp);
+				g_free(answer_sdp);
+				/* How long will the gateway take to push the event? */
+				g_atomic_int_set(&session->hangingup, 0);
+				gint64 start = janus_get_monotonic_time();
+				int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, jsep);
+				JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
 				/* Done */
 				if(res != JANUS_OK) {
 					/* TODO Failed to negotiate? We should remove this publisher */
-					g_free(newsdp);
+					g_free(offer_sdp);
 				} else {
 					/* Store the participant's SDP for interested listeners */
-					participant->sdp = newsdp;
+					participant->sdp = offer_sdp;
 					/* We'll wait for the setup_media event before actually telling listeners */
 				}
+				json_decref(event);
+				json_decref(jsep);
 			}
 		}
 		janus_videoroom_message_free(msg);
@@ -4152,238 +3594,6 @@ error:
 }
 
 
-/* Multiplexing helpers */
-int janus_videoroom_muxed_subscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction) {
-	if(!muxed_listener || !feeds)
-		return -1;
-	janus_mutex_lock(&muxed_listener->listeners_mutex);
-	JANUS_LOG(LOG_VERB, "Subscribing to %d feeds\n", g_list_length(feeds));
-	janus_videoroom *videoroom = muxed_listener->room;
-	GList *ps = feeds;
-	json_t *list = json_array();
-	int added_feeds = 0;
-	while(ps) {
-		uint64_t feed_id = GPOINTER_TO_UINT(ps->data);
-		janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, &feed_id);
-		if(publisher == NULL) { //~ || publisher->sdp == NULL) {
-			/* FIXME For muxed listeners, we accept subscriptions to existing participants who haven't published yet */
-			JANUS_LOG(LOG_WARN, "No such feed (%"SCNu64"), skipping\n", feed_id);
-			ps = ps->next;
-			continue;
-		}
-		/* Are we already subscribed? */
-		gboolean subscribed = FALSE;
-		GSList *ls = muxed_listener->listeners;
-		while(ls) {
-			janus_videoroom_listener *l = (janus_videoroom_listener *)ls->data;
-			if(l && (l->feed == publisher)) {
-				subscribed = TRUE;
-				JANUS_LOG(LOG_WARN, "Already subscribed to feed %"SCNu64", skipping\n", feed_id);
-				break;
-			}
-			ls = ls->next;
-		}
-		if(subscribed) {
-			ps = ps->next;
-			continue;
-		}
-		janus_videoroom_listener *listener = g_malloc0(sizeof(janus_videoroom_listener));
-		listener->session = muxed_listener->session;
-		listener->room = videoroom;
-		listener->feed = publisher;
-		//~ listener->paused = TRUE;	/* We need an explicit start from the listener */
-		listener->paused = FALSE;
-		listener->parent = muxed_listener;
-		janus_mutex_lock(&publisher->listeners_mutex);
-		publisher->listeners = g_slist_append(publisher->listeners, listener);
-		janus_mutex_unlock(&publisher->listeners_mutex);
-		muxed_listener->listeners = g_slist_append(muxed_listener->listeners, listener);
-		JANUS_LOG(LOG_VERB, "Now subscribed to %d feeds\n", g_slist_length(muxed_listener->listeners));
-		/* Add to feeds in the answer */
-		added_feeds++;
-		json_t *f = json_object();
-		json_object_set_new(f, "id", json_integer(feed_id));
-		if(publisher->display)
-			json_object_set_new(f, "display", json_string(publisher->display));
-		json_array_append_new(list, f);
-		ps = ps->next;
-	}
-	janus_mutex_unlock(&muxed_listener->listeners_mutex);
-	if(added_feeds == 0) {
-		/* Nothing changed */
-		return 0;
-	}
-	/* Prepare event */
-	json_t *event = json_object();
-	json_object_set_new(event, "videoroom", json_string("muxed-attached"));
-	json_object_set_new(event, "room", json_integer(videoroom->room_id));
-	json_object_set_new(event, "feeds", list);
-	JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
-	/* Send the updated offer */
-	return janus_videoroom_muxed_offer(muxed_listener, transaction, event);
-}
-
-int janus_videoroom_muxed_unsubscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction) {
-	janus_mutex_lock(&muxed_listener->listeners_mutex);
-	JANUS_LOG(LOG_VERB, "Unsubscribing from %d feeds\n", g_list_length(feeds));
-	janus_videoroom *videoroom = muxed_listener->room;
-	GList *ps = feeds;
-	json_t *list = json_array();
-	int removed_feeds = 0;
-	while(ps) {
-		uint64_t feed_id = GPOINTER_TO_UINT(ps->data);
-		GSList *ls = muxed_listener->listeners;
-		while(ls) {
-			janus_videoroom_listener *listener = (janus_videoroom_listener *)ls->data;
-			if(listener) {
-				janus_videoroom_participant *publisher = listener->feed;
-				if(publisher == NULL || publisher->user_id != feed_id) {
-					/* Not the publisher we're looking for */
-					ls = ls->next;
-					continue;
-				}
-				janus_mutex_lock(&publisher->listeners_mutex);
-				publisher->listeners = g_slist_remove(publisher->listeners, listener);
-				janus_mutex_unlock(&publisher->listeners_mutex);
-				listener->feed = NULL;
-				muxed_listener->listeners = g_slist_remove(muxed_listener->listeners, listener);
-				JANUS_LOG(LOG_VERB, "Now subscribed to %d feeds\n", g_slist_length(muxed_listener->listeners));
-				janus_videoroom_listener_free(listener);
-				/* Add to feeds in the answer */
-				removed_feeds++;
-				json_t *f = json_object();
-				json_object_set_new(f, "id", json_integer(feed_id));
-				json_array_append_new(list, f);
-				break;
-			}
-			ls = ls->next;
-		}
-		ps = ps->next;
-	}
-	janus_mutex_unlock(&muxed_listener->listeners_mutex);
-	if(removed_feeds == 0) {
-		/* Nothing changed */
-		return 0;
-	}
-	/* Prepare event */
-	json_t *event = json_object();
-	json_object_set_new(event, "videoroom", json_string("muxed-detached"));
-	json_object_set_new(event, "room", json_integer(videoroom->room_id));
-	json_object_set_new(event, "feeds", list);
-	JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
-	/* Send the updated offer */
-	return janus_videoroom_muxed_offer(muxed_listener, transaction, event);
-}
-
-int janus_videoroom_muxed_offer(janus_videoroom_listener_muxed *muxed_listener, char *transaction, json_t *event) {
-	if(muxed_listener == NULL)
-		return -1;
-	/* Negotiate by placing a 'muxed' fake attribute for each publisher we subscribed to,
-	 * that will translate to multiple SSRCs when merging the SDP */
-	int audio = 0, video = 0;
-	char audio_muxed[1024], video_muxed[1024], temp[255];
-	char sdp[2048], audio_mline[512], video_mline[512], data_mline[1];
-	data_mline[0] = '\0'; /* Multiplexed streams do not support data channels */
-	memset(audio_muxed, 0, 1024);
-	memset(video_muxed, 0, 1024);
-	memset(audio_mline, 0, 512);
-	memset(video_mline, 0, 512);
-	/* Prepare the m-lines (FIXME this will result in an audio line even for video-only rooms, but we don't care) */
-	int pt = -1;
-	switch(muxed_listener->room->acodec) {
-		case JANUS_VIDEOROOM_OPUS:
-			pt = OPUS_PT;
-			break;
-		case JANUS_VIDEOROOM_ISAC_32K:
-			pt = ISAC32_PT;
-			break;
-		case JANUS_VIDEOROOM_ISAC_16K:
-			pt = ISAC16_PT;
-			break;
-		case JANUS_VIDEOROOM_PCMU:
-			pt = PCMU_PT;
-			break;
-		case JANUS_VIDEOROOM_PCMA:
-			pt = PCMA_PT;
-			break;
-		default:
-			/* Shouldn't happen */
-			break;
-	}
-	janus_videoroom_sdp_a_format(audio_mline, 512, muxed_listener->room->acodec, pt, "sendonly", FALSE, 0);
-	pt = -1;
-	switch(muxed_listener->room->vcodec) {
-		case JANUS_VIDEOROOM_VP8:
-			pt = VP8_PT;
-			break;
-		case JANUS_VIDEOROOM_VP9:
-			pt = VP9_PT;
-			break;
-		case JANUS_VIDEOROOM_H264:
-			pt = H264_PT;
-			break;
-		default:
-			/* Shouldn't happen */
-			break;
-	}
-	janus_videoroom_sdp_v_format(video_mline, 512, muxed_listener->room->vcodec, pt, 0, "sendonly", FALSE, 0, FALSE, 0);
-	/* FIXME Add a fake user/SSRC just to avoid the "Failed to set max send bandwidth for video content" bug */
-	g_strlcat(audio_muxed, "a=planb:sfu0 1\r\n", 1024);
-	g_strlcat(video_muxed, "a=planb:sfu0 2\r\n", 1024);
-	/* Go through all the available publishers */
-	GSList *ps = muxed_listener->listeners;
-	while(ps) {
-		janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
-		if(l && l->feed) { //~ && l->feed->sdp) {
-			if(strstr(l->feed->sdp, "m=audio")) {
-				audio++;
-				g_snprintf(temp, 255, "a=planb:sfu%"SCNu64" %"SCNu32"\r\n", l->feed->user_id, l->feed->audio_ssrc);
-				g_strlcat(audio_muxed, temp, 1024);
-			}
-			if(strstr(l->feed->sdp, "m=video")) {
-				video++;
-				g_snprintf(temp, 255, "a=planb:sfu%"SCNu64" %"SCNu32"\r\n", l->feed->user_id, l->feed->video_ssrc);
-				g_strlcat(video_muxed, temp, 1024);
-			}
-		}
-		ps = ps->next;
-	}
-	/* Also add a bandwidth SDP attribute if we're capping the bitrate in the room */
-	if(audio) {
-		g_strlcat(audio_mline, audio_muxed, 2048);
-	}
-	if(video) {
-		g_strlcat(video_mline, video_muxed, 2048);
-	}
-	g_snprintf(sdp, 2048, sdp_template,
-		janus_get_real_time(),			/* We need current time here */
-		janus_get_real_time(),			/* We need current time here */
-		muxed_listener->room->room_name,	/* Video room name */
-		audio_mline,					/* Audio m-line */
-		video_mline,					/* Video m-line */
-		data_mline);					/* Data channel m-line */
-	char *newsdp = g_strdup(sdp);
-	if(video) {
-		/* Remove useless bandwidth attribute, if any */
-		newsdp = janus_string_replace(newsdp, "b=AS:0\r\n", "");
-	}
-	JANUS_LOG(LOG_VERB, "%s", newsdp);
-	json_t *jsep = json_pack("{ssss}", "type", "offer", "sdp", newsdp);
-	/* How long will the gateway take to push the event? */
-	gint64 start = janus_get_monotonic_time();
-	int res = gateway->push_event(muxed_listener->session->handle, &janus_videoroom_plugin, transaction, event, jsep);
-	JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
-	json_decref(event);
-	json_decref(jsep);
-	if(res != JANUS_OK) {
-		/* TODO Failed to negotiate? We should remove this listener */
-	} else {
-		/* Let's wait for the setup_media event */
-	}
-	return 0;
-}
-
-
 /* Helper to quickly relay RTP packets from publishers to subscribers */
 static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) {
 	janus_videoroom_rtp_relay_packet *packet = (janus_videoroom_rtp_relay_packet *)user_data;
@@ -4523,20 +3733,6 @@ static void janus_videoroom_listener_free(janus_videoroom_listener *l) {
 	g_free(l);
 }
 
-static void janus_videoroom_muxed_listener_free(janus_videoroom_listener_muxed *l) {
-	JANUS_LOG(LOG_VERB, "Freeing muxed-listener\n");
-	GSList *ls = l->listeners;
-	while(ls) {
-		janus_videoroom_listener *listener = (janus_videoroom_listener *)ls->data;
-		if(listener) {
-			janus_videoroom_listener_free(listener);
-		}
-		ls = ls->next;
-	}
-	g_slist_free(l->listeners);
-	g_free(l);
-}
-
 static void janus_videoroom_participant_free(janus_videoroom_participant *p) {
 	JANUS_LOG(LOG_VERB, "Freeing publisher\n");
 	g_free(p->display);
diff --git a/sdp-utils.c b/sdp-utils.c
index 1e72307..8c6116b 100644
--- a/sdp-utils.c
+++ b/sdp-utils.c
@@ -38,22 +38,7 @@ void janus_sdp_free(janus_sdp *sdp) {
 	temp = sdp->m_lines;
 	while(temp) {
 		janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
-		g_free(m->type_str);
-		g_free(m->proto);
-		g_free(m->c_addr);
-		g_free(m->b_name);
-		g_list_free_full(m->fmts, (GDestroyNotify)g_free);
-		m->fmts = NULL;
-		g_list_free(m->ptypes);
-		m->ptypes = NULL;
-		GList *temp2 = m->attributes;
-		while(temp2) {
-			janus_sdp_attribute *a = (janus_sdp_attribute *)temp2->data;
-			janus_sdp_attribute_destroy(a);
-			temp2 = temp2->next;
-		}
-		g_list_free(m->attributes);
-		g_free(m);
+		janus_sdp_mline_destroy(m);
 		temp = temp->next;
 	}
 	g_list_free(sdp->m_lines);
@@ -61,6 +46,55 @@ void janus_sdp_free(janus_sdp *sdp) {
 	g_free(sdp);
 }
 
+janus_sdp_mline *janus_sdp_mline_create(janus_sdp_mtype type, guint16 port, const char *proto, janus_sdp_mdirection direction) {
+	janus_sdp_mline *m = g_malloc0(sizeof(janus_sdp_mline));
+	m->type = type;
+	const char *type_str = janus_sdp_mtype_str(type);
+	if(type_str == NULL) {
+		JANUS_LOG(LOG_WARN, "Unknown media type, type_str will have to be set manually\n");
+	} else {
+		m->type_str = g_strdup(type_str);
+	}
+	m->port = port;
+	m->proto = proto ? g_strdup(proto) : NULL;
+	m->direction = direction;
+	return m;
+}
+
+void janus_sdp_mline_destroy(janus_sdp_mline *mline) {
+	if(!mline)
+		return;
+	g_free(mline->type_str);
+	g_free(mline->proto);
+	g_free(mline->c_addr);
+	g_free(mline->b_name);
+	g_list_free_full(mline->fmts, (GDestroyNotify)g_free);
+	mline->fmts = NULL;
+	g_list_free(mline->ptypes);
+	mline->ptypes = NULL;
+	GList *temp = mline->attributes;
+	while(temp) {
+		janus_sdp_attribute *a = (janus_sdp_attribute *)temp->data;
+		janus_sdp_attribute_destroy(a);
+		temp = temp->next;
+	}
+	g_list_free(mline->attributes);
+	g_free(mline);
+}
+
+janus_sdp_mline *janus_sdp_mline_find(janus_sdp *sdp, janus_sdp_mtype type) {
+	if(sdp == NULL)
+		return NULL;
+	GList *ml = sdp->m_lines;
+	while(ml) {
+		janus_sdp_mline *m = (janus_sdp_mline *)ml->data;
+		if(m->type == type)
+			return m;
+		ml = ml->next;
+	}
+	return NULL;
+}
+
 janus_sdp_attribute *janus_sdp_attribute_create(const char *name, const char *value, ...) {
 	if(!name)
 		return NULL;
@@ -85,6 +119,72 @@ void janus_sdp_attribute_destroy(janus_sdp_attribute *attr) {
 	g_free(attr);
 }
 
+int janus_sdp_attribute_add_to_mline(janus_sdp_mline *mline, janus_sdp_attribute *attr) {
+	if(!mline || !attr)
+		return -1;
+	mline->attributes = g_list_append(mline->attributes, attr);
+	return 0;
+}
+
+janus_sdp_mtype janus_sdp_parse_mtype(const char *type) {
+	if(type == NULL)
+		return JANUS_SDP_OTHER;
+	if(!strcasecmp(type, "audio"))
+		return JANUS_SDP_AUDIO;
+	if(!strcasecmp(type, "video"))
+		return JANUS_SDP_VIDEO;
+	if(!strcasecmp(type, "application"))
+		return JANUS_SDP_APPLICATION;
+	return JANUS_SDP_OTHER;
+}
+
+const char *janus_sdp_mtype_str(janus_sdp_mtype type) {
+	switch(type) {
+		case JANUS_SDP_AUDIO:
+			return "audio";
+		case JANUS_SDP_VIDEO:
+			return "video";
+		case JANUS_SDP_APPLICATION:
+			return "application";
+		case JANUS_SDP_OTHER:
+		default:
+			break;
+	}
+	return NULL;
+}
+
+janus_sdp_mdirection janus_sdp_parse_mdirection(const char *direction) {
+	if(direction == NULL)
+		return JANUS_SDP_INVALID;
+	if(!strcasecmp(direction, "sendrecv"))
+		return JANUS_SDP_SENDRECV;
+	if(!strcasecmp(direction, "sendonly"))
+		return JANUS_SDP_SENDONLY;
+	if(!strcasecmp(direction, "recvonly"))
+		return JANUS_SDP_RECVONLY;
+	if(!strcasecmp(direction, "inactive"))
+		return JANUS_SDP_INACTIVE;
+	return JANUS_SDP_INVALID;
+}
+
+const char *janus_sdp_mdirection_str(janus_sdp_mdirection direction) {
+	switch(direction) {
+		case JANUS_SDP_DEFAULT:
+		case JANUS_SDP_SENDRECV:
+			return "sendrecv";
+		case JANUS_SDP_SENDONLY:
+			return "sendonly";
+		case JANUS_SDP_RECVONLY:
+			return "recvonly";
+		case JANUS_SDP_INACTIVE:
+			return "inactive";
+		case JANUS_SDP_INVALID:
+		default:
+			break;
+	}
+	return NULL;
+}
+
 janus_sdp *janus_sdp_parse(const char *sdp, char *error, size_t errlen) {
 	if(!sdp)
 		return NULL;
@@ -94,6 +194,8 @@ janus_sdp *janus_sdp_parse(const char *sdp, char *error, size_t errlen) {
 		return NULL;
 	}
 	janus_sdp *imported = g_malloc0(sizeof(janus_sdp));
+	imported->o_ipv4 = TRUE;
+	imported->c_ipv4 = TRUE;
 
 	gboolean success = TRUE;
 	janus_sdp_mline *mline = NULL;
@@ -222,17 +324,11 @@ janus_sdp *janus_sdp_parse(const char *sdp, char *error, size_t errlen) {
 							success = FALSE;
 							break;
 						}
-						if(!strcasecmp(type, "audio"))
-							m->type = JANUS_SDP_AUDIO;
-						else if(!strcasecmp(type, "video"))
-							m->type = JANUS_SDP_VIDEO;
-						else if(!strcasecmp(type, "application"))
-							m->type = JANUS_SDP_APPLICATION;
-						else
-							m->type = JANUS_SDP_OTHER;
+						m->type = janus_sdp_parse_mtype(type);
 						m->type_str = g_strdup(type);
 						m->proto = g_strdup(proto);
 						m->direction = JANUS_SDP_SENDRECV;
+						m->c_ipv4 = TRUE;
 						if(m->port > 0) {
 							/* Now let's check the payload types/formats */
 							gchar **mline_parts = g_strsplit(line+2, " ", -1);
@@ -319,21 +415,10 @@ janus_sdp *janus_sdp_parse(const char *sdp, char *error, size_t errlen) {
 						char *semicolon = strchr(line, ':');
 						if(semicolon == NULL) {
 							/* Is this a media direction attribute? */
-							if(!strcasecmp(line, "sendrecv")) {
+							janus_sdp_mdirection direction = janus_sdp_parse_mdirection(line);
+							if(direction != JANUS_SDP_INVALID) {
 								g_free(a);
-								mline->direction = JANUS_SDP_SENDRECV;
-								break;
-							} else if(!strcasecmp(line, "sendonly")) {
-								g_free(a);
-								mline->direction = JANUS_SDP_SENDONLY;
-								break;
-							} else if(!strcasecmp(line, "recvonly")) {
-								g_free(a);
-								mline->direction = JANUS_SDP_RECVONLY;
-								break;
-							} else if(!strcasecmp(line, "inactive")) {
-								g_free(a);
-								mline->direction = JANUS_SDP_INACTIVE;
+								mline->direction = direction;
 								break;
 							}
 							a->name = g_strdup(line);
@@ -408,6 +493,154 @@ int janus_sdp_remove_payload_type(janus_sdp *sdp, int pt) {
 	return 0;
 }
 
+int janus_sdp_get_codec_pt(janus_sdp *sdp, const char *codec) {
+	if(sdp == NULL || codec == NULL)
+		return -1;
+	/* Check the format string (note that we only parse what browsers can negotiate) */
+	gboolean video = FALSE;
+	const char *format = NULL, *format2 = NULL;
+	if(!strcasecmp(codec, "opus")) {
+		format = "opus/48000/2";
+		format2 = "OPUS/48000/2";
+	} else if(!strcasecmp(codec, "pcmu")) {
+		/* We know the payload type is 0: we just need to make sure it's there */
+		format = "pcmu/8000";
+		format2 = "PCMU/8000";
+	} else if(!strcasecmp(codec, "pcma")) {
+		/* We know the payload type is 8: we just need to make sure it's there */
+		format = "pcma/8000";
+		format2 = "PCMA/8000";
+	} else if(!strcasecmp(codec, "g722")) {
+		/* We know the payload type is 9: we just need to make sure it's there */
+		format = "g722/8000";
+		format2 = "G722/8000";
+	} else if(!strcasecmp(codec, "isac16")) {
+		format = "isac/16000";
+		format2 = "ISAC/16000";
+	} else if(!strcasecmp(codec, "isac32")) {
+		format = "isac/32000";
+		format2 = "ISAC/32000";
+	} else if(!strcasecmp(codec, "dtmf")) {
+		format = "telephone-event/8000";
+		format2 = "TELEPHONE-EVENT/8000";
+	} else if(!strcasecmp(codec, "vp8")) {
+		video = TRUE;
+		format = "vp8/90000";
+		format2 = "VP8/90000";
+	} else if(!strcasecmp(codec, "vp9")) {
+		video = TRUE;
+		format = "vp9/90000";
+		format2 = "VP9/90000";
+	} else if(!strcasecmp(codec, "h264")) {
+		video = TRUE;
+		format = "h264/90000";
+		format2 = "H264/90000";
+	} else {
+		JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", codec);
+		return -1;
+	}
+	/* Check all m->lines */
+	GList *ml = sdp->m_lines;
+	while(ml) {
+		janus_sdp_mline *m = (janus_sdp_mline *)ml->data;
+		if((!video && m->type != JANUS_SDP_AUDIO) || (video && m->type != JANUS_SDP_VIDEO)) {
+			ml = ml->next;
+			continue;
+		}
+		/* Look in all rtpmap attributes */
+		GList *ma = m->attributes;
+		while(ma) {
+			janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data;
+			if(a->name != NULL && a->value != NULL && !strcasecmp(a->name, "rtpmap")) {
+				int pt = atoi(a->value);
+				if(strstr(a->value, format) || strstr(a->value, format2))
+					return pt;
+			}
+			ma = ma->next;
+		}
+		ml = ml->next;
+	}
+	return -1;
+}
+
+const char *janus_sdp_get_codec_name(janus_sdp *sdp, int pt) {
+	if(sdp == NULL || pt < 0)
+		return NULL;
+	if(pt == 0)
+		return "pcmu";
+	if(pt == 8)
+		return "pcma";
+	if(pt == 9)
+		return "g722";
+	GList *ml = sdp->m_lines;
+	while(ml) {
+		janus_sdp_mline *m = (janus_sdp_mline *)ml->data;
+		/* Look in all rtpmap attributes */
+		GList *ma = m->attributes;
+		while(ma) {
+			janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data;
+			if(a->name != NULL && a->value != NULL && !strcasecmp(a->name, "rtpmap")) {
+				int a_pt = atoi(a->value);
+				if(a_pt == pt) {
+					/* Found! */
+					if(strstr(a->value, "vp8") || strstr(a->value, "VP8"))
+						return "vp8";
+					if(strstr(a->value, "vp9") || strstr(a->value, "VP9"))
+						return "vp9";
+					if(strstr(a->value, "h264") || strstr(a->value, "H264"))
+						return "h264";
+					if(strstr(a->value, "opus") || strstr(a->value, "OPUS"))
+						return "opus";
+					if(strstr(a->value, "pcmu") || strstr(a->value, "PMCU"))
+						return "pcmu";
+					if(strstr(a->value, "pcma") || strstr(a->value, "PMCA"))
+						return "pcma";
+					if(strstr(a->value, "g722") || strstr(a->value, "G722"))
+						return "g722";
+					if(strstr(a->value, "isac/16") || strstr(a->value, "ISAC/16"))
+						return "isac16";
+					if(strstr(a->value, "isac/32") || strstr(a->value, "ISAC/32"))
+						return "isac32";
+					if(strstr(a->value, "telephone-event/8000") || strstr(a->value, "telephone-event/8000"))
+						return "dtmf";
+					JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", a->value);
+					return NULL;
+				}
+			}
+			ma = ma->next;
+		}
+		ml = ml->next;
+	}
+	return NULL;
+}
+
+const char *janus_sdp_get_codec_rtpmap(const char *codec) {
+	if(codec == NULL)
+		return NULL;
+	if(!strcasecmp(codec, "opus"))
+		return "opus/48000/2";
+	if(!strcasecmp(codec, "pcmu"))
+		return "PCMU/8000";
+	if(!strcasecmp(codec, "pcma"))
+		return "PCMA/8000";
+	if(!strcasecmp(codec, "g722"))
+		return "G722/8000";
+	if(!strcasecmp(codec, "isac16"))
+		return "ISAC/16000";
+	if(!strcasecmp(codec, "isac32"))
+		return "ISAC/32000";
+	if(!strcasecmp(codec, "dtmf"))
+		return "telephone-event/8000";
+	if(!strcasecmp(codec, "vp8"))
+		return "VP8/90000";
+	if(!strcasecmp(codec, "vp9"))
+		return "VP9/90000";
+	if(!strcasecmp(codec, "h264"))
+		return "H264/90000";
+	JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", codec);
+	return NULL;
+}
+
 char *janus_sdp_write(janus_sdp *imported) {
 	if(!imported)
 		return NULL;
@@ -461,7 +694,7 @@ char *janus_sdp_write(janus_sdp *imported) {
 			m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(0));
 			g_strlcat(sdp, " 0", JANUS_BUFSIZE);
 		} else {
-			if(strstr(m->proto, "RTP") != NULL) {
+			if(m->proto != NULL && strstr(m->proto, "RTP") != NULL) {
 				/* RTP profile, use payload types */
 				GList *ptypes = m->ptypes;
 				while(ptypes) {
@@ -493,26 +726,8 @@ char *janus_sdp_write(janus_sdp *imported) {
 				g_strlcat(sdp, buffer, JANUS_BUFSIZE);
 			}
 		}
-		/* a= */
-		const char *direction = NULL;
-		switch(m->direction) {
-			case JANUS_SDP_DEFAULT:
-				/* Dob't write the direction */
-				break;
-			case JANUS_SDP_SENDONLY:
-				direction = "sendonly";
-				break;
-			case JANUS_SDP_RECVONLY:
-				direction = "recvonly";
-				break;
-			case JANUS_SDP_INACTIVE:
-				direction = "inactive";
-				break;
-			case JANUS_SDP_SENDRECV:
-			default:
-				direction = "sendrecv";
-				break;
-		}
+		/* a= (note that we don't format the direction if it's JANUS_SDP_DEFAULT) */
+		const char *direction = m->direction != JANUS_SDP_DEFAULT ? janus_sdp_mdirection_str(m->direction) : NULL;
 		if(direction != NULL) {
 			g_snprintf(buffer, sizeof(buffer), "a=%s\r\n", direction);
 			g_strlcat(sdp, buffer, JANUS_BUFSIZE);
@@ -542,3 +757,398 @@ char *janus_sdp_write(janus_sdp *imported) {
 	}
 	return sdp;
 }
+
+janus_sdp *janus_sdp_new(const char *name, const char *address) {
+	janus_sdp *sdp = g_malloc0(sizeof(janus_sdp));
+	/* Fill in some predefined stuff */
+	sdp->version = 0;
+	sdp->o_name = g_strdup("-");
+	sdp->o_sessid = janus_get_real_time();
+	sdp->o_version = 1;
+	sdp->o_ipv4 = TRUE;
+	sdp->o_addr = g_strdup(address ? address : "127.0.0.1");
+	sdp->s_name = g_strdup(name ? name : "Janus session");
+	sdp->t_start = 0;
+	sdp->t_stop = 0;
+	sdp->c_ipv4 = TRUE;
+	sdp->c_addr = g_strdup(address ? address : "127.0.0.1");
+	/* Done */
+	return sdp;
+}
+
+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 */
+	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, h264_fmtp = TRUE;
+	const char *audio_codec = NULL, *video_codec = NULL;
+	int audio_pt = 111, video_pt = 96;
+	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_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_VIDEO_RTCPFB_DEFAULTS) {
+			video_rtcpfb = va_arg(args, gboolean);
+		} else if(property == JANUS_SDP_OA_VIDEO_H264_FMTP) {
+			h264_fmtp = va_arg(args, gboolean);
+		} else {
+			JANUS_LOG(LOG_WARN, "Unknown property %d for preparing SDP answer, ignoring...\n", property);
+		}
+		property = va_arg(args, int);
+	}
+	if(audio_codec == NULL)
+		audio_codec = "opus";
+	const char *audio_rtpmap = janus_sdp_get_codec_rtpmap(audio_codec);
+	if(do_audio && audio_rtpmap == NULL) {
+		JANUS_LOG(LOG_ERR, "Unsupported audio codec '%s', can't prepare an offer\n", audio_codec);
+		return NULL;
+	}
+	if(video_codec == NULL)
+		video_codec = "vp8";
+	const char *video_rtpmap = janus_sdp_get_codec_rtpmap(video_codec);
+	if(do_video && video_rtpmap == NULL) {
+		JANUS_LOG(LOG_ERR, "Unsupported video codec '%s', can't prepare an offer\n", video_codec);
+		return NULL;
+	}
+#ifndef HAVE_SCTP
+	do_data = FALSE;
+#endif
+
+	/* 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);
+		}
+		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);
+		m->attributes = g_list_append(m->attributes, a);
+		if(!strcasecmp(video_codec, "h264") && h264_fmtp) {
+			/* If it's H.264 and we were asked to, add the default fmtp profile as well */
+			a = janus_sdp_attribute_create("fmtp", "%d profile-level-id=42e01f;packetization-mode=1", video_pt);
+			m->attributes = g_list_append(m->attributes, a);
+		}
+		if(video_rtcpfb) {
+			/* Add rtcp-fb attributes */
+			a = janus_sdp_attribute_create("rtcp-fb", "%d ccm fir", video_pt);
+			m->attributes = g_list_append(m->attributes, a);
+			a = janus_sdp_attribute_create("rtcp-fb", "%d nack", video_pt);
+			m->attributes = g_list_append(m->attributes, a);
+			a = janus_sdp_attribute_create("rtcp-fb", "%d nack pli", video_pt);
+			m->attributes = g_list_append(m->attributes, a);
+			a = janus_sdp_attribute_create("rtcp-fb", "%d goog-remb", video_pt);
+			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, "DTLS/SCTP", JANUS_SDP_DEFAULT);
+		m->c_ipv4 = TRUE;
+		m->c_addr = g_strdup(offer->c_addr);
+		m->fmts = g_list_append(m->fmts, g_strdup("5000"));
+		/* Add an sctmap attribute */
+		janus_sdp_attribute *aa = janus_sdp_attribute_create("sctmap", "5000 webrtc-datachannel 16");
+		m->attributes = g_list_append(m->attributes, aa);
+		offer->m_lines = g_list_append(offer->m_lines, m);
+	}
+
+	/* Done */
+	va_end(args);
+
+	return offer;
+}
+
+janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...) {
+	if(offer == NULL)
+		return NULL;
+
+	/* 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, h264_fmtp = TRUE;
+	const char *audio_codec = NULL, *video_codec = 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_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_VIDEO_H264_FMTP) {
+			h264_fmtp = va_arg(args, gboolean);
+		} 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
+
+	janus_sdp *answer = g_malloc0(sizeof(janus_sdp));
+	/* 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;
+	answer->o_version = offer->o_version;
+	answer->o_ipv4 = offer->o_ipv4;
+	answer->o_addr = g_strdup(offer->o_addr ? offer->o_addr : "127.0.0.1");
+	answer->s_name = g_strdup(offer->s_name ? offer->s_name : "Janus session");
+	answer->t_start = 0;
+	answer->t_stop = 0;
+	answer->c_ipv4 = offer->c_ipv4;
+	answer->c_addr = g_strdup(offer->c_addr ? offer->c_addr : "127.0.0.1");
+
+	/* Now iterate on all media, and let's see what we should do */
+	int audio = 0, video = 0, data = 0;
+	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));
+		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 */
+		/* 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 */
+				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 */
+				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;
+				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;
+			/* What is the direction we were offered? And how were we asked to react?
+			 * Adapt the direction in our answer accordingly */
+			switch(m->direction) {
+				case JANUS_SDP_RECVONLY:
+					if(target_dir == JANUS_SDP_SENDRECV || target_dir == 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': using 'inactive'\n",
+							m->type == JANUS_SDP_AUDIO ? "Audio" : "Video",
+							janus_sdp_mdirection_str(m->direction), janus_sdp_mdirection_str(target_dir));
+						am->direction = JANUS_SDP_INACTIVE;
+					}
+					break;
+				case JANUS_SDP_SENDONLY:
+					if(target_dir == JANUS_SDP_SENDRECV || target_dir == 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': using 'inactive'\n",
+							m->type == JANUS_SDP_AUDIO ? "Audio" : "Video",
+							janus_sdp_mdirection_str(m->direction), janus_sdp_mdirection_str(target_dir));
+						am->direction = JANUS_SDP_INACTIVE;
+					}
+					break;
+				case JANUS_SDP_INACTIVE:
+					/* Peer inactive, set inactive in the answer to */
+					am->direction = JANUS_SDP_INACTIVE;
+					break;
+				case JANUS_SDP_SENDRECV:
+				default:
+					/* The peer is fine with everything, so use our constraint */
+					am->direction = target_dir;
+					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
+				 * pick some other codec we know about among the ones that were offered.
+				 * 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) {
+					/* We couldn't find our preferred codec, let's try something else */
+					if(m->type == JANUS_SDP_AUDIO) {
+						/* Opus not found, maybe mu-law? */
+						codec = "pcmu";
+						if(janus_sdp_get_codec_pt(offer, codec) < 0) {
+							/* mu-law not found, maybe a-law? */
+							codec = "pcma";
+							if(janus_sdp_get_codec_pt(offer, codec) < 0) {
+								/* a-law not found, maybe G.722? */
+								codec = "722";
+								if(janus_sdp_get_codec_pt(offer, codec) < 0) {
+									/* G.722 not found, maybe isac32? */
+									codec = "isac32";
+									if(janus_sdp_get_codec_pt(offer, codec) < 0) {
+										/* isac32 not found, maybe isac16? */
+										codec = "isac16";
+									}
+								}
+							}
+						}
+					} else {
+						/* VP8 not found, maybe VP9? */
+						codec = "vp9";
+						if(janus_sdp_get_codec_pt(offer, codec) < 0) {
+							/* VP9 not found either, maybe H.264? */
+							codec = "h264";
+						}
+					}
+				}
+			}
+			int pt = janus_sdp_get_codec_pt(offer, codec);
+			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");
+				am->port = 0;
+				am->direction = JANUS_SDP_INACTIVE;
+				temp = temp->next;
+				continue;
+			}
+			am->ptypes = g_list_append(am->ptypes, GINT_TO_POINTER(pt));
+			/* Add the related attributes */
+			if(m->type == JANUS_SDP_AUDIO) {
+				/* Add rtpmap attribute */
+				janus_sdp_attribute *a = janus_sdp_attribute_create("rtpmap", "%d %s", pt, janus_sdp_get_codec_rtpmap(codec));
+				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");
+					if(dtmf_pt >= 0) {
+						/* We do */
+						am->ptypes = g_list_append(am->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"));
+						am->attributes = g_list_append(am->attributes, a);
+					}
+				}
+			} else {
+				/* Add rtpmap attribute */
+				janus_sdp_attribute *a = janus_sdp_attribute_create("rtpmap", "%d %s", pt, janus_sdp_get_codec_rtpmap(codec));
+				am->attributes = g_list_append(am->attributes, a);
+				if(!strcasecmp(codec, "h264") && h264_fmtp) {
+					/* If it's H.264 and we were asked to, add the default fmtp profile as well */
+					a = janus_sdp_attribute_create("fmtp", "%d profile-level-id=42e01f;packetization-mode=1", pt);
+					am->attributes = g_list_append(am->attributes, a);
+				}
+				if(video_rtcpfb) {
+					/* Add rtcp-fb attributes */
+					a = janus_sdp_attribute_create("rtcp-fb", "%d ccm fir", pt);
+					am->attributes = g_list_append(am->attributes, a);
+					a = janus_sdp_attribute_create("rtcp-fb", "%d nack", pt);
+					am->attributes = g_list_append(am->attributes, a);
+					a = janus_sdp_attribute_create("rtcp-fb", "%d nack pli", pt);
+					am->attributes = g_list_append(am->attributes, a);
+					a = janus_sdp_attribute_create("rtcp-fb", "%d goog-remb", pt);
+					am->attributes = g_list_append(am->attributes, a);
+				}
+			}
+		} else {
+			/* This is for data, add formats and an sctmap attribute */
+			am->direction = JANUS_SDP_DEFAULT;
+			GList *fmt = m->fmts;
+			while(fmt) {
+				char *fmt_str = (char *)fmt->data;
+				if(fmt_str)
+					am->fmts = g_list_append(am->fmts, g_strdup(fmt_str));
+				fmt = fmt->next;
+			}
+			janus_sdp_attribute *aa = janus_sdp_attribute_create("sctmap", "5000 webrtc-datachannel 16");
+			am->attributes = g_list_append(am->attributes, aa);
+		}
+		temp = temp->next;
+	}
+
+	/* Done */
+	va_end(args);
+
+	return answer;
+}
diff --git a/sdp-utils.h b/sdp-utils.h
index 2c11f96..faf4aac 100644
--- a/sdp-utils.h
+++ b/sdp-utils.h
@@ -60,6 +60,14 @@ typedef enum janus_sdp_mtype {
 	/*! \brief m=whatever (we don't care, unsupported) */
 	JANUS_SDP_OTHER
 } janus_sdp_mtype;
+/*! \brief Helper method to get a janus_sdp_mtype from a string
+ * @param[in] type The type to parse as a string (e.g., "audio")
+ * @returns The corresponding janus_sdp_mtype value */
+janus_sdp_mtype janus_sdp_parse_mtype(const char *type);
+/*! \brief Helper method to get the string associated to a janus_sdp_mtype value
+ * @param[in] type The type to stringify
+ * @returns The type as a string, if valid, or NULL otherwise */
+const char *janus_sdp_mtype_str(janus_sdp_mtype type);
 
 /*! \brief Helper enumeration to quickly identify m-line directions */
 typedef enum janus_sdp_mdirection {
@@ -72,8 +80,18 @@ typedef enum janus_sdp_mdirection {
 	/*! \brief recvonly */
 	JANUS_SDP_RECVONLY,
 	/*! \brief inactive */
-	JANUS_SDP_INACTIVE
+	JANUS_SDP_INACTIVE,
+	/*! \brief invalid direction (when parsing) */
+	JANUS_SDP_INVALID
 } janus_sdp_mdirection;
+/*! \brief Helper method to get a janus_sdp_mdirection from a string
+ * @param[in] direction The direction to parse as a string (e.g., "sendrecv")
+ * @returns The corresponding janus_sdp_mdirection value */
+janus_sdp_mdirection janus_sdp_parse_mdirection(const char *direction);
+/*! \brief Helper method to get the string associated to a janus_sdp_mdirection value
+ * @param[in] direction The direction to stringify
+ * @returns The direction as a string, if valid, or NULL otherwise */
+const char *janus_sdp_mdirection_str(janus_sdp_mdirection direction);
 
 /*! \brief SDP m-line representation */
 typedef struct janus_sdp_mline {
@@ -102,6 +120,28 @@ typedef struct janus_sdp_mline {
 	/*! \brief List of m-line attributes */
 	GList *attributes;
 } janus_sdp_mline;
+/*! \brief Helper method to quickly create a janus_sdp_mline instance
+ * @note The \c type_str property of the new m-line is created automatically
+ * depending on the provided \c type attribute. If \c type is JANUS_SDP_OTHER,
+ * though, \c type_str will NOT we allocated, and will be up to the caller.
+ * @param[in] type Type of the media (audio/video/application) as a janus_sdp_mtype
+ * @param[in] port Port to advertise
+ * @param[in] proto Profile to advertise
+ * @param[in] type Direction of the media as a janus_sdp_direction
+ * @returns A pointer to a valid janus_sdp_mline instance, if successfull, NULL otherwise */
+janus_sdp_mline *janus_sdp_mline_create(janus_sdp_mtype type, guint16 port, const char *proto, janus_sdp_mdirection direction);
+/*! \brief Helper method to free a janus_sdp_mline instance
+ * @note This method does not remove the m-line from the janus_sdp instance, that's up to the caller
+ * @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.
+ * @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 SDP a= attribute representation */
 typedef struct janus_sdp_attribute {
@@ -119,6 +159,11 @@ janus_sdp_attribute *janus_sdp_attribute_create(const char *name, const char *va
  * @note This method does not remove the attribute from the global or m-line attributes, that's up to the caller
  * @param[in] attr The janus_sdp_attribute instance to free */
 void janus_sdp_attribute_destroy(janus_sdp_attribute *attr);
+/*! \brief Helper method to add an attribute to a media line
+ * @param[in] mline The m-line to add the attribute to
+ * @param[in] attr The attribute to add
+ * @returns 0 in case of success, -1 otherwise */
+int janus_sdp_attribute_add_to_mline(janus_sdp_mline *mline, janus_sdp_attribute *attr);
 
 /*! \brief Method to parse an SDP string to a janus_sdp object
  * @param[in] sdp The SDP string to parse
@@ -139,8 +184,102 @@ int janus_sdp_remove_payload_type(janus_sdp *sdp, int pt);
  * @returns A pointer to a string with the serialized SDP, if successful, NULL otherwise */
 char *janus_sdp_write(janus_sdp *sdp);
 
+/*! \brief Method to quickly generate a janus_sdp instance from a few selected fields
+ * @note This allocates the \c o_addr, \c s_name and \c c_addr properties: if you
+ * want to replace them, don't remember to \c g_free the original pointers first.
+ * @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_new(const char *name, const char *address);
+
 /*! \brief Method to free a Janus SDP object
  * @param[in] sdp The Janus SDP object to free */
 void janus_sdp_free(janus_sdp *sdp);
 
+/*! \brief When generating an offer or answer automatically, accept/reject audio if offered (depends on value that follows) */
+#define JANUS_SDP_OA_AUDIO					1
+/*! \brief When generating an offer or answer automatically, accept/reject video if offered (depends on value that follows) */
+#define JANUS_SDP_OA_VIDEO					2
+/*! \brief When generating an offer or answer automatically, accept/reject datachannels if offered (depends on value that follows) */
+#define JANUS_SDP_OA_DATA					3
+/*! \brief When generating an offer or answer automatically, use this direction for audio (depends on value that follows) */
+#define JANUS_SDP_OA_AUDIO_DIRECTION		4
+/*! \brief When generating an offer or answer automatically, use this direction for video (depends on value that follows) */
+#define JANUS_SDP_OA_VIDEO_DIRECTION		5
+/*! \brief When generating an offer or answer automatically, use this codec for audio (depends on value that follows) */
+#define JANUS_SDP_OA_AUDIO_CODEC			6
+/*! \brief When generating an offer or answer automatically, use this codec for video (depends on value that follows) */
+#define JANUS_SDP_OA_VIDEO_CODEC			7
+/*! \brief When generating an offer (this is ignored for answers), use this payload type for audio (depends on value that follows) */
+#define JANUS_SDP_OA_AUDIO_PT				8
+/*! \brief When generating an offer (this is ignored for answers), use this payload type for video (depends on value that follows) */
+#define JANUS_SDP_OA_VIDEO_PT			9
+/*! \brief When generating an offer or answer automatically, do or do not negotiate telephone events (FIXME telephone-event/8000 only) */
+#define JANUS_SDP_OA_AUDIO_DTMF				10
+/*! \brief When generating an offer or answer automatically, do or do not add the rtcpfb attributes we typically negotiate (fir, nack, pli, remb) */
+#define JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS	11
+/*! \brief When generating an offer or answer automatically, do or do not add the default fmtp attribute for H.264 (profile-level-id=42e01f;packetization-mode=1) */
+#define JANUS_SDP_OA_VIDEO_H264_FMTP		12
+/*! \brief MUST be used as the last argument in janus_sdp_generate_answer */
+#define JANUS_SDP_OA_DONE					0
+
+/*! \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);
+ \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.
+ * @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 responde (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.:
+ \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);
+ \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
+ * the header file for a complete list of how you can drive the answer.
+ * 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, ...);
+
+/*! \brief Helper to get the payload type associated to a specific codec
+ * @param sdp The Janus SDP instance to process
+ * @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);
+
+/*! \brief Helper to get the codec name associated to a specific payload type
+ * @param sdp The Janus SDP instance to process
+ * @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);
+
+/*! \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 */
+const char *janus_sdp_get_codec_rtpmap(const char *codec);
+
 #endif
diff --git a/utils.c b/utils.c
index 5d9fd05..10a69be 100644
--- a/utils.c
+++ b/utils.c
@@ -246,6 +246,11 @@ int janus_get_codec_pt(const char *sdp, const char *codec) {
 		video = 0;
 		format = "pcma/8000";
 		format2 = "PCMA/8000";
+	} else if(!strcasecmp(codec, "g722")) {
+		/* We know the payload type is 9: we just need to make sure it's there */
+		video = 0;
+		format = "g722/8000";
+		format2 = "G722/8000";
 	} else if(!strcasecmp(codec, "isac16")) {
 		video = 0;
 		format = "isac/16000";
@@ -318,6 +323,8 @@ const char *janus_get_codec_from_pt(const char *sdp, int pt) {
 		return "pcmu";
 	if(pt == 8)
 		return "pcma";
+	if(pt == 9)
+		return "g722";
 	/* Look for the mapping */
 	char rtpmap[50];
 	g_snprintf(rtpmap, 50, "a=rtpmap:%d ", pt);
@@ -343,6 +350,8 @@ const char *janus_get_codec_from_pt(const char *sdp, int pt) {
 						return "pcmu";
 					if(strstr(name, "pcma") || strstr(name, "PMCA"))
 						return "pcma";
+					if(strstr(name, "g722") || strstr(name, "G722"))
+						return "g722";
 					if(strstr(name, "isac/16") || strstr(name, "ISAC/16"))
 						return "isac16";
 					if(strstr(name, "isac/32") || strstr(name, "ISAC/32"))

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-voip/janus.git



More information about the Pkg-voip-commits mailing list