forked from mirrors/linphone-iphone
It is now an application preference, the core never modifies it. It represents whether the camera is allowed to be sent (in case of video request).
445 lines
14 KiB
C
445 lines
14 KiB
C
/***************************************************************************
|
|
* 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(¶ms);
|
|
}
|
|
}
|
|
|
|
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){
|
|
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
|
|
* @returns 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.
|
|
*
|
|
* @returns 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*/
|
|
}
|
|
if (call==lc->current_call){
|
|
lc->current_call=NULL;
|
|
}
|
|
/*this will trigger a reINVITE that will later redraw the 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;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* 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.
|
|
* The conference's resources are then automatically destroyed.
|
|
*
|
|
* 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.
|
|
*
|
|
* @returns 0 if successful, -1 otherwise.
|
|
**/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
**/
|
|
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.
|
|
* @returns 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.
|
|
*
|
|
* @returns 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.
|
|
*
|
|
* @returns 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.
|
|
*
|
|
* @returns 0 if successful, -1 otherwise
|
|
**/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
**/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @}
|
|
**/
|
|
|