Beispiel #1
0
char *janus_sdp_merge(janus_ice_handle *handle, const char *origsdp) {
	if(handle == NULL || origsdp == NULL)
		return NULL;
	sdp_session_t *anon = NULL;
	sdp_parser_t *parser = sdp_parse(home, origsdp, strlen(origsdp), 0);
	if(!(anon = sdp_session(parser))) {
		JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error parsing/merging SDP: %s\n", handle->handle_id, sdp_parsing_error(parser));
		sdp_parser_free(parser);
		return NULL;
	}
	/* Prepare SDP to merge */
	gchar buffer[512];
	memset(buffer, 0, 512);
	char *sdp = (char*)calloc(BUFSIZE, sizeof(char));
	if(sdp == NULL) {
		JANUS_LOG(LOG_FATAL, "Memory error!\n");
		sdp_parser_free(parser);
		return NULL;
	}
	sdp[0] = '\0';
	/* FIXME Any Plan B to take into account? */
	int planb = strstr(origsdp, "a=planb:") ? 1 : 0;
	if(planb) {
		janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_PLAN_B);
	} else {
		janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_PLAN_B);
	}
	/* Version v= */
	g_strlcat(sdp,
		"v=0\r\n", BUFSIZE);
	/* Origin o= */
	if(anon->sdp_origin) {
		g_snprintf(buffer, 512,
			"o=%s %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n",	/* FIXME Should we fix the address? */
				anon->sdp_origin->o_username ? anon->sdp_origin->o_username : "******",
				anon->sdp_origin->o_id, anon->sdp_origin->o_version);
		g_strlcat(sdp, buffer, BUFSIZE);
	} else {
		gint64 sessid = janus_get_monotonic_time();
		gint64 version = sessid;	/* FIXME This needs to be increased when it changes, so time should be ok */
		g_snprintf(buffer, 512,
			"o=%s %"SCNi64" %"SCNi64" IN IP4 127.0.0.1\r\n",	/* FIXME Should we fix the address? */
				"-", sessid, version);
		g_strlcat(sdp, buffer, BUFSIZE);
	}
	/* Session name s= */
	if(anon->sdp_subject && strlen(anon->sdp_subject) > 0) {
		g_snprintf(buffer, 512, "s=%s\r\n", anon->sdp_subject);
	} else {
		g_snprintf(buffer, 512, "s=%s\r\n", "Meetecho Janus");
	}
	g_strlcat(sdp, buffer, BUFSIZE);
	/* Timing t= */
	g_snprintf(buffer, 512,
		"t=%lu %lu\r\n", anon->sdp_time ? anon->sdp_time->t_start : 0, anon->sdp_time ? anon->sdp_time->t_stop : 0);
	g_strlcat(sdp, buffer, BUFSIZE);
	/* ICE Full or Lite? */
	if(janus_ice_is_ice_lite_enabled()) {
		/* Janus is acting in ICE Lite mode, advertize this */
		g_strlcat(sdp, "a=ice-lite\r\n", BUFSIZE);
	}
	/* bundle: add new global attribute */
	int audio = (strstr(origsdp, "m=audio") != NULL);
	int video = (strstr(origsdp, "m=video") != NULL);
#ifdef HAVE_SCTP
	int data = (strstr(origsdp, "DTLS/SCTP") && !strstr(origsdp, " 0 DTLS/SCTP"));
#else
	int data = 0;
#endif
	g_strlcat(sdp, "a=group:BUNDLE", BUFSIZE);
	if(audio) {
		g_snprintf(buffer, 512,
			" %s", handle->audio_mid ? handle->audio_mid : "audio");
		g_strlcat(sdp, buffer, BUFSIZE);
	}
	if(video) {
		g_snprintf(buffer, 512,
			" %s", handle->video_mid ? handle->video_mid : "video");
		g_strlcat(sdp, buffer, BUFSIZE);
	}
	if(data) {
		g_snprintf(buffer, 512,
			" %s", handle->data_mid ? handle->data_mid : "data");
		g_strlcat(sdp, buffer, BUFSIZE);
	}
	g_strlcat(sdp, "\r\n", BUFSIZE);
	/* msid-semantic: add new global attribute */
	g_strlcat(sdp,
		"a=msid-semantic: WMS janus\r\n",
		BUFSIZE);
	char wms[BUFSIZE];
	memset(wms, 0, BUFSIZE);
	g_strlcat(wms, "WMS", BUFSIZE);
	/* Copy other global attributes, if any */
	if(anon->sdp_attributes) {
		sdp_attribute_t *a = anon->sdp_attributes;
		while(a) {
			if(a->a_value == NULL) {
				g_snprintf(buffer, 512,
					"a=%s\r\n", a->a_name);
				g_strlcat(sdp, buffer, BUFSIZE);
			} else {
				g_snprintf(buffer, 512,
					"a=%s:%s\r\n", a->a_name, a->a_value);
				g_strlcat(sdp, buffer, BUFSIZE);
			}
			a = a->a_next;
		}
	}
	gboolean ipv6 = strstr(janus_get_public_ip(), ":") != NULL;
	/* Media lines now */
	if(anon->sdp_media) {
		int audio = 0, video = 0;
#ifdef HAVE_SCTP
		int data = 0;
#endif
		sdp_media_t *m = anon->sdp_media;
		janus_ice_stream *stream = NULL;
		while(m) {
			if(m->m_type == sdp_media_audio && m->m_port > 0) {
				audio++;
				if(audio > 1 || !handle->audio_id) {
					JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping audio line (we have %d audio lines, and the id is %d)\n", handle->handle_id, audio, handle->audio_id);
					g_strlcat(sdp, "m=audio 0 RTP/SAVPF 0\r\n", BUFSIZE);
					/* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */
					g_snprintf(buffer, 512,
						"c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
					g_strlcat(sdp, buffer, BUFSIZE);
					m = m->m_next;
					continue;
				}
				/* Audio */
				stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(handle->audio_id));
				if(stream == NULL) {
					JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping audio line (invalid stream %d)\n", handle->handle_id, handle->audio_id);
					g_strlcat(sdp, "m=audio 0 RTP/SAVPF 0\r\n", BUFSIZE);
					/* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */
					g_snprintf(buffer, 512,
						"c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
					g_strlcat(sdp, buffer, BUFSIZE);
					m = m->m_next;
					continue;
				}
				g_strlcat(sdp, "m=audio 1 RTP/SAVPF", BUFSIZE);
			} else if(m->m_type == sdp_media_video && m->m_port > 0) {
				video++;
				gint id = handle->video_id;
				if(id == 0 && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_BUNDLE))
					id = handle->audio_id > 0 ? handle->audio_id : handle->video_id;
				if(video > 1 || !id) {
					JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping video line (we have %d video lines, and the id is %d)\n", handle->handle_id, video,
						janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_BUNDLE) ? handle->audio_id : handle->video_id);
					g_strlcat(sdp, "m=video 0 RTP/SAVPF 0\r\n", BUFSIZE);
					/* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */
					g_snprintf(buffer, 512,
						"c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
					g_strlcat(sdp, buffer, BUFSIZE);
					m = m->m_next;
					continue;
				}
				/* Video */
				stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(id));
				if(stream == NULL) {
					JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping video line (invalid stream %d)\n", handle->handle_id, id);
					g_strlcat(sdp, "m=video 0 RTP/SAVPF 0\r\n", BUFSIZE);
					/* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */
					g_snprintf(buffer, 512,
						"c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
					g_strlcat(sdp, buffer, BUFSIZE);
					m = m->m_next;
					continue;
				}
				g_strlcat(sdp, "m=video 1 RTP/SAVPF", BUFSIZE);
#ifdef HAVE_SCTP
			} else if(m->m_type == sdp_media_application) {
				/* Is this SCTP for DataChannels? */
				if(m->m_port > 0 && m->m_proto_name != NULL && !strcasecmp(m->m_proto_name, "DTLS/SCTP") && m->m_port > 0) {
					/* Yep */
					data++;
					gint id = handle->data_id;
					if(id == 0 && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_BUNDLE))
						id = handle->audio_id > 0 ? handle->audio_id : handle->video_id;
					if(data > 1 || !id) {
						JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping SCTP line (we have %d SCTP lines, and the id is %d)\n", handle->handle_id, data, id);
						g_snprintf(buffer, 512,
							"m=%s 0 %s 0\r\n",
							m->m_type_name, m->m_proto_name);
						g_strlcat(sdp, buffer, BUFSIZE);
						/* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */
						g_snprintf(buffer, 512,
							"c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
						g_strlcat(sdp, buffer, BUFSIZE);
						m = m->m_next;
						continue;
					}
					/* SCTP */
					stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(id));
					if(stream == NULL) {
						JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping SCTP line (invalid stream %d)\n", handle->handle_id, id);
						g_snprintf(buffer, 512,
							"m=%s 0 %s 0\r\n",
							m->m_type_name, m->m_proto_name);
						g_strlcat(sdp, buffer, BUFSIZE);
						/* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */
						g_snprintf(buffer, 512,
							"c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
						g_strlcat(sdp, buffer, BUFSIZE);
						m = m->m_next;
						continue;
					}
					g_strlcat(sdp, "m=application 1 DTLS/SCTP", BUFSIZE);
				} else {
					JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping unsupported application media line...\n", handle->handle_id);
					g_snprintf(buffer, 512,
						"m=%s 0 %s 0\r\n",
						m->m_type_name, m->m_proto_name);
					g_strlcat(sdp, buffer, BUFSIZE);
					m = m->m_next;
					continue;
				}
#endif
			} else {
				JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping disabled/unsupported media line...\n", handle->handle_id);
				g_snprintf(buffer, 512,
					"m=%s 0 %s 0\r\n",
					m->m_type_name, m->m_proto_name);
				g_strlcat(sdp, buffer, BUFSIZE);
				/* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */
				g_snprintf(buffer, 512,
					"c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
				g_strlcat(sdp, buffer, BUFSIZE);
				m = m->m_next;
				continue;
			}
			/* Add formats now */
			if(!m->m_rtpmaps) {
				JANUS_LOG(LOG_VERB, "[%"SCNu64"] No RTP maps?? trying formats...\n", handle->handle_id);
				if(!m->m_format) {
					JANUS_LOG(LOG_ERR, "[%"SCNu64"] No formats either?? this sucks!\n", handle->handle_id);
					g_strlcat(sdp, " 0", BUFSIZE);	/* FIXME Won't work apparently */
				} else {
					sdp_list_t *fmt = m->m_format;
					while(fmt) {
						g_snprintf(buffer, 512, " %s", fmt->l_text);
						g_strlcat(sdp, buffer, BUFSIZE);
						fmt = fmt->l_next;
					}
				}
			} else {
				sdp_rtpmap_t *r = m->m_rtpmaps;
				while(r) {
					g_snprintf(buffer, 512, " %d", r->rm_pt);
					g_strlcat(sdp, buffer, BUFSIZE);
					r = r->rm_next;
				}
			}
			g_strlcat(sdp, "\r\n", BUFSIZE);
			/* Media connection c= */
			g_snprintf(buffer, 512,
				"c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
			g_strlcat(sdp, buffer, BUFSIZE);
			/* Any bandwidth? */
			if(m->m_bandwidths) {
				g_snprintf(buffer, 512,
					"b=%s:%lu\r\n",	/* FIXME Are we doing this correctly? */
						m->m_bandwidths->b_modifier_name ? m->m_bandwidths->b_modifier_name : "AS",
						m->m_bandwidths->b_value);
				g_strlcat(sdp, buffer, BUFSIZE);
			}
			/* a=mid:(audio|video|data) */
			switch(m->m_type) {
				case sdp_media_audio:
					g_snprintf(buffer, 512, "a=mid:%s\r\n", handle->audio_mid ? handle->audio_mid : "audio");
					break;
				case sdp_media_video:
					g_snprintf(buffer, 512, "a=mid:%s\r\n", handle->video_mid ? handle->video_mid : "video");
					break;
#ifdef HAVE_SCTP
				case sdp_media_application:
					/* FIXME sctpmap and webrtc-datachannel should be dynamic */
					g_snprintf(buffer, 512, "a=mid:%s\r\na=sctpmap:5000 webrtc-datachannel 16\r\n",
						handle->data_mid ? handle->data_mid : "data");
					break;
#endif
				default:
					break;
			}
			g_strlcat(sdp, buffer, BUFSIZE);
			if(m->m_type != sdp_media_application) {
				/* What is the direction? */
				switch(m->m_mode) {
					case sdp_sendonly:
						g_strlcat(sdp, "a=sendonly\r\n", BUFSIZE);
						break;
					case sdp_recvonly:
						g_strlcat(sdp, "a=recvonly\r\n", BUFSIZE);
						break;
					case sdp_inactive:
						g_strlcat(sdp, "a=inactive\r\n", BUFSIZE);
						break;
					case sdp_sendrecv:
					default:
						g_strlcat(sdp, "a=sendrecv\r\n", BUFSIZE);
						break;
				}
				/* rtcp-mux */
				g_snprintf(buffer, 512, "a=rtcp-mux\n");
				g_strlcat(sdp, buffer, BUFSIZE);
				/* RTP maps */
				if(m->m_rtpmaps) {
					sdp_rtpmap_t *rm = NULL;
					for(rm = m->m_rtpmaps; rm; rm = rm->rm_next) {
						g_snprintf(buffer, 512, "a=rtpmap:%u %s/%lu%s%s\r\n",
							rm->rm_pt, rm->rm_encoding, rm->rm_rate,
							rm->rm_params ? "/" : "", 
							rm->rm_params ? rm->rm_params : "");
						g_strlcat(sdp, buffer, BUFSIZE);
					}
					for(rm = m->m_rtpmaps; rm; rm = rm->rm_next) {
						if(rm->rm_fmtp) {
							g_snprintf(buffer, 512, "a=fmtp:%u %s\r\n", rm->rm_pt, rm->rm_fmtp);
							g_strlcat(sdp, buffer, BUFSIZE);
						}
					}
				}
			}
			/* ICE ufrag and pwd, DTLS fingerprint setup and connection a= */
			gchar *ufrag = NULL;
			gchar *password = NULL;
			nice_agent_get_local_credentials(handle->agent, stream->stream_id, &ufrag, &password);
			memset(buffer, 0, 100);
			g_snprintf(buffer, 512,
				"a=ice-ufrag:%s\r\n"
				"a=ice-pwd:%s\r\n"
				"a=ice-options:trickle\r\n"
				"a=fingerprint:sha-256 %s\r\n"
				"a=setup:%s\r\n"
				"a=connection:new\r\n",
					ufrag, password,
					janus_dtls_get_local_fingerprint(),
					janus_get_dtls_srtp_role(stream->dtls_role));
			if(ufrag != NULL)
				g_free(ufrag);
			ufrag = NULL;
			if(password != NULL)
				g_free(password);
			password = NULL;
			g_strlcat(sdp, buffer, BUFSIZE);
			/* Copy existing media attributes, if any */
			if(m->m_attributes) {
				sdp_attribute_t *a = m->m_attributes;
				while(a) {
					if(!strcmp(a->a_name, "planb")) {
						/* Skip the fake planb attribute, it's for internal use only */
						a = a->a_next;
						continue;
					}
					if(a->a_value == NULL) {
						g_snprintf(buffer, 512,
							"a=%s\r\n", a->a_name);
						g_strlcat(sdp, buffer, BUFSIZE);
					} else {
						g_snprintf(buffer, 512,
							"a=%s:%s\r\n", a->a_name, a->a_value);
						g_strlcat(sdp, buffer, BUFSIZE);
					}
					a = a->a_next;
				}
			}
			/* Add last attributes, rtcp and ssrc (msid) */
			if(!planb) {
				/* Single SSRC */
				if(m->m_type == sdp_media_audio && m->m_mode != sdp_inactive && m->m_mode != sdp_recvonly) {
					g_snprintf(buffer, 512,
						"a=ssrc:%"SCNu32" cname:janusaudio\r\n"
						"a=ssrc:%"SCNu32" msid:janus janusa0\r\n"
						"a=ssrc:%"SCNu32" mslabel:janus\r\n"
						"a=ssrc:%"SCNu32" label:janusa0\r\n",
							stream->audio_ssrc, stream->audio_ssrc, stream->audio_ssrc, stream->audio_ssrc);
					g_strlcat(sdp, buffer, BUFSIZE);
				} else if(m->m_type == sdp_media_video && m->m_mode != sdp_inactive && m->m_mode != sdp_recvonly) {
					g_snprintf(buffer, 512,
						"a=ssrc:%"SCNu32" cname:janusvideo\r\n"
						"a=ssrc:%"SCNu32" msid:janus janusv0\r\n"
						"a=ssrc:%"SCNu32" mslabel:janus\r\n"
						"a=ssrc:%"SCNu32" label:janusv0\r\n",
							stream->video_ssrc, stream->video_ssrc, stream->video_ssrc, stream->video_ssrc);
					g_strlcat(sdp, buffer, BUFSIZE);
				}
			} else {
				/* Multiple SSRCs */
				char mslabel[255];
				memset(mslabel, 0, 255);
				if(m->m_attributes) {
					char id[256];
					uint32_t ssrc = 0;
					sdp_attribute_t *a = m->m_attributes;
					while(a) {
						if(a->a_name == NULL || a->a_value == NULL || strcmp(a->a_name, "planb")) {
							a = a->a_next;
							continue;
						}
						if(sscanf(a->a_value, "%255s %"SCNu32"", id, &ssrc) != 2) {
							JANUS_LOG(LOG_ERR, "Error parsing 'planb' attribute, skipping it...\n");
							a = a->a_next;
							continue;
						}
						JANUS_LOG(LOG_VERB, "Parsing 'planb' attribute: %s\n", a->a_value);
						/* Add proper SSRC attributes */
						if(m->m_type == sdp_media_audio) {
							g_snprintf(buffer, 512,
								"a=ssrc:%"SCNu32" cname:%saudio\r\n"
								"a=ssrc:%"SCNu32" msid:%s %sa0\r\n"
								"a=ssrc:%"SCNu32" mslabel:%s\r\n"
								"a=ssrc:%"SCNu32" label:%sa0\r\n",
									ssrc, id, ssrc, id, id, ssrc, id, ssrc, id);
						} else if(m->m_type == sdp_media_video) {
							g_snprintf(buffer, 512,
								"a=ssrc:%"SCNu32" cname:%svideo\r\n"
								"a=ssrc:%"SCNu32" msid:%s %sv0\r\n"
								"a=ssrc:%"SCNu32" mslabel:%s\r\n"
								"a=ssrc:%"SCNu32" label:%sv0\r\n",
									ssrc, id, ssrc, id, id, ssrc, id, ssrc, id);
						}
						g_strlcat(sdp, buffer, BUFSIZE);
						/* Add to msid-semantic, if needed */
						if(!strstr(wms, id)) {
							g_snprintf(mslabel, 255, " %s", id);
							g_strlcat(wms, mslabel, BUFSIZE);
						}
						/* Go on */
						a = a->a_next;
					}
				}
			}
			/* And now the candidates */
			janus_ice_candidates_to_sdp(handle, sdp, stream->stream_id, 1);
			if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RTCPMUX) &&
					m->m_type != sdp_media_application)
				janus_ice_candidates_to_sdp(handle, sdp, stream->stream_id, 2);
			/* Next */
			m = m->m_next;
		}
	}

	/* Do we need to update the msid-semantic attribute? */
	if(planb) {
		sdp = janus_string_replace(sdp, "WMS janus", wms);
	}
	
	sdp_parser_free(parser);

	JANUS_LOG(LOG_VERB, " -------------------------------------------\n");
	JANUS_LOG(LOG_VERB, "  >> Merged (%zu --> %zu bytes)\n", strlen(origsdp), strlen(sdp));
	JANUS_LOG(LOG_VERB, " -------------------------------------------\n");
	JANUS_LOG(LOG_VERB, "%s\n", sdp);

	return sdp;
}
Beispiel #2
0
char *janus_sdp_merge(janus_ice_handle *handle, const char *origsdp) {
	if(handle == NULL || origsdp == NULL)
		return NULL;
	//~ su_home_t home[1] = { SU_HOME_INIT(home) };
	sdp_session_t *anon = NULL;
	sdp_parser_t *parser = sdp_parse(home, origsdp, strlen(origsdp), 0);
	if(!(anon = sdp_session(parser))) {
		JANUS_DEBUG("[%"SCNu64"] Error parsing/merging SDP: %s\n", handle->handle_id, sdp_parsing_error(parser));
		return NULL;
	}
	/* Prepare SDP to merge */
	gchar buffer[200];
	memset(buffer, 0, 200);
	char *sdp = (char*)calloc(BUFSIZE, sizeof(char));
	if(sdp == NULL) {
		JANUS_DEBUG("Memory error!\n");
		return NULL;
	}
	sdp[0] = '\0';
	/* Version v= */
	g_strlcat(sdp,
		"v=0\r\n", BUFSIZE);
	/* Origin o= */
	if(anon->sdp_origin) {
		g_sprintf(buffer,
			"o=%s %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n",	/* FIXME Should we fix the address? */
				anon->sdp_origin->o_username ? anon->sdp_origin->o_username : "******",
				anon->sdp_origin->o_id, anon->sdp_origin->o_version);
		g_strlcat(sdp, buffer, BUFSIZE);
	} else {
		gint64 sessid = g_get_monotonic_time();
		gint64 version = sessid;	/* FIXME This needs to be increased when it changes, so time should be ok */
		g_sprintf(buffer,
			"o=%s %"SCNi64" %"SCNi64" IN IP4 127.0.0.1\r\n",	/* FIXME Should we fix the address? */
				"-", sessid, version);
		g_strlcat(sdp, buffer, BUFSIZE);
	}
	/* Session name s= */
	g_sprintf(buffer,
		"s=%s\r\n", anon->sdp_subject ? anon->sdp_subject : "Meetecho Janus");
	g_strlcat(sdp, buffer, BUFSIZE);
	/* Timing t= */
	g_sprintf(buffer,
		"t=%lu %lu\r\n", anon->sdp_time ? anon->sdp_time->t_start : 0, anon->sdp_time ? anon->sdp_time->t_stop : 0);
	g_strlcat(sdp, buffer, BUFSIZE);
	/* Any global bandwidth? */
	//~ if(anon->sdp_bandwidths) {
		//~ g_sprintf(buffer,
			//~ "b=%s:%"SCNu64"\r\n",
				//~ anon->sdp_bandwidths->b_modifier_name ? anon->sdp_bandwidths->b_modifier_name : "AS",
				//~ anon->sdp_bandwidths->b_value);
		//~ g_strlcat(sdp, buffer, BUFSIZE);
	//~ }
	/* msid-semantic: add new global attribute */
	g_strlcat(sdp,
		"a=msid-semantic: WMS janus\r\n",
		BUFSIZE);
	//~ /* Connection c= (global) */
	//~ if(anon->sdp_connection) {
		//~ g_sprintf(buffer,
			//~ "c=IN IP4 %s\r\n", janus_get_local_ip());
		//~ g_strlcat(sdp, buffer, BUFSIZE);
	//~ }
	/* DTLS fingerprint a= (global) */
	g_sprintf(buffer,
		"a=fingerprint:sha-256 %s\r\n", janus_dtls_get_local_fingerprint());
	g_strlcat(sdp, buffer, BUFSIZE);
	/* Copy other global attributes, if any */
	if(anon->sdp_attributes) {
		sdp_attribute_t *a = anon->sdp_attributes;
		while(a) {
			if(a->a_value == NULL) {
				g_sprintf(buffer,
					"a=%s\r\n", a->a_name);
				g_strlcat(sdp, buffer, BUFSIZE);
			} else {
				g_sprintf(buffer,
					"a=%s:%s\r\n", a->a_name, a->a_value);
				g_strlcat(sdp, buffer, BUFSIZE);
			}
			a = a->a_next;
		}
	}
	/* Media lines now */
	if(anon->sdp_media) {
		int audio = 0, video = 0;
		sdp_media_t *m = anon->sdp_media;
		janus_ice_stream *stream = NULL;
		while(m) {
			if(m->m_type == sdp_media_audio) {
				audio++;
				if(audio > 1 || !handle->audio_id) {
					JANUS_DEBUG("[%"SCNu64"] Skipping audio line (we have %d audio lines, and the id is %d)\n", handle->handle_id, audio, handle->audio_id);
					g_strlcat(sdp, "m=audio 0 RTP/SAVPF 0\r\n", BUFSIZE);
					m = m->m_next;
					continue;
				}
				/* Audio */
				stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(handle->audio_id));
				if(stream == NULL) {
					JANUS_DEBUG("[%"SCNu64"] Skipping audio line (invalid stream %d)\n", handle->handle_id, handle->audio_id);
					g_strlcat(sdp, "m=audio 0 RTP/SAVPF 0\r\n", BUFSIZE);
					m = m->m_next;
					continue;
				}
				g_strlcat(sdp, "m=audio ARTPP RTP/SAVPF", BUFSIZE);
			} else if(m->m_type == sdp_media_video) {
				video++;
				if(video > 1 || !handle->video_id) {
					JANUS_DEBUG("[%"SCNu64"] Skipping video line (we have %d video lines, and the id is %d)\n", handle->handle_id, video, handle->video_id);
					g_strlcat(sdp, "m=video 0 RTP/SAVPF 0\r\n", BUFSIZE);
					m = m->m_next;
					continue;
				}
				/* Video */
				stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(handle->video_id));
				if(stream == NULL) {
					JANUS_DEBUG("[%"SCNu64"] Skipping video line (invalid stream %d)\n", handle->handle_id, handle->audio_id);
					g_strlcat(sdp, "m=video 0 RTP/SAVPF 0\r\n", BUFSIZE);
					m = m->m_next;
					continue;
				}
				g_strlcat(sdp, "m=video VRTPP RTP/SAVPF", BUFSIZE);
			} else {
				JANUS_DEBUG("[%"SCNu64"] Skipping unsupported media line...\n", handle->handle_id);
				g_sprintf(buffer,
					"m=%s 0 %s 0\r\n",
					m->m_type_name, m->m_proto_name);
				g_strlcat(sdp, buffer, BUFSIZE);
				m = m->m_next;
				continue;
			}
			/* Add formats now */
			if(!m->m_rtpmaps) {
				JANUS_PRINT("[%"SCNu64"] No RTP maps?? trying formats...\n", handle->handle_id);
				if(!m->m_format) {
					JANUS_DEBUG("[%"SCNu64"] No formats either?? this sucks!\n", handle->handle_id);
					g_strlcat(sdp, " 0", BUFSIZE);	/* FIXME Won't work apparently */
				} else {
					sdp_list_t *fmt = m->m_format;
					while(fmt) {
						g_sprintf(buffer, " %s", fmt->l_text);
						g_strlcat(sdp, buffer, BUFSIZE);
						fmt = fmt->l_next;
					}
				}
			} else {
				sdp_rtpmap_t *r = m->m_rtpmaps;
				while(r) {
					g_sprintf(buffer, " %d", r->rm_pt);
					g_strlcat(sdp, buffer, BUFSIZE);
					r = r->rm_next;
				}
			}
			g_strlcat(sdp, "\r\n", BUFSIZE);
			/* Any bandwidth? */
			if(m->m_bandwidths) {
				g_sprintf(buffer,
					"b=%s:%lu\r\n",	/* FIXME Are we doing this correctly? */
						m->m_bandwidths->b_modifier_name ? m->m_bandwidths->b_modifier_name : "AS",
						m->m_bandwidths->b_value);
				g_strlcat(sdp, buffer, BUFSIZE);
			}
			/* Media connection c= */
			//~ if(m->m_connections) {
				g_sprintf(buffer,
					"c=IN IP4 %s\r\n", janus_get_local_ip());
				g_strlcat(sdp, buffer, BUFSIZE);
			//~ }
			/* What is the direction? */
			switch(m->m_mode) {
				case sdp_inactive:
					g_strlcat(sdp, "a=inactive\r\n", BUFSIZE);
					break;
				case sdp_sendonly:
					g_strlcat(sdp, "a=sendonly\r\n", BUFSIZE);
					break;
				case sdp_recvonly:
					g_strlcat(sdp, "a=recvonly\r\n", BUFSIZE);
					break;
				case sdp_sendrecv:
				default:
					g_strlcat(sdp, "a=sendrecv\r\n", BUFSIZE);
					break;
			}
			/* RTCP */
			g_sprintf(buffer, "a=rtcp:%s IN IP4 %s\r\n",
				m->m_type == sdp_media_audio ? "ARTCP" : "VRTCP", janus_get_local_ip());
			g_strlcat(sdp, buffer, BUFSIZE);
			/* RTP maps */
			if(m->m_rtpmaps) {
				sdp_rtpmap_t *rm = NULL;
				for(rm = m->m_rtpmaps; rm; rm = rm->rm_next) {
					g_sprintf(buffer, "a=rtpmap:%u %s/%lu%s%s\r\n",
						rm->rm_pt, rm->rm_encoding, rm->rm_rate,
						rm->rm_params ? "/" : "", 
						rm->rm_params ? rm->rm_params : "");
					g_strlcat(sdp, buffer, BUFSIZE);
				}
				for(rm = m->m_rtpmaps; rm; rm = rm->rm_next) {
					if(rm->rm_fmtp) {
						g_sprintf(buffer, "a=fmtp:%u %s\r\n", rm->rm_pt, rm->rm_fmtp);
						g_strlcat(sdp, buffer, BUFSIZE);
					}
				}
			}
			/* ICE ufrag and pwd, DTLS setup and connection a= */
			gchar *ufrag = NULL;
			gchar *password = NULL;
			nice_agent_get_local_credentials(handle->agent, stream->stream_id, &ufrag, &password);
			memset(buffer, 0, 100);
			g_sprintf(buffer,
				"a=ice-ufrag:%s\r\n"
				"a=ice-pwd:%s\r\n"
				"a=setup:%s\r\n"
				"a=connection:new\r\n",
					ufrag, password,
					janus_get_dtls_srtp_role(stream->dtls_role));
			g_strlcat(sdp, buffer, BUFSIZE);
			/* Copy existing media attributes, if any */
			if(m->m_attributes) {
				sdp_attribute_t *a = m->m_attributes;
				while(a) {
					if(a->a_value == NULL) {
						g_sprintf(buffer,
							"a=%s\r\n", a->a_name);
						g_strlcat(sdp, buffer, BUFSIZE);
					} else {
						g_sprintf(buffer,
							"a=%s:%s\r\n", a->a_name, a->a_value);
						g_strlcat(sdp, buffer, BUFSIZE);
					}
					a = a->a_next;
				}
			}
			/* Add last attributes, rtcp and ssrc (msid) */
			if(m->m_type == sdp_media_audio) {
				g_sprintf(buffer,
					//~ "a=rtcp:ARTCP IN IP4 %s\r\n"
					"a=ssrc:%i cname:janusaudio\r\n"
					"a=ssrc:%i msid:janus janusa0\r\n"
					"a=ssrc:%i mslabel:janus\r\n"
					"a=ssrc:%i label:janusa0\r\n",
						//~ janus_get_local_ip(),
						stream->ssrc, stream->ssrc, stream->ssrc, stream->ssrc);
				g_strlcat(sdp, buffer, BUFSIZE);
			} else if(m->m_type == sdp_media_video) {
				g_sprintf(buffer,
					//~ "a=rtcp:VRTCP IN IP4 %s\r\n"
					"a=ssrc:%i cname:janusvideo\r\n"
					"a=ssrc:%i msid:janus janusv0\r\n"
					"a=ssrc:%i mslabel:janus\r\n"
					"a=ssrc:%i label:janusv0\r\n",
						//~ janus_get_local_ip(),
						stream->ssrc, stream->ssrc, stream->ssrc, stream->ssrc);
				g_strlcat(sdp, buffer, BUFSIZE);
			}
			/* And now the candidates */
			janus_ice_setup_candidate(handle, sdp, stream->stream_id, 1);
			janus_ice_setup_candidate(handle, sdp, stream->stream_id, 2);
			/* Next */
			m = m->m_next;
		}
	}
	JANUS_PRINT(" -------------------------------------------\n");
	JANUS_PRINT("  >> Merged (%zu --> %zu bytes)\n", strlen(origsdp), strlen(sdp));
	JANUS_PRINT(" -------------------------------------------\n");
	JANUS_PRINT("%s\n", sdp);
	return sdp;
}