conference.c 13.9 KB
Newer Older
Simon Morlat's avatar
Simon Morlat committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
/***************************************************************************
 *            conference.c
 *
 *  Mon Sep 12, 2011
 *  Copyright  2011  Belledonne Communications
 *  Author: Simon Morlat
 *  Email simon dot morlat at linphone dot org
 ****************************************************************************/

/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
 
#include "private.h"
27
#include "lpconfig.h"
Simon Morlat's avatar
Simon Morlat committed
28

29
#include "mediastreamer2/msvolume.h"
Simon Morlat's avatar
Simon Morlat committed
30

31 32 33 34 35 36
/**
 * @addtogroup conferencing
 * @{
**/


37 38
static int convert_conference_to_call(LinphoneCore *lc);

39
static void conference_check_init(LinphoneConference *ctx, int samplerate){
Simon Morlat's avatar
Simon Morlat committed
40
	if (ctx->conf==NULL){
41
		MSAudioConferenceParams params;
42
		params.samplerate=samplerate;
43
		ctx->conf=ms_audio_conference_new(&params);
Simon Morlat's avatar
Simon Morlat committed
44 45 46
	}
}

47 48 49 50 51 52 53
static void remove_local_endpoint(LinphoneConference *ctx){
	if (ctx->local_endpoint){
		ms_audio_conference_remove_member(ctx->conf,ctx->local_endpoint);
		ms_audio_endpoint_release_from_stream(ctx->local_endpoint);
		ctx->local_endpoint=NULL;
		audio_stream_stop(ctx->local_participant);
		ctx->local_participant=NULL;
54
		rtp_profile_destroy(ctx->local_dummy_profile);
55 56 57
	}
}

58 59 60 61 62 63 64
static int linphone_conference_get_size(LinphoneConference *conf){
	if (conf->conf == NULL) {
		return 0;
	}
	return ms_audio_conference_get_size(conf->conf) - (conf->record_endpoint ? 1 : 0);
}

65
static int remote_participants_count(LinphoneConference *ctx) {
66 67 68 69
	int count=linphone_conference_get_size(ctx);
	if (count==0) return 0;
	if (!ctx->local_participant) return count;
	return count -1;
70 71 72 73
}

void linphone_core_conference_check_uninit(LinphoneCore *lc){
	LinphoneConference *ctx=&lc->conf_ctx;
Simon Morlat's avatar
Simon Morlat committed
74
	if (ctx->conf){
75 76 77
		int remote_count=remote_participants_count(ctx);
		ms_message("conference_check_uninit(): size=%i",linphone_conference_get_size(ctx));
		if (remote_count==1){
78 79
			convert_conference_to_call(lc);
		}
80 81 82 83 84 85 86 87
		if (remote_count==0){
			if (ctx->local_participant!=NULL)
				remove_local_endpoint(ctx);
			if (ctx->record_endpoint){
				ms_audio_conference_remove_member(ctx->conf,ctx->record_endpoint);
				ms_audio_endpoint_destroy(ctx->record_endpoint);
				ctx->record_endpoint=NULL;
			}
88
		}
89
		
90
		if (ms_audio_conference_get_size(ctx->conf)==0){
Simon Morlat's avatar
Simon Morlat committed
91 92 93 94 95 96
			ms_audio_conference_destroy(ctx->conf);
			ctx->conf=NULL;
		}
	}
}

97
void linphone_call_add_to_conf(LinphoneCall *call, bool_t muted){
Simon Morlat's avatar
Simon Morlat committed
98 99 100
	LinphoneCore *lc=call->core;
	LinphoneConference *conf=&lc->conf_ctx;
	MSAudioEndpoint *ep;
101 102
	call->params.has_video = FALSE;
	call->camera_active = FALSE;
Simon Morlat's avatar
Simon Morlat committed
103 104
	ep=ms_audio_endpoint_get_from_stream(call->audiostream,TRUE);
	ms_audio_conference_add_member(conf->conf,ep);
105
	ms_audio_conference_mute_member(conf->conf,ep,muted);
Simon Morlat's avatar
Simon Morlat committed
106 107 108 109 110 111 112 113 114 115 116 117
	call->endpoint=ep;
}

void linphone_call_remove_from_conf(LinphoneCall *call){
	LinphoneCore *lc=call->core;
	LinphoneConference *conf=&lc->conf_ctx;
	
	ms_audio_conference_remove_member(conf->conf,call->endpoint);
	ms_audio_endpoint_release_from_stream(call->endpoint);
	call->endpoint=NULL;
}

118 119 120 121 122 123 124 125
static RtpProfile *make_dummy_profile(int samplerate){
	RtpProfile *prof=rtp_profile_new("dummy");
	PayloadType *pt=payload_type_clone(&payload_type_l16_mono);
	pt->clock_rate=samplerate;
	rtp_profile_set_payload(prof,0,pt);
	return prof;
}

126 127 128
static void add_local_endpoint(LinphoneConference *conf,LinphoneCore *lc){
	/*create a dummy audiostream in order to extract the local part of it */
	/* network address and ports have no meaning and are not used here. */
129
	AudioStream *st=audio_stream_new(65000,65001,FALSE);
130 131 132
	MSSndCard *playcard=lc->sound_conf.lsd_card ? 
			lc->sound_conf.lsd_card : lc->sound_conf.play_sndcard;
	MSSndCard *captcard=lc->sound_conf.capt_sndcard;
133
	const MSAudioConferenceParams *params=ms_audio_conference_get_params(conf->conf);
134
	conf->local_dummy_profile=make_dummy_profile(params->samplerate);
135
	
136
	audio_stream_start_full(st, conf->local_dummy_profile,
137 138
				"127.0.0.1",
				65000,
139
				"127.0.0.1",
140 141 142 143 144 145 146 147 148 149 150 151 152
				65001,
				0,
				40,
				NULL,
				NULL,
				playcard,
				captcard,
				linphone_core_echo_cancellation_enabled(lc)
				);
	_post_configure_audio_stream(st,lc,FALSE);
	conf->local_participant=st;
	conf->local_endpoint=ms_audio_endpoint_get_from_stream(st,FALSE);
	ms_audio_conference_add_member(conf->conf,conf->local_endpoint);
153
	
154 155
}

156 157 158 159 160
/**
 * Returns the sound volume (mic input) of the local participant of the conference.
 * @param lc the linphone core
 * @returns the measured input volume expressed in dbm0.
 **/
161 162 163
float linphone_core_get_conference_local_input_volume(LinphoneCore *lc){
	LinphoneConference *conf=&lc->conf_ctx;
	AudioStream *st=conf->local_participant;
164
	if (st && st->volsend && !conf->local_muted){
165 166 167 168 169 170 171 172
		float vol=0;
		ms_filter_call_method(st->volsend,MS_VOLUME_GET,&vol);
		return vol;
		
	}
	return LINPHONE_VOLUME_DB_LOWEST;
}

173 174 175 176 177 178 179
/**
 * Merge a call into a conference.
 * @param lc the linphone core
 * @param call an established call, either in LinphoneCallStreamsRunning or LinphoneCallPaused state.
 * 
 * If this is the first call that enters the conference, the virtual conference will be created automatically.
 * If the local user was actively part of the call (ie not in paused state), then the local user is automatically entered into the conference.
Simon Morlat's avatar
Simon Morlat committed
180
 * If the call was in paused state, then it is automatically resumed when entering into the conference.
181 182 183
 * 
 * @returns 0 if successful, -1 otherwise.
**/
Simon Morlat's avatar
Simon Morlat committed
184 185
int linphone_core_add_to_conference(LinphoneCore *lc, LinphoneCall *call){
	LinphoneCallParams params;
186 187
	LinphoneConference *conf=&lc->conf_ctx;
	
Simon Morlat's avatar
Simon Morlat committed
188 189 190 191
	if (call->current_params.in_conference){
		ms_error("Already in conference");
		return -1;
	}
192
	conference_check_init(&lc->conf_ctx, lp_config_get_int(lc->config, "sound","conference_rate",16000));
Simon Morlat's avatar
Simon Morlat committed
193 194
	call->params.in_conference=TRUE;
	call->params.has_video=FALSE;
195
	call->params.media_encryption=LinphoneMediaEncryptionNone;
Simon Morlat's avatar
Simon Morlat committed
196 197 198 199 200
	params=call->params;
	if (call->state==LinphoneCallPaused)
		linphone_core_resume_call(lc,call);
	else if (call->state==LinphoneCallStreamsRunning){
		/*this will trigger a reINVITE that will later redraw the streams */
201
		if (call->audiostream || call->videostream){
202
			linphone_call_stop_media_streams (call); /*free the audio & video local resources*/
203 204 205 206
		}
		if (call==lc->current_call){
			lc->current_call=NULL;
		}
Simon Morlat's avatar
Simon Morlat committed
207
		linphone_core_update_call(lc,call,&params);
208
		add_local_endpoint(conf,lc);
Simon Morlat's avatar
Simon Morlat committed
209 210 211 212 213 214 215
	}else{
		ms_error("Call is in state %s, it cannot be added to the conference.",linphone_call_state_to_string(call->state));
		return -1;
	}
	return 0;
}

216
static int remove_from_conference(LinphoneCore *lc, LinphoneCall *call, bool_t active){
217
	int err=0;
218

Simon Morlat's avatar
Simon Morlat committed
219 220 221 222 223 224 225 226 227 228
	if (!call->current_params.in_conference){
		if (call->params.in_conference){
			ms_warning("Not (yet) in conference, be patient");
			return -1;
		}else{
			ms_error("Not in a conference.");
			return -1;
		}
	}
	call->params.in_conference=FALSE;
229 230 231 232 233 234 235 236 237 238 239 240 241 242

	char *str=linphone_call_get_remote_address_as_string(call);
	ms_message("%s will be removed from conference", str);
	ms_free(str);
	if (active){
		// reconnect local audio with this call
		if (linphone_core_is_in_conference(lc)){
			ms_message("Leaving conference for reconnecting with unique call.");
			linphone_core_leave_conference(lc);
		}
		ms_message("Updating call to actually remove from conference");
		err=linphone_core_update_call(lc,call,&call->params);
	} else{
		ms_message("Pausing call to actually remove from conference");
243
		err=_linphone_core_pause_call(lc,call);
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
	}

	return err;
}

static int convert_conference_to_call(LinphoneCore *lc){
	int err=0;
	MSList *calls=lc->calls;

	if (remote_participants_count(&lc->conf_ctx)!=1){
		ms_error("No unique call remaining in conference.");
		return -1;
	}

	while (calls) {
		LinphoneCall *rc=(LinphoneCall*)calls->data;
		calls=calls->next;
		if (rc->params.in_conference) { // not using current_param
			bool_t active_after_removed=linphone_core_is_in_conference(lc);
			err=remove_from_conference(lc, rc, active_after_removed);
			break;
		}
	}
	return err;
}

270 271 272 273 274 275
/**
 * Remove a call from the conference.
 * @param lc the linphone core
 * @param call a call that has been previously merged into the conference.
 * 
 * After removing the remote participant belonging to the supplied call, the call becomes a normal call in paused state.
Simon Morlat's avatar
Simon Morlat committed
276 277
 * If one single remote participant is left alone together with the local user in the conference after the removal, then the conference is
 * automatically transformed into a simple call in StreamsRunning state.
278 279
 * The conference's resources are then automatically destroyed.
 * 
Simon Morlat's avatar
Simon Morlat committed
280 281 282
 * In other words, unless linphone_core_leave_conference() is explicitely called, the last remote participant of a conference is automatically
 * put in a simple call in running state.
 * 
283 284
 * @returns 0 if successful, -1 otherwise.
 **/
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
int linphone_core_remove_from_conference(LinphoneCore *lc, LinphoneCall *call){
	char * str=linphone_call_get_remote_address_as_string(call);
	ms_message("Removing call %s from the conference", str);
	ms_free(str);
	int err=remove_from_conference(lc,call, FALSE);
	if (err){
		ms_error("Error removing participant from conference.");
		return err;
	}

	if (remote_participants_count(&lc->conf_ctx)==1){
		ms_message("conference size is 1: need to be converted to plain call");
		err=convert_conference_to_call(lc);
	} else {
		ms_message("the conference need not to be converted as size is %i", remote_participants_count(&lc->conf_ctx));
	}
301
	return err;
Simon Morlat's avatar
Simon Morlat committed
302 303
}

304 305 306 307 308
/**
 * Indicates whether the local participant is part of the conference.
 * @param lc the linphone core
 * @returns TRUE if the local participant is in the conference, FALSE otherwise.
**/
309 310 311 312
bool_t linphone_core_is_in_conference(const LinphoneCore *lc){
	return lc->conf_ctx.local_participant!=NULL;
}

313 314 315 316 317 318
/**
 * Moves the local participant out of the conference.
 * @param lc the linphone core
 * When the local participant is out of the conference, the remote participants can continue to talk normally.
 * @returns 0 if successful, -1 otherwise.
**/
319 320 321 322
int linphone_core_leave_conference(LinphoneCore *lc){
	LinphoneConference *conf=&lc->conf_ctx;
	if (linphone_core_is_in_conference(lc))
		remove_local_endpoint(conf);
Simon Morlat's avatar
Simon Morlat committed
323 324 325
	return 0;
}

326 327 328 329 330 331 332 333 334 335 336
/**
 * Moves the local participant inside the conference.
 * @param lc the linphone core
 * 
 * Makes the local participant to join the conference. 
 * Typically, the local participant is by default always part of the conference when joining an active call into a conference.
 * However, by calling linphone_core_leave_conference() and linphone_core_enter_conference() the application can decide to temporarily
 * move out and in the local participant from the conference.
 * 
 * @returns 0 if successful, -1 otherwise
**/
337
int linphone_core_enter_conference(LinphoneCore *lc){
Guillaume Beraudo's avatar
Guillaume Beraudo committed
338
	if (linphone_core_sound_resources_locked(lc)) {
339 340
		return -1;
	}
341
	if (lc->current_call != NULL) {
342
		_linphone_core_pause_call(lc, lc->current_call);
343
	}
344 345
	LinphoneConference *conf=&lc->conf_ctx;
	if (conf->local_participant==NULL) add_local_endpoint(conf,lc);
Simon Morlat's avatar
Simon Morlat committed
346 347 348
	return 0;
}

349 350 351 352 353 354 355 356
/**
 * Add all calls into a conference.
 * @param lc the linphone core
 * 
 * Merge all established calls (either in LinphoneCallStreamsRunning or LinphoneCallPaused) into a conference.
 * 
 * @returns 0 if successful, -1 otherwise
**/
357 358 359 360 361 362 363 364 365
int linphone_core_add_all_to_conference(LinphoneCore *lc) {
	MSList *calls=lc->calls;
	while (calls) {
		LinphoneCall *call=(LinphoneCall*)calls->data;
		calls=calls->next;
		if (!call->current_params.in_conference) {
			linphone_core_add_to_conference(lc, call);
		}
	}
366
	linphone_core_enter_conference(lc);
367 368 369
	return 0;
}

370 371 372 373 374 375 376 377
/**
 * Terminates the conference and the calls associated with it.
 * @param lc the linphone core
 * 
 * All the calls that were merged to the conference are terminated, and the conference resources are destroyed.
 * 
 * @returns 0 if successful, -1 otherwise
**/
378 379 380 381 382 383 384 385 386 387 388 389
int linphone_core_terminate_conference(LinphoneCore *lc) {
	MSList *calls=lc->calls;
	while (calls) {
		LinphoneCall *call=(LinphoneCall*)calls->data;
		calls=calls->next;
		if (call->current_params.in_conference) {
			linphone_core_terminate_call(lc, call);
		}
	}
	return 0;
}

390 391 392 393 394 395 396 397 398
/**
 * Returns the number of participants to the conference, including the local participant.
 * @param lc the linphone core
 * 
 * Typically, after merging two calls into the conference, there is total of 3 participants:
 * the local participant (or local user), and two remote participants that were the destinations of the two previously establised calls.
 * 
 * @returns the number of participants to the conference
**/
399
int linphone_core_get_conference_size(LinphoneCore *lc) {
400 401 402 403 404 405 406 407 408 409 410 411 412 413
	LinphoneConference *conf=&lc->conf_ctx;
	return linphone_conference_get_size(conf);
}


int linphone_core_start_conference_recording(LinphoneCore *lc, const char *path){
	LinphoneConference *conf=&lc->conf_ctx;
	if (conf->conf == NULL) {
		ms_warning("linphone_core_start_conference_recording(): no conference now.");
		return -1;
	}
	if (conf->record_endpoint==NULL){
		conf->record_endpoint=ms_audio_endpoint_new_recorder();
		ms_audio_conference_add_member(conf->conf,conf->record_endpoint);
Sylvain Berfini's avatar
Sylvain Berfini committed
414
	}
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
	ms_audio_recorder_endpoint_start(conf->record_endpoint,path);
	return 0;
}

int linphone_core_stop_conference_recording(LinphoneCore *lc){
	LinphoneConference *conf=&lc->conf_ctx;
	if (conf->conf == NULL) {
		ms_warning("linphone_core_stop_conference_recording(): no conference now.");
		return -1;
	}
	if (conf->record_endpoint==NULL){
		ms_warning("linphone_core_stop_conference_recording(): no record active.");
		return -1;
	}
	ms_audio_recorder_endpoint_stop(conf->record_endpoint);
	return 0;
431
}
432 433 434 435 436

/**
 * @}
**/