text: /***************************************************************************
   *            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"
  #include "lpconfig.h"
  
  #include "mediastreamer2/msvolume.h"
  
  /**
   * @addtogroup conferencing
   * @{
  **/
  
  
  static int convert_conference_to_call(LinphoneCore *lc);
  
  static void conference_check_init(LinphoneConference *ctx, int samplerate){
  	if (ctx->conf==NULL){
  		MSAudioConferenceParams params;
  		params.samplerate=samplerate;
  		ctx->conf=ms_audio_conference_new(&params);
  		ctx->terminated=FALSE;
  	}
  }
  
  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;
  		rtp_profile_destroy(ctx->local_dummy_profile);
  	}
  }
  
  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);
  }
  
  static int remote_participants_count(LinphoneConference *ctx) {
  	int count=linphone_conference_get_size(ctx);
  	if (count==0) return 0;
  	if (!ctx->local_participant) return count;
  	return count -1;
  }
  
  void linphone_core_conference_check_uninit(LinphoneCore *lc){
  	LinphoneConference *ctx=&lc->conf_ctx;
  	if (ctx->conf){
  		int remote_count=remote_participants_count(ctx);
  		ms_message("conference_check_uninit(): size=%i",linphone_conference_get_size(ctx));
  		if (remote_count==1 && !ctx->terminated){
  			convert_conference_to_call(lc);
  		}
  		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;
  			}
  		}
  
  		if (ms_audio_conference_get_size(ctx->conf)==0){
  			ms_audio_conference_destroy(ctx->conf);
  			ctx->conf=NULL;
  		}
  	}
  }
  
  void linphone_call_add_to_conf(LinphoneCall *call, bool_t muted){
  	LinphoneCore *lc=call->core;
  	LinphoneConference *conf=&lc->conf_ctx;
  	MSAudioEndpoint *ep;
  	call->params->has_video = FALSE;
  	call->camera_enabled = FALSE;
  	ep=ms_audio_endpoint_get_from_stream(call->audiostream,TRUE);
  	ms_audio_conference_add_member(conf->conf,ep);
  	ms_audio_conference_mute_member(conf->conf,ep,muted);
  	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;
  }
  
  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;
  }
  
  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. */
  	AudioStream *st=audio_stream_new(65000,65001,FALSE);
  	MSSndCard *playcard=lc->sound_conf.lsd_card ?
  			lc->sound_conf.lsd_card : lc->sound_conf.play_sndcard;
  	MSSndCard *captcard=lc->sound_conf.capt_sndcard;
  	const MSAudioConferenceParams *params=ms_audio_conference_get_params(conf->conf);
  	conf->local_dummy_profile=make_dummy_profile(params->samplerate);
  
  	audio_stream_start_full(st, conf->local_dummy_profile,
  				"127.0.0.1",
  				65000,
  				"127.0.0.1",
  				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);
  
  }
  
  /**
   * Returns the sound volume (mic input) of the local participant of the conference.
   * @param lc the linphone core
   * @return the measured input volume expressed in dbm0.
   **/
  float linphone_core_get_conference_local_input_volume(LinphoneCore *lc){
  	LinphoneConference *conf=&lc->conf_ctx;
  	AudioStream *st=conf->local_participant;
  	if (st && st->volsend && !conf->local_muted){
  		float vol=0;
  		ms_filter_call_method(st->volsend,MS_VOLUME_GET,&vol);
  		return vol;
  
  	}
  	return LINPHONE_VOLUME_DB_LOWEST;
  }
  
  /**
   * 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.
   * If the call was in paused state, then it is automatically resumed when entering into the conference.
   *
   * @return 0 if successful, -1 otherwise.
  **/
  int linphone_core_add_to_conference(LinphoneCore *lc, LinphoneCall *call){
  	LinphoneConference *conf=&lc->conf_ctx;
  
  	if (call->current_params->in_conference){
  		ms_error("Already in conference");
  		return -1;
  	}
  	conference_check_init(&lc->conf_ctx, lp_config_get_int(lc->config, "sound","conference_rate",16000));
  
  	if (call->state==LinphoneCallPaused){
  		call->params->in_conference=TRUE;
  		call->params->has_video=FALSE;
  		linphone_core_resume_call(lc,call);
  	}else if (call->state==LinphoneCallStreamsRunning){
  		LinphoneCallParams *params=linphone_call_params_copy(linphone_call_get_current_params(call));
  		params->in_conference=TRUE;
  		params->has_video=FALSE;
  
  		if (call->audiostream || call->videostream){
  			linphone_call_stop_media_streams(call); /*free the audio & video local resources*/
  			linphone_call_init_media_streams(call);
  		}
  		if (call==lc->current_call){
  			lc->current_call=NULL;
  		}
  		/*this will trigger a reINVITE that will later redraw the streams */
  		/*FIXME probably a bit too much to just redraw streams !*/
  		linphone_core_update_call(lc,call,params);
  		linphone_call_params_destroy(params);
  		add_local_endpoint(conf,lc);
  	}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;
  }
  
  static int remove_from_conference(LinphoneCore *lc, LinphoneCall *call, bool_t active){
  	int err=0;
  	char *str;
  
  	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;
  
  	str=linphone_call_get_remote_address_as_string(call);
  	ms_message("%s will be removed from conference", str);
  	ms_free(str);
  	if (active){
  		LinphoneCallParams *params=linphone_call_params_copy(linphone_call_get_current_params(call));
  		params->in_conference=FALSE;
  		// 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,params);
  		linphone_call_params_destroy(params);
  	} else{
  		ms_message("Pausing call to actually remove from conference");
  		err=_linphone_core_pause_call(lc,call);
  	}
  	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;
  }
  
  int linphone_core_remove_from_conference(LinphoneCore *lc, LinphoneCall *call){
  	int err;
  	char * str=linphone_call_get_remote_address_as_string(call);
  	ms_message("Removing call %s from the conference", str);
  	ms_free(str);
  	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));
  	}
  	return err;
  }
  
  bool_t linphone_core_is_in_conference(const LinphoneCore *lc){
  	return lc->conf_ctx.local_participant!=NULL;
  }
  
  /**
   * 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.
   * @return 0 if successful, -1 otherwise.
  **/
  int linphone_core_leave_conference(LinphoneCore *lc){
  	LinphoneConference *conf=&lc->conf_ctx;
  	if (linphone_core_is_in_conference(lc))
  		remove_local_endpoint(conf);
  	return 0;
  }
  
  /**
   * 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.
   *
   * @return 0 if successful, -1 otherwise
  **/
  int linphone_core_enter_conference(LinphoneCore *lc){
  	LinphoneConference *conf;
  	if (linphone_core_sound_resources_locked(lc)) {
  		return -1;
  	}
  	if (lc->current_call != NULL) {
  		_linphone_core_pause_call(lc, lc->current_call);
  	}
  	conf=&lc->conf_ctx;
  	if (conf->local_participant==NULL) add_local_endpoint(conf,lc);
  	return 0;
  }
  
  /**
   * Add all calls into a conference.
   * @param lc the linphone core
   *
   * Merge all established calls (either in LinphoneCallStreamsRunning or LinphoneCallPaused) into a conference.
   *
   * @return 0 if successful, -1 otherwise
  **/
  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);
  		}
  	}
  	linphone_core_enter_conference(lc);
  	return 0;
  }
  
  /**
   * 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.
   *
   * @return 0 if successful, -1 otherwise
  **/
  int linphone_core_terminate_conference(LinphoneCore *lc) {
  	MSList *calls=lc->calls;
  	LinphoneConference *conf=&lc->conf_ctx;
  	conf->terminated=TRUE;
  
  	while (calls) {
  		LinphoneCall *call=(LinphoneCall*)calls->data;
  		calls=calls->next;
  		if (call->current_params->in_conference) {
  			linphone_core_terminate_call(lc, call);
  		}
  	}
  	return 0;
  }
  
  /**
   * 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.
   *
   * @return the number of participants to the conference
  **/
  int linphone_core_get_conference_size(LinphoneCore *lc) {
  	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);
  	}
  	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;
  }
  
  /**
   * @}
  **/
  
  