]> sjero.net Git - linphone/blob - coreapi/conference.c
important conference bugfixes
[linphone] / coreapi / conference.c
1 /***************************************************************************
2  *            conference.c
3  *
4  *  Mon Sep 12, 2011
5  *  Copyright  2011  Belledonne Communications
6  *  Author: Simon Morlat
7  *  Email simon dot morlat at linphone dot org
8  ****************************************************************************/
9
10 /*
11  *  This program is free software; you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation; either version 2 of the License, or
14  *  (at your option) any later version.
15  *
16  *  This program is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, write to the Free Software
23  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24  */
25  
26 #include "private.h"
27 #include "lpconfig.h"
28
29 #include "mediastreamer2/msvolume.h"
30
31 /**
32  * @addtogroup conferencing
33  * @{
34 **/
35
36
37 static int convert_conference_to_call(LinphoneCore *lc);
38
39 static void conference_check_init(LinphoneConference *ctx, int samplerate){
40         if (ctx->conf==NULL){
41                 MSAudioConferenceParams params;
42                 params.samplerate=samplerate;
43                 ctx->conf=ms_audio_conference_new(&params);
44         }
45 }
46
47 static void remove_local_endpoint(LinphoneConference *ctx){
48         if (ctx->local_endpoint){
49                 ms_audio_conference_remove_member(ctx->conf,ctx->local_endpoint);
50                 ms_audio_endpoint_release_from_stream(ctx->local_endpoint);
51                 ctx->local_endpoint=NULL;
52                 audio_stream_stop(ctx->local_participant);
53                 ctx->local_participant=NULL;
54                 rtp_profile_destroy(ctx->local_dummy_profile);
55         }
56 }
57
58 static int linphone_conference_get_size(LinphoneConference *conf){
59         if (conf->conf == NULL) {
60                 return 0;
61         }
62         return ms_audio_conference_get_size(conf->conf) - (conf->record_endpoint ? 1 : 0);
63 }
64
65 static int remote_participants_count(LinphoneConference *ctx) {
66         int count=linphone_conference_get_size(ctx);
67         if (count==0) return 0;
68         if (!ctx->local_participant) return count;
69         return count -1;
70 }
71
72 void linphone_core_conference_check_uninit(LinphoneCore *lc){
73         LinphoneConference *ctx=&lc->conf_ctx;
74         if (ctx->conf){
75                 int remote_count=remote_participants_count(ctx);
76                 ms_message("conference_check_uninit(): size=%i",linphone_conference_get_size(ctx));
77                 if (remote_count==1){
78                         convert_conference_to_call(lc);
79                 }
80                 if (remote_count==0){
81                         if (ctx->local_participant!=NULL)
82                                 remove_local_endpoint(ctx);
83                         if (ctx->record_endpoint){
84                                 ms_audio_conference_remove_member(ctx->conf,ctx->record_endpoint);
85                                 ms_audio_endpoint_destroy(ctx->record_endpoint);
86                                 ctx->record_endpoint=NULL;
87                         }
88                 }
89                 
90                 if (ms_audio_conference_get_size(ctx->conf)==0){
91                         ms_audio_conference_destroy(ctx->conf);
92                         ctx->conf=NULL;
93                 }
94         }
95 }
96
97 void linphone_call_add_to_conf(LinphoneCall *call, bool_t muted){
98         LinphoneCore *lc=call->core;
99         LinphoneConference *conf=&lc->conf_ctx;
100         MSAudioEndpoint *ep;
101         call->params.has_video = FALSE;
102         call->camera_active = FALSE;
103         ep=ms_audio_endpoint_get_from_stream(call->audiostream,TRUE);
104         ms_audio_conference_add_member(conf->conf,ep);
105         ms_audio_conference_mute_member(conf->conf,ep,muted);
106         call->endpoint=ep;
107 }
108
109 void linphone_call_remove_from_conf(LinphoneCall *call){
110         LinphoneCore *lc=call->core;
111         LinphoneConference *conf=&lc->conf_ctx;
112         
113         ms_audio_conference_remove_member(conf->conf,call->endpoint);
114         ms_audio_endpoint_release_from_stream(call->endpoint);
115         call->endpoint=NULL;
116 }
117
118 static RtpProfile *make_dummy_profile(int samplerate){
119         RtpProfile *prof=rtp_profile_new("dummy");
120         PayloadType *pt=payload_type_clone(&payload_type_l16_mono);
121         pt->clock_rate=samplerate;
122         rtp_profile_set_payload(prof,0,pt);
123         return prof;
124 }
125
126 static void add_local_endpoint(LinphoneConference *conf,LinphoneCore *lc){
127         /*create a dummy audiostream in order to extract the local part of it */
128         /* network address and ports have no meaning and are not used here. */
129         AudioStream *st=audio_stream_new(65000,65001,FALSE);
130         MSSndCard *playcard=lc->sound_conf.lsd_card ? 
131                         lc->sound_conf.lsd_card : lc->sound_conf.play_sndcard;
132         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                                 "127.0.0.1",
138                                 65000,
139                                 "127.0.0.1",
140                                 65001,
141                                 0,
142                                 40,
143                                 NULL,
144                                 NULL,
145                                 playcard,
146                                 captcard,
147                                 linphone_core_echo_cancellation_enabled(lc)
148                                 );
149         _post_configure_audio_stream(st,lc,FALSE);
150         conf->local_participant=st;
151         conf->local_endpoint=ms_audio_endpoint_get_from_stream(st,FALSE);
152         ms_audio_conference_add_member(conf->conf,conf->local_endpoint);
153         
154 }
155
156 /**
157  * Returns the sound volume (mic input) of the local participant of the conference.
158  * @param lc the linphone core
159  * @returns the measured input volume expressed in dbm0.
160  **/
161 float linphone_core_get_conference_local_input_volume(LinphoneCore *lc){
162         LinphoneConference *conf=&lc->conf_ctx;
163         AudioStream *st=conf->local_participant;
164         if (st && st->volsend && !conf->local_muted){
165                 float vol=0;
166                 ms_filter_call_method(st->volsend,MS_VOLUME_GET,&vol);
167                 return vol;
168                 
169         }
170         return LINPHONE_VOLUME_DB_LOWEST;
171 }
172
173 /**
174  * Merge a call into a conference.
175  * @param lc the linphone core
176  * @param call an established call, either in LinphoneCallStreamsRunning or LinphoneCallPaused state.
177  * 
178  * If this is the first call that enters the conference, the virtual conference will be created automatically.
179  * 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.
180  * If the call was in paused state, then it is automatically resumed when entering into the conference.
181  * 
182  * @returns 0 if successful, -1 otherwise.
183 **/
184 int linphone_core_add_to_conference(LinphoneCore *lc, LinphoneCall *call){
185         LinphoneConference *conf=&lc->conf_ctx;
186         
187         if (call->current_params.in_conference){
188                 ms_error("Already in conference");
189                 return -1;
190         }
191         conference_check_init(&lc->conf_ctx, lp_config_get_int(lc->config, "sound","conference_rate",16000));
192         
193         if (call->state==LinphoneCallPaused){
194                 call->params.in_conference=TRUE;
195                 call->params.has_video=FALSE;
196                 linphone_core_resume_call(lc,call);
197         }else if (call->state==LinphoneCallStreamsRunning){
198                 LinphoneCallParams *params=linphone_call_params_copy(linphone_call_get_current_params(call));
199                 params->in_conference=TRUE;
200                 params->has_video=FALSE;
201                 
202                 if (call->audiostream || call->videostream){
203                         linphone_call_stop_media_streams (call); /*free the audio & video local resources*/
204                 }
205                 if (call==lc->current_call){
206                         lc->current_call=NULL;
207                 }
208                 /*this will trigger a reINVITE that will later redraw the streams */
209                 linphone_core_update_call(lc,call,params);
210                 linphone_call_params_destroy(params);
211                 add_local_endpoint(conf,lc);
212         }else{
213                 ms_error("Call is in state %s, it cannot be added to the conference.",linphone_call_state_to_string(call->state));
214                 return -1;
215         }
216         return 0;
217 }
218
219 static int remove_from_conference(LinphoneCore *lc, LinphoneCall *call, bool_t active){
220         int err=0;
221
222         if (!call->current_params.in_conference){
223                 if (call->params.in_conference){
224                         ms_warning("Not (yet) in conference, be patient");
225                         return -1;
226                 }else{
227                         ms_error("Not in a conference.");
228                         return -1;
229                 }
230         }
231         call->params.in_conference=FALSE;
232
233         char *str=linphone_call_get_remote_address_as_string(call);
234         ms_message("%s will be removed from conference", str);
235         ms_free(str);
236         if (active){
237                 LinphoneCallParams *params=linphone_call_params_copy(linphone_call_get_current_params(call));
238                 params->in_conference=FALSE;
239                 // reconnect local audio with this call
240                 if (linphone_core_is_in_conference(lc)){
241                         ms_message("Leaving conference for reconnecting with unique call.");
242                         linphone_core_leave_conference(lc);
243                 }
244                 ms_message("Updating call to actually remove from conference");
245                 err=linphone_core_update_call(lc,call,params);
246                 linphone_call_params_destroy(params);
247         } else{
248                 ms_message("Pausing call to actually remove from conference");
249                 err=_linphone_core_pause_call(lc,call);
250         }
251
252         return err;
253 }
254
255 static int convert_conference_to_call(LinphoneCore *lc){
256         int err=0;
257         MSList *calls=lc->calls;
258
259         if (remote_participants_count(&lc->conf_ctx)!=1){
260                 ms_error("No unique call remaining in conference.");
261                 return -1;
262         }
263
264         while (calls) {
265                 LinphoneCall *rc=(LinphoneCall*)calls->data;
266                 calls=calls->next;
267                 if (rc->params.in_conference) { // not using current_param
268                         bool_t active_after_removed=linphone_core_is_in_conference(lc);
269                         err=remove_from_conference(lc, rc, active_after_removed);
270                         break;
271                 }
272         }
273         return err;
274 }
275
276 /**
277  * Remove a call from the conference.
278  * @param lc the linphone core
279  * @param call a call that has been previously merged into the conference.
280  * 
281  * After removing the remote participant belonging to the supplied call, the call becomes a normal call in paused state.
282  * If one single remote participant is left alone together with the local user in the conference after the removal, then the conference is
283  * automatically transformed into a simple call in StreamsRunning state.
284  * The conference's resources are then automatically destroyed.
285  * 
286  * In other words, unless linphone_core_leave_conference() is explicitely called, the last remote participant of a conference is automatically
287  * put in a simple call in running state.
288  * 
289  * @returns 0 if successful, -1 otherwise.
290  **/
291 int linphone_core_remove_from_conference(LinphoneCore *lc, LinphoneCall *call){
292         char * str=linphone_call_get_remote_address_as_string(call);
293         ms_message("Removing call %s from the conference", str);
294         ms_free(str);
295         int err=remove_from_conference(lc,call, FALSE);
296         if (err){
297                 ms_error("Error removing participant from conference.");
298                 return err;
299         }
300
301         if (remote_participants_count(&lc->conf_ctx)==1){
302                 ms_message("conference size is 1: need to be converted to plain call");
303                 err=convert_conference_to_call(lc);
304         } else {
305                 ms_message("the conference need not to be converted as size is %i", remote_participants_count(&lc->conf_ctx));
306         }
307         return err;
308 }
309
310 /**
311  * Indicates whether the local participant is part of the conference.
312  * @param lc the linphone core
313  * @returns TRUE if the local participant is in the conference, FALSE otherwise.
314 **/
315 bool_t linphone_core_is_in_conference(const LinphoneCore *lc){
316         return lc->conf_ctx.local_participant!=NULL;
317 }
318
319 /**
320  * Moves the local participant out of the conference.
321  * @param lc the linphone core
322  * When the local participant is out of the conference, the remote participants can continue to talk normally.
323  * @returns 0 if successful, -1 otherwise.
324 **/
325 int linphone_core_leave_conference(LinphoneCore *lc){
326         LinphoneConference *conf=&lc->conf_ctx;
327         if (linphone_core_is_in_conference(lc))
328                 remove_local_endpoint(conf);
329         return 0;
330 }
331
332 /**
333  * Moves the local participant inside the conference.
334  * @param lc the linphone core
335  * 
336  * Makes the local participant to join the conference. 
337  * Typically, the local participant is by default always part of the conference when joining an active call into a conference.
338  * However, by calling linphone_core_leave_conference() and linphone_core_enter_conference() the application can decide to temporarily
339  * move out and in the local participant from the conference.
340  * 
341  * @returns 0 if successful, -1 otherwise
342 **/
343 int linphone_core_enter_conference(LinphoneCore *lc){
344         if (linphone_core_sound_resources_locked(lc)) {
345                 return -1;
346         }
347         if (lc->current_call != NULL) {
348                 _linphone_core_pause_call(lc, lc->current_call);
349         }
350         LinphoneConference *conf=&lc->conf_ctx;
351         if (conf->local_participant==NULL) add_local_endpoint(conf,lc);
352         return 0;
353 }
354
355 /**
356  * Add all calls into a conference.
357  * @param lc the linphone core
358  * 
359  * Merge all established calls (either in LinphoneCallStreamsRunning or LinphoneCallPaused) into a conference.
360  * 
361  * @returns 0 if successful, -1 otherwise
362 **/
363 int linphone_core_add_all_to_conference(LinphoneCore *lc) {
364         MSList *calls=lc->calls;
365         while (calls) {
366                 LinphoneCall *call=(LinphoneCall*)calls->data;
367                 calls=calls->next;
368                 if (!call->current_params.in_conference) {
369                         linphone_core_add_to_conference(lc, call);
370                 }
371         }
372         linphone_core_enter_conference(lc);
373         return 0;
374 }
375
376 /**
377  * Terminates the conference and the calls associated with it.
378  * @param lc the linphone core
379  * 
380  * All the calls that were merged to the conference are terminated, and the conference resources are destroyed.
381  * 
382  * @returns 0 if successful, -1 otherwise
383 **/
384 int linphone_core_terminate_conference(LinphoneCore *lc) {
385         MSList *calls=lc->calls;
386         while (calls) {
387                 LinphoneCall *call=(LinphoneCall*)calls->data;
388                 calls=calls->next;
389                 if (call->current_params.in_conference) {
390                         linphone_core_terminate_call(lc, call);
391                 }
392         }
393         return 0;
394 }
395
396 /**
397  * Returns the number of participants to the conference, including the local participant.
398  * @param lc the linphone core
399  * 
400  * Typically, after merging two calls into the conference, there is total of 3 participants:
401  * the local participant (or local user), and two remote participants that were the destinations of the two previously establised calls.
402  * 
403  * @returns the number of participants to the conference
404 **/
405 int linphone_core_get_conference_size(LinphoneCore *lc) {
406         LinphoneConference *conf=&lc->conf_ctx;
407         return linphone_conference_get_size(conf);
408 }
409
410
411 int linphone_core_start_conference_recording(LinphoneCore *lc, const char *path){
412         LinphoneConference *conf=&lc->conf_ctx;
413         if (conf->conf == NULL) {
414                 ms_warning("linphone_core_start_conference_recording(): no conference now.");
415                 return -1;
416         }
417         if (conf->record_endpoint==NULL){
418                 conf->record_endpoint=ms_audio_endpoint_new_recorder();
419                 ms_audio_conference_add_member(conf->conf,conf->record_endpoint);
420         }
421         ms_audio_recorder_endpoint_start(conf->record_endpoint,path);
422         return 0;
423 }
424
425 int linphone_core_stop_conference_recording(LinphoneCore *lc){
426         LinphoneConference *conf=&lc->conf_ctx;
427         if (conf->conf == NULL) {
428                 ms_warning("linphone_core_stop_conference_recording(): no conference now.");
429                 return -1;
430         }
431         if (conf->record_endpoint==NULL){
432                 ms_warning("linphone_core_stop_conference_recording(): no record active.");
433                 return -1;
434         }
435         ms_audio_recorder_endpoint_stop(conf->record_endpoint);
436         return 0;
437 }
438
439 /**
440  * @}
441 **/
442