442fdea499
git-svn-id: http://bigbluebutton.googlecode.com/svn/trunk@2294 af16638f-c34d-0410-8cfa-b39d5352b314
3526 lines
88 KiB
C
3526 lines
88 KiB
C
|
|
// $Id: member.c 885 2007-06-27 15:41:18Z sbalea $
|
|
|
|
/*
|
|
* app_conference
|
|
*
|
|
* A channel independent conference application for Asterisk
|
|
*
|
|
* Copyright (C) 2002, 2003 Junghanns.NET GmbH
|
|
* Copyright (C) 2003, 2004 HorizonLive.com, Inc.
|
|
* Copyright (C) 2005, 2006 HorizonWimba, Inc.
|
|
* Copyright (C) 2007 Wimba, Inc.
|
|
*
|
|
* Klaus-Peter Junghanns <kapejod@ns1.jnetdns.de>
|
|
*
|
|
* Video Conferencing support added by
|
|
* Neil Stratford <neils@vipadia.com>
|
|
* Copyright (C) 2005, 2005 Vipadia Limited
|
|
*
|
|
* VAD driven video conferencing, text message support
|
|
* and miscellaneous enhancements added by
|
|
* Mihai Balea <mihai at hates dot ms>
|
|
*
|
|
* This program may be modified and distributed under the
|
|
* terms of the GNU General Public License. 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 <stdio.h>
|
|
#include "asterisk/autoconfig.h"
|
|
#include "member.h"
|
|
|
|
#include "asterisk/musiconhold.h"
|
|
|
|
// process an incoming frame. Returns 0 normally, 1 if hangup was received.
|
|
static int process_incoming(struct ast_conf_member *member, struct ast_conference *conf, struct ast_frame *f)
|
|
{
|
|
int silent_frame = 0;
|
|
#ifdef VIDEO
|
|
struct ast_conf_member *src_member ;
|
|
#endif
|
|
|
|
// In Asterisk 1.4 AST_FRAME_DTMF is equivalent to AST_FRAME_DTMF_END
|
|
if (f->frametype == AST_FRAME_DTMF)
|
|
{
|
|
#ifdef VIDEO
|
|
if (member->dtmf_switch)
|
|
{
|
|
ast_mutex_lock( &member->lock ) ;
|
|
switch (f->subclass) {
|
|
case '0' :member->req_id=0;
|
|
break;
|
|
case '1' :member->req_id=1;
|
|
break;
|
|
case '2' :member->req_id=2;
|
|
break;
|
|
case '3' :member->req_id=3;
|
|
break;
|
|
case '4' :member->req_id=4;
|
|
break;
|
|
case '5' :member->req_id=5;
|
|
break;
|
|
case '6' :member->req_id=6;
|
|
break;
|
|
case '7' :member->req_id=7;
|
|
break;
|
|
case '8' :member->req_id=8;
|
|
break;
|
|
case '9' :member->req_id=9;
|
|
break;
|
|
case '*' :
|
|
if (member->mute_video == 0 && member->mute_audio == 0)
|
|
{
|
|
member->mute_video = 1;
|
|
member->mute_audio = 1;
|
|
}
|
|
else if (member->mute_video == 1 && member->mute_audio == 1)
|
|
{
|
|
member->mute_video = 0;
|
|
member->mute_audio = 0;
|
|
}
|
|
break;
|
|
}
|
|
member->conference = 1; // switch me
|
|
ast_mutex_unlock( &member->lock ) ;
|
|
}
|
|
#endif
|
|
if (member->dtmf_relay)
|
|
{
|
|
// output to manager...
|
|
manager_event(
|
|
EVENT_FLAG_CALL,
|
|
"ConferenceDTMF",
|
|
"ConferenceName: %s\r\n"
|
|
"Type: %s\r\n"
|
|
"UniqueID: %s\r\n"
|
|
"Channel: %s\r\n"
|
|
"CallerID: %s\r\n"
|
|
"CallerIDName: %s\r\n"
|
|
"Key: %c\r\n"
|
|
"Count: %d\r\n"
|
|
"Flags: %s\r\n"
|
|
"Mute: %d\r\n",
|
|
conf->name,
|
|
member->type,
|
|
member->uniqueid,
|
|
member->channel_name,
|
|
member->chan->cid.cid_num ? member->chan->cid.cid_num : "unknown",
|
|
member->chan->cid.cid_name ? member->chan->cid.cid_name : "unknown",
|
|
f->subclass,
|
|
conf->membercount,
|
|
member->flags,
|
|
member->mute_audio
|
|
) ;
|
|
|
|
}
|
|
#ifdef DTMF
|
|
if (!member->mute_audio &&
|
|
#ifdef VIDEO
|
|
!member->dtmf_switch &&
|
|
#endif
|
|
!member->dtmf_relay)
|
|
{
|
|
// relay this to the listening channels
|
|
queue_incoming_dtmf_frame( member, f );
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef DTMF
|
|
else if (f->frametype == AST_FRAME_DTMF_BEGIN)
|
|
{
|
|
if (!member->mute_audio &&
|
|
#ifdef VIDEO
|
|
!member->dtmf_switch &&
|
|
#endif
|
|
!member->dtmf_relay)
|
|
{
|
|
// relay this to the listening channels
|
|
queue_incoming_dtmf_frame( member, f );
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef VIDEO
|
|
ast_mutex_lock( &member->lock ) ;
|
|
// Handle a local or remote conference
|
|
if (member->conference)
|
|
{
|
|
int req_id = member->req_id;
|
|
ast_mutex_unlock( &member->lock );
|
|
// this will return NULL or a locked member
|
|
src_member = check_active_video(req_id,conf);
|
|
// Stream a picture to the recipient if no active video
|
|
if (!src_member)
|
|
{
|
|
// Mihai: we don't want to send video here, we cannot negotiate codec
|
|
// and we don't know what codec the conference is using
|
|
//if (member->norecv_video == 0)
|
|
//{
|
|
// if(!ast_streamfile(member->chan,"novideo",member->chan->language))
|
|
// {
|
|
// ast_waitstream(member->chan,"");
|
|
// }
|
|
//}
|
|
}
|
|
else
|
|
{
|
|
// Send a FIR to the new sender
|
|
ast_indicate(src_member->chan,AST_CONTROL_VIDUPDATE);
|
|
// we will have locked in check_active_video()
|
|
ast_mutex_unlock( &src_member->lock);
|
|
}
|
|
ast_mutex_lock( &member->lock );
|
|
member->conference = 0;
|
|
}
|
|
ast_mutex_unlock( &member->lock );
|
|
#endif
|
|
if ((f->frametype == AST_FRAME_VOICE && (member->mute_audio == 1 ||
|
|
#ifdef VIDEO
|
|
(f->frametype == AST_FRAME_VIDEO && member->mute_video == 1) ||
|
|
#endif
|
|
(member->soundq && member->muted))))
|
|
{
|
|
// this is a listen-only user, ignore the frame
|
|
//ast_log( AST_CONF_DEBUG, "Listen only user frame");
|
|
ast_frfree( f ) ;
|
|
f = NULL ;
|
|
}
|
|
else if ( f->frametype == AST_FRAME_VOICE )
|
|
{ //ast_log( AST_CONF_DEBUG, "Got voice frame");
|
|
// reset silence detection flag
|
|
silent_frame = 0 ;
|
|
|
|
// accounting: count the incoming frame
|
|
member->frames_in++ ;
|
|
|
|
#if ( SILDET == 2 )
|
|
//
|
|
// make sure we have a valid dsp and frame type
|
|
//
|
|
if (
|
|
member->dsp != NULL
|
|
&& f->subclass == AST_FORMAT_SLINEAR
|
|
&& f->datalen == AST_CONF_FRAME_DATA_SIZE
|
|
)
|
|
{
|
|
// send the frame to the preprocessor
|
|
int spx_ret;
|
|
spx_ret = speex_preprocess( member->dsp, CASTDATA2PTR(f->data, void), NULL );
|
|
#ifdef DEBUG_USE_TIMELOG
|
|
TIMELOG(spx_ret, 3, "speex_preprocess");
|
|
#endif
|
|
if ( spx_ret == 0 )
|
|
{
|
|
//
|
|
// we ignore the preprocessor's outcome if we've seen voice frames
|
|
// in within the last AST_CONF_SKIP_SPEEX_PREPROCESS frames
|
|
//
|
|
if ( member->ignore_speex_count > 0 )
|
|
{
|
|
// ast_log( AST_CONF_DEBUG, "ignore_speex_count => %d\n", ignore_speex_count ) ;
|
|
|
|
// skip speex_preprocess(), and decrement counter
|
|
--member->ignore_speex_count ;
|
|
}
|
|
else
|
|
{
|
|
// set silent_frame flag
|
|
silent_frame = 1 ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// voice detected, reset skip count
|
|
member->ignore_speex_count = AST_CONF_SKIP_SPEEX_PREPROCESS ;
|
|
}
|
|
}
|
|
#endif
|
|
if ( !silent_frame )
|
|
queue_incoming_frame( member, f );
|
|
|
|
// free the original frame
|
|
ast_frfree( f ) ;
|
|
f = NULL ;
|
|
|
|
}
|
|
#ifdef VIDEO
|
|
else if (f->frametype == AST_FRAME_VIDEO)
|
|
{
|
|
queue_incoming_video_frame( member, f );
|
|
|
|
// free the original frame
|
|
ast_frfree( f ) ;
|
|
f = NULL ;
|
|
|
|
}
|
|
#endif
|
|
else if (
|
|
f->frametype == AST_FRAME_CONTROL
|
|
&& f->subclass == AST_CONTROL_HANGUP
|
|
)
|
|
{
|
|
// hangup received
|
|
|
|
// free the frame
|
|
ast_frfree( f ) ;
|
|
f = NULL ;
|
|
|
|
// break out of the while ( 42 == 42 )
|
|
return 1;
|
|
}
|
|
#ifdef VIDEO
|
|
else if (
|
|
f->frametype == AST_FRAME_CONTROL
|
|
&& f->subclass == AST_CONTROL_VIDUPDATE
|
|
)
|
|
{
|
|
// say we have switched to cause a FIR to
|
|
// be sent to the sender
|
|
ast_mutex_lock( &member->lock ) ;
|
|
member->conference = 1;
|
|
ast_mutex_unlock( &member->lock ) ;
|
|
|
|
// free the original frame
|
|
ast_frfree( f ) ;
|
|
f = NULL ;
|
|
}
|
|
#endif
|
|
#ifdef VIDEO
|
|
#ifdef TEXT
|
|
else if ( f->frametype == AST_FRAME_TEXT && member->does_text )
|
|
{
|
|
if ( strncmp(CASTDATA2PTR(f->data, char), AST_CONF_CONTROL_CAMERA_DISABLED, strlen(AST_CONF_CONTROL_CAMERA_DISABLED)) == 0 )
|
|
{
|
|
ast_mutex_lock(&member->lock);
|
|
manager_event(EVENT_FLAG_CALL,
|
|
"ConferenceCameraDisabled",
|
|
"ConferenceName: %s\r\nChannel: %s\r\n",
|
|
conf->name,
|
|
member->channel_name);
|
|
member->no_camera = 1;
|
|
ast_mutex_unlock(&member->lock);
|
|
} else if ( strncmp(CASTDATA2PTR(f->data, char), AST_CONF_CONTROL_CAMERA_ENABLED, strlen(AST_CONF_CONTROL_CAMERA_ENABLED)) == 0 )
|
|
{
|
|
ast_mutex_lock(&member->lock);
|
|
manager_event(EVENT_FLAG_CALL,
|
|
"ConferenceCameraEnabled",
|
|
"ConferenceName: %s\r\nChannel: %s\r\n",
|
|
conf->name,
|
|
member->channel_name);
|
|
member->no_camera = 0;
|
|
ast_mutex_unlock(&member->lock);
|
|
} else if ( strncmp(CASTDATA2PTR(f->data, char), AST_CONF_CONTROL_STOP_VIDEO_TRANSMIT, strlen(AST_CONF_CONTROL_STOP_VIDEO_TRANSMIT)) == 0 )
|
|
{
|
|
ast_mutex_lock(&member->lock);
|
|
manager_event(EVENT_FLAG_CALL,
|
|
"ConferenceStopVideoTransmit",
|
|
"ConferenceName: %s\r\nChannel: %s\r\n",
|
|
conf->name,
|
|
member->channel_name);
|
|
member->norecv_video = 1;
|
|
ast_mutex_unlock(&member->lock);
|
|
} else if ( strncmp(CASTDATA2PTR(f->data, char), AST_CONF_CONTROL_START_VIDEO_TRANSMIT, strlen(AST_CONF_CONTROL_START_VIDEO_TRANSMIT)) == 0 )
|
|
{
|
|
ast_mutex_lock(&member->lock);
|
|
manager_event(EVENT_FLAG_CALL,
|
|
"ConferenceStartVideoTransmit",
|
|
"ConferenceName: %s\r\nChannel: %s\r\n",
|
|
conf->name,
|
|
member->channel_name);
|
|
member->norecv_video = 0;
|
|
ast_mutex_unlock(&member->lock);
|
|
}
|
|
ast_frfree(f);
|
|
f = NULL;
|
|
}
|
|
#endif
|
|
#endif
|
|
else {
|
|
// undesirables
|
|
ast_frfree( f ) ;
|
|
f = NULL ;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// get the next frame from the soundq; must be called with member locked.
|
|
static struct ast_frame *get_next_soundframe(struct ast_conf_member *member, struct ast_frame
|
|
*exampleframe) {
|
|
struct ast_frame *f;
|
|
|
|
again:
|
|
ast_mutex_unlock(&member->lock);
|
|
f=(member->soundq->stream && !member->soundq->stopped ? ast_readframe(member->soundq->stream) : NULL);
|
|
|
|
if(!f) { // we're done with this sound; remove it from the queue, and try again
|
|
struct ast_conf_soundq *toboot = member->soundq;
|
|
|
|
if (!toboot->stopped && !toboot->stream)
|
|
{
|
|
toboot->stream = ast_openstream(member->chan, toboot->name, NULL);
|
|
//ast_log( LOG_WARNING, "trying to play sound: name = %s, stream = %p\n", toboot->name, toboot->stream);
|
|
if (toboot->stream)
|
|
{
|
|
member->chan->stream = NULL;
|
|
goto again;
|
|
}
|
|
//ast_log( LOG_WARNING, "trying to play sound, %s not found!?", toboot->name);
|
|
}
|
|
|
|
if (toboot->stream) {
|
|
ast_closestream(toboot->stream);
|
|
//ast_log( LOG_WARNING, "finished playing a sound: name = %s, stream = %p\n", toboot->name, toboot->stream);
|
|
// notify applications via mgr interface that this sound has been played
|
|
manager_event(
|
|
EVENT_FLAG_CALL,
|
|
"ConferenceSoundComplete",
|
|
"ConferenceName: %s\r\n"
|
|
"Channel: %s\r\n"
|
|
"Sound: %s\r\n",
|
|
member->conf_name,
|
|
member->channel_name,
|
|
toboot->name
|
|
);
|
|
}
|
|
|
|
ast_mutex_lock( &member->lock ) ;
|
|
member->soundq = toboot->next;
|
|
|
|
free(toboot);
|
|
if(member->soundq) goto again;
|
|
|
|
ast_mutex_unlock(&member->lock);
|
|
|
|
// if we get here, we've gotten to the end of the queue; reset write format
|
|
if ( ast_set_write_format( member->chan, member->write_format ) < 0 )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to set write format to %d\n",
|
|
member->write_format ) ;
|
|
}
|
|
} else {
|
|
// copy delivery from exampleframe
|
|
f->delivery = exampleframe->delivery;
|
|
}
|
|
|
|
return f;
|
|
}
|
|
|
|
|
|
// process outgoing frames for the channel, playing either normal conference audio,
|
|
// or requested sounds
|
|
static int process_outgoing(struct ast_conf_member *member)
|
|
{
|
|
conf_frame* cf ; // frame read from the output queue
|
|
struct ast_frame *f;
|
|
|
|
for(;;)
|
|
{
|
|
// acquire member mutex and grab a frame.
|
|
cf = get_outgoing_frame( member ) ;
|
|
|
|
// if there's no frames exit the loop.
|
|
if ( !cf )
|
|
{
|
|
break;
|
|
}
|
|
|
|
|
|
struct ast_frame *realframe = f = cf->fr;
|
|
|
|
// if we're playing sounds, we can just replace the frame with the
|
|
// next sound frame, and send it instead
|
|
ast_mutex_lock( &member->lock ) ;
|
|
if ( member->soundq )
|
|
{
|
|
f = get_next_soundframe(member, f);
|
|
if ( !f )
|
|
{
|
|
// if we didn't get anything, just revert to "normal"
|
|
f = realframe;
|
|
}
|
|
} else {
|
|
if (member->moh_flag) {
|
|
member->muted = 1;
|
|
member->ready_for_outgoing = 0;
|
|
delete_conf_frame( cf ) ;
|
|
ast_moh_start(member->chan, NULL, NULL);
|
|
ast_mutex_unlock(&member->lock);
|
|
return 0;
|
|
}
|
|
ast_mutex_unlock(&member->lock);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG_FRAME_TIMESTAMPS
|
|
// !!! TESTING !!!
|
|
int delivery_diff = usecdiff( &f->delivery, &member->lastsent_timeval ) ;
|
|
if ( delivery_diff != AST_CONF_FRAME_INTERVAL )
|
|
{
|
|
ast_log( AST_CONF_DEBUG, "unanticipated delivery time, delivery_diff => %d, delivery.tv_usec => %ld\n",
|
|
delivery_diff, f->delivery.tv_usec ) ;
|
|
}
|
|
|
|
// !!! TESTING !!!
|
|
if (
|
|
f->delivery.tv_sec < member->lastsent_timeval.tv_sec
|
|
|| (
|
|
f->delivery.tv_sec == member->lastsent_timeval.tv_sec
|
|
&& f->delivery.tv_usec <= member->lastsent_timeval.tv_usec
|
|
)
|
|
)
|
|
{
|
|
ast_log( LOG_WARNING, "queued frame timestamped in the past, %ld.%ld <= %ld.%ld\n",
|
|
f->delivery.tv_sec, f->delivery.tv_usec,
|
|
member->lastsent_timeval.tv_sec, member->lastsent_timeval.tv_usec ) ;
|
|
}
|
|
member->lastsent_timeval = f->delivery ;
|
|
#endif
|
|
|
|
#ifdef DEBUG_USE_TIMELOG
|
|
TIMELOG( ast_write( member->chan, f ), 10, "member: ast_write");
|
|
#else
|
|
|
|
// send the voice frame
|
|
if ( ast_write( member->chan, f ) == 0 )
|
|
{
|
|
struct timeval tv = ast_tvnow();
|
|
ast_log( AST_CONF_DEBUG, "SENT VOICE FRAME, channel => %s, frames_out => %ld, s => %ld, ms => %ld\n",
|
|
member->channel_name, member->frames_out, tv.tv_sec, tv.tv_usec ) ;
|
|
}
|
|
else
|
|
{
|
|
if ( member->chan->_softhangup )
|
|
return 1;
|
|
|
|
// log 'dropped' outgoing frame
|
|
ast_log( LOG_ERROR, "unable to write voice frame to channel, channel => %s\n", member->channel_name ) ;
|
|
|
|
// accounting: count dropped outgoing frames
|
|
member->frames_out_dropped++ ;
|
|
}
|
|
#endif
|
|
// clean up frame
|
|
delete_conf_frame( cf ) ;
|
|
|
|
// free sound frame
|
|
if ( f != realframe )
|
|
ast_frfree(f) ;
|
|
|
|
}
|
|
#ifdef VIDEO
|
|
// Do the same for video, suck it dry
|
|
for(;;)
|
|
{
|
|
// grab a frame.
|
|
cf = get_outgoing_video_frame( member ) ;
|
|
|
|
// if there's no frames exit the loop.
|
|
if(!cf){
|
|
break;
|
|
}
|
|
|
|
f = cf->fr;
|
|
|
|
// send the video frame
|
|
if ( ast_write_video( member->chan, f ) == 1 )
|
|
{
|
|
struct timeval tv = ast_tvnow();
|
|
ast_log( AST_CONF_DEBUG, "SENT VIDEO FRAME, channel => %s, frames_out => %ld, s => %ld, ms => %ld\n",
|
|
member->channel_name, member->frames_out, tv.tv_sec, tv.tv_usec ) ;
|
|
}
|
|
else
|
|
{
|
|
if ( member->chan->_softhangup )
|
|
return 1;
|
|
|
|
// log 'dropped' outgoing frame
|
|
ast_log( AST_CONF_DEBUG, "unable to write video frame to channel, channel => %s\n", member->channel_name ) ;
|
|
|
|
// accounting: count dropped outgoing frames
|
|
member->video_frames_out_dropped++ ;
|
|
}
|
|
|
|
// clean up frame
|
|
delete_conf_frame( cf ) ;
|
|
|
|
}
|
|
#endif
|
|
#ifdef DTMF
|
|
// Do the same for dtmf, suck it dry
|
|
for(;;)
|
|
{
|
|
// acquire member mutex and grab a frame.
|
|
cf = get_outgoing_dtmf_frame( member ) ;
|
|
|
|
// if there's no frames exit the loop.
|
|
if(!cf) break;
|
|
|
|
// send the dtmf frame
|
|
if ( ast_write( member->chan, cf->fr ) == 0 )
|
|
{
|
|
struct timeval tv = ast_tvnow();
|
|
ast_log( AST_CONF_DEBUG, "SENT DTMF FRAME, channel => %s, frames_out => %ld, s => %ld, ms => %ld\n",
|
|
member->channel_name, member->frames_out, tv.tv_sec, tv.tv_usec ) ;
|
|
|
|
}
|
|
else
|
|
{
|
|
if ( member->chan->_softhangup )
|
|
return 1;
|
|
|
|
// log 'dropped' outgoing frame
|
|
ast_log( AST_CONF_DEBUG, "unable to write dtmf frame to channel, channel => %s\n", member->channel_name ) ;
|
|
|
|
// accounting: count dropped outgoing frames
|
|
member->dtmf_frames_out_dropped++ ;
|
|
}
|
|
|
|
// clean up frame
|
|
delete_conf_frame( cf ) ;
|
|
}
|
|
#endif
|
|
#ifdef TEXT
|
|
// Do the same for text, hell, why not?
|
|
for(;;)
|
|
{
|
|
// acquire member mutex and grab a frame.
|
|
cf = get_outgoing_text_frame( member ) ;
|
|
|
|
// if there's no frames exit the loop.
|
|
if(!cf) break;
|
|
|
|
// send the text frame
|
|
if ( ast_write( member->chan, cf->fr ) == 0 )
|
|
{
|
|
struct timeval tv = ast_tvnow();
|
|
ast_log( AST_CONF_DEBUG, "SENT TEXT FRAME, channel => %s, frames_out => %ld, s => %ld, ms => %ld\n",
|
|
member->channel_name, member->frames_out, tv.tv_sec, tv.tv_usec ) ;
|
|
|
|
}
|
|
else
|
|
{
|
|
if ( member->chan->_softhangup )
|
|
return 1;
|
|
|
|
// log 'dropped' outgoing frame
|
|
ast_log( AST_CONF_DEBUG, "unable to write text frame to channel, channel => %s\n", member->channel_name ) ;
|
|
|
|
// accounting: count dropped outgoing frames
|
|
member->text_frames_out_dropped++ ;
|
|
}
|
|
|
|
// clean up frame
|
|
delete_conf_frame( cf ) ;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// main member thread function
|
|
//
|
|
|
|
int member_exec( struct ast_channel* chan, void* data )
|
|
{
|
|
// struct timeval start, end ;
|
|
// start = ast_tvnow();
|
|
|
|
struct ast_conference *conf ;
|
|
struct ast_conf_member *member ;
|
|
|
|
struct ast_frame *f ; // frame received from ast_read()
|
|
|
|
int left = 0 ;
|
|
int res;
|
|
|
|
ast_log( AST_CONF_DEBUG, "Begin processing member thread, channel => %s\n", chan->name ) ;
|
|
|
|
//
|
|
// If the call has not yet been answered, answer the call
|
|
// Note: asterisk apps seem to check _state, but it seems like it's safe
|
|
// to just call ast_answer. It will just do nothing if it is up.
|
|
// it will also return -1 if the channel is a zombie, or has hung up.
|
|
//
|
|
|
|
res = ast_answer( chan ) ;
|
|
if ( res )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to answer call\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
//
|
|
// create a new member for the conference
|
|
//
|
|
|
|
// ast_log( AST_CONF_DEBUG, "creating new member, id => %s, flags => %s, p => %s\n",
|
|
// id, flags, priority ) ;
|
|
|
|
member = create_member( chan, (const char*)( data ) ) ; // flags, atoi( priority ) ) ;
|
|
|
|
// unable to create member, return an error
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to create member\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
//
|
|
// setup asterisk read/write formats
|
|
//
|
|
#if 0
|
|
ast_log( AST_CONF_DEBUG, "CHANNEL INFO, CHANNEL => %s, DNID => %s, CALLER_ID => %s, ANI => %s\n",
|
|
chan->name, chan->dnid, chan->callerid, chan->ani ) ;
|
|
|
|
ast_log( AST_CONF_DEBUG, "CHANNEL CODECS, CHANNEL => %s, NATIVE => %d, READ => %d, WRITE => %d\n",
|
|
chan->name, chan->nativeformats, member->read_format, member->write_format ) ;
|
|
#endif
|
|
if ( ast_set_read_format( chan, member->read_format ) < 0 )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to set read format to signed linear\n" ) ;
|
|
delete_member( member ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
if ( ast_set_write_format( chan, member->write_format ) < 0 ) // AST_FORMAT_SLINEAR, chan->nativeformats
|
|
{
|
|
ast_log( LOG_ERROR, "unable to set write format to signed linear\n" ) ;
|
|
delete_member( member ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
//
|
|
// setup a conference for the new member
|
|
//
|
|
|
|
conf = join_conference( member ) ;
|
|
|
|
if ( conf == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to setup member conference\n" ) ;
|
|
int res = (member->max_users_flag ? 0 : -1 ) ;
|
|
delete_member( member) ;
|
|
return res ;
|
|
}
|
|
|
|
// add member to channel table
|
|
member->bucket = &(channel_table[hash(member->chan->name) % CHANNEL_TABLE_SIZE]);
|
|
|
|
AST_LIST_LOCK (member->bucket ) ;
|
|
AST_LIST_INSERT_HEAD (member->bucket, member, hash_entry) ;
|
|
AST_LIST_UNLOCK (member->bucket ) ;
|
|
|
|
//ast_log( AST_CONF_DEBUG, "Added %s to the channel table, bucket => %ld\n", member->chan->name, member->bucket - channel_table) ;
|
|
|
|
manager_event(
|
|
EVENT_FLAG_CALL,
|
|
"ConferenceJoin",
|
|
"ConferenceName: %s\r\n"
|
|
"Type: %s\r\n"
|
|
"UniqueID: %s\r\n"
|
|
"Member: %d\r\n"
|
|
"Flags: %s\r\n"
|
|
"Channel: %s\r\n"
|
|
"CallerID: %s\r\n"
|
|
"CallerIDName: %s\r\n"
|
|
"Moderators: %d\r\n"
|
|
"Count: %d\r\n",
|
|
conf->name,
|
|
member->type,
|
|
member->uniqueid,
|
|
member->id,
|
|
member->flags,
|
|
member->channel_name,
|
|
member->chan->cid.cid_num ? member->chan->cid.cid_num : "unknown",
|
|
member->chan->cid.cid_name ? member->chan->cid.cid_name: "unknown",
|
|
conf->stats.moderators,
|
|
conf->membercount
|
|
) ;
|
|
|
|
// Store the CID information
|
|
if ( member->chan->cid.cid_num )
|
|
{
|
|
if ( (member->callerid = malloc(strlen(member->chan->cid.cid_num)+1)) )
|
|
memcpy(member->callerid,member->chan->cid.cid_num, strlen(member->chan->cid.cid_num)+1);
|
|
} else
|
|
member->callerid = NULL;
|
|
|
|
if ( member->chan->cid.cid_name )
|
|
{
|
|
if ( (member->callername = malloc(strlen(member->chan->cid.cid_name)+1)) )
|
|
memcpy(member->callername, member->chan->cid.cid_name, strlen(member->chan->cid.cid_name)+1);
|
|
} else
|
|
member->callername = NULL;
|
|
|
|
|
|
//
|
|
// process loop for new member ( this runs in it's own thread )
|
|
//
|
|
|
|
ast_log( AST_CONF_DEBUG, "begin member event loop, channel => %s\n", chan->name ) ;
|
|
|
|
// timer timestamps
|
|
struct timeval base, curr ;
|
|
base = ast_tvnow();
|
|
|
|
// tell conference_exec we're ready for frames
|
|
member->ready_for_outgoing = 1 ;
|
|
while ( 42 == 42 )
|
|
{
|
|
// make sure we have a channel to process
|
|
if ( chan == NULL )
|
|
{
|
|
ast_log( LOG_NOTICE, "member channel has closed\n" ) ;
|
|
break ;
|
|
}
|
|
|
|
//-----------------//
|
|
// INCOMING FRAMES //
|
|
//-----------------//
|
|
|
|
// wait for an event on this channel
|
|
left = ast_waitfor( chan, AST_CONF_WAITFOR_LATENCY ) ;
|
|
|
|
//ast_log( AST_CONF_DEBUG, "received event on channel, name => %s, left => %d\n", chan->name, left ) ;
|
|
|
|
if ( left < 0 )
|
|
{
|
|
// an error occured
|
|
ast_log(
|
|
LOG_NOTICE,
|
|
"an error occured waiting for a frame, channel => %s, error => %d\n",
|
|
chan->name, left
|
|
) ;
|
|
break; // out of the 42==42
|
|
}
|
|
else if ( left == 0 )
|
|
{
|
|
// no frame has arrived yet
|
|
// ast_log( LOG_NOTICE, "no frame available from channel, channel => %s\n", chan->name ) ;
|
|
}
|
|
else if ( left > 0 )
|
|
{
|
|
// a frame has come in before the latency timeout
|
|
// was reached, so we process the frame
|
|
|
|
f = ast_read( chan ) ;
|
|
|
|
if ( f == NULL )
|
|
{
|
|
if (conf->debug_flag)
|
|
{
|
|
ast_log( LOG_NOTICE, "unable to read from channel, channel => %s\n", chan->name ) ;
|
|
// They probably want to hangup...
|
|
}
|
|
break ;
|
|
}
|
|
|
|
// actually process the frame: break if we got hangup.
|
|
if(process_incoming(member, conf, f)) break;
|
|
|
|
}
|
|
|
|
if (conf->kick_flag || member->kick_flag) {
|
|
pbx_builtin_setvar_helper(member->chan, "KONFERENCE", "KICKED" );
|
|
break;
|
|
}
|
|
|
|
//-----------------//
|
|
// OUTGOING FRAMES //
|
|
//-----------------//
|
|
|
|
// update the current timestamps
|
|
curr = ast_tvnow();
|
|
|
|
if ( !process_outgoing(member) )
|
|
// back to process incoming frames
|
|
continue ;
|
|
else
|
|
// they probably hungup...
|
|
break ;
|
|
}
|
|
|
|
ast_log( AST_CONF_DEBUG, "end member event loop, time_entered => %ld\n", member->time_entered.tv_sec ) ;
|
|
|
|
//
|
|
// clean up
|
|
//
|
|
|
|
#ifdef DEBUG_OUTPUT_PCM
|
|
// !!! TESTING !!!
|
|
if ( incoming_fh != NULL )
|
|
fclose( incoming_fh ) ;
|
|
#endif
|
|
// end = ast_tvnow();
|
|
// int expected_frames = ( int )( floor( (double)( msecdiff( &end, &start ) / AST_CONF_FRAME_INTERVAL ) ) ) ;
|
|
// ast_log( AST_CONF_DEBUG, "expected_frames => %d\n", expected_frames ) ;
|
|
|
|
remove_member( member, conf ) ;
|
|
return 0 ;
|
|
}
|
|
|
|
|
|
#ifdef VIDEO
|
|
struct ast_conf_member *check_active_video( int id, struct ast_conference *conf )
|
|
{
|
|
struct ast_conf_member *member;
|
|
|
|
// acquire the conference lock
|
|
ast_rwlock_rdlock( &conf->lock ) ;
|
|
|
|
member = conf->memberlist;
|
|
while (member)
|
|
{
|
|
if (member->id == id)
|
|
{
|
|
// lock this member
|
|
ast_mutex_lock( &member->lock ) ;
|
|
ast_rwlock_unlock( &conf->lock ) ;
|
|
return member;
|
|
}
|
|
member = member->next;
|
|
}
|
|
ast_rwlock_unlock( &conf->lock ) ;
|
|
return NULL;
|
|
}
|
|
#endif
|
|
//
|
|
// manange member functions
|
|
//
|
|
|
|
struct ast_conf_member* create_member( struct ast_channel *chan, const char* data )
|
|
{
|
|
//
|
|
// check input
|
|
//
|
|
|
|
if ( chan == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to create member with null channel\n" ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
if ( chan->name == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to create member with null channel name\n" ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
//
|
|
// allocate memory for new conference member
|
|
//
|
|
|
|
struct ast_conf_member *member = calloc( 1, sizeof( struct ast_conf_member ) ) ;
|
|
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to malloc ast_conf_member\n" ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
// initialize mutex
|
|
ast_mutex_init( &member->lock ) ;
|
|
|
|
// initialize cv
|
|
ast_cond_init( &member->delete_var, NULL ) ;
|
|
// initialize flag and count
|
|
member->delete_flag = member->use_count = 0 ;
|
|
|
|
// Default values for parameters that can get overwritten by dialplan arguments
|
|
#ifdef VIDEO
|
|
member->video_start_timeout = AST_CONF_VIDEO_START_TIMEOUT;
|
|
member->video_stop_timeout = AST_CONF_VIDEO_STOP_TIMEOUT;
|
|
#endif
|
|
member->priority = 0;
|
|
member->vad_prob_start = AST_CONF_PROB_START;
|
|
member->vad_prob_continue = AST_CONF_PROB_CONTINUE;
|
|
member->max_users = AST_CONF_MAX_USERS;
|
|
member->type = NULL;
|
|
|
|
//
|
|
// initialize member with passed data values
|
|
//
|
|
char argstr[256] ;
|
|
|
|
// copy the passed data
|
|
strncpy( argstr, data, sizeof(argstr) - 1 ) ;
|
|
|
|
// point to the copied data
|
|
char *stringp = argstr;
|
|
|
|
ast_log( AST_CONF_DEBUG, "attempting to parse passed params, stringp => %s\n", stringp ) ;
|
|
|
|
// parse the id
|
|
char *token;
|
|
if ( ( token = strsep( &stringp, "/" ) ) != NULL )
|
|
{
|
|
member->conf_name = malloc( strlen( token ) + 1 ) ;
|
|
strcpy( member->conf_name, token ) ;
|
|
}
|
|
else
|
|
{
|
|
ast_log( LOG_ERROR, "unable to parse member id\n" ) ;
|
|
free( member ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
// parse the flags
|
|
if ( ( token = strsep( &stringp, "/" ) ) != NULL )
|
|
{
|
|
member->flags = malloc( strlen( token ) + 1 ) ;
|
|
strcpy( member->flags, token ) ;
|
|
}
|
|
else
|
|
{
|
|
// make member->flags something
|
|
member->flags = malloc( sizeof( char ) ) ;
|
|
memset( member->flags, 0x0, sizeof( char ) ) ;
|
|
}
|
|
|
|
while ( (token = strsep(&stringp, "/")) != NULL )
|
|
{
|
|
static const char arg_priority[] = "priority";
|
|
static const char arg_vad_prob_start[] = "vad_prob_start";
|
|
static const char arg_vad_prob_continue[] = "vad_prob_continue";
|
|
#ifdef VIDEO
|
|
static const char arg_video_start_timeout[] = "video_start_timeout";
|
|
static const char arg_video_stop_timeout[] = "video_stop_timeout";
|
|
#endif
|
|
static const char arg_max_users[] = "max_users";
|
|
static const char arg_conf_type[] = "type";
|
|
|
|
char *value = token;
|
|
const char *key = strsep(&value, "=");
|
|
|
|
if ( key == NULL || value == NULL )
|
|
{
|
|
ast_log(LOG_WARNING, "Incorrect argument %s\n", token);
|
|
continue;
|
|
}
|
|
if ( strncasecmp(key, arg_priority, sizeof(arg_priority) - 1) == 0 )
|
|
{
|
|
member->priority = strtol(value, (char **)NULL, 10);
|
|
ast_log(AST_CONF_DEBUG, "priority = %d\n", member->priority);
|
|
} else if ( strncasecmp(key, arg_vad_prob_start, sizeof(arg_vad_prob_start) - 1) == 0 )
|
|
{
|
|
member->vad_prob_start = strtof(value, (char **)NULL);
|
|
ast_log(AST_CONF_DEBUG, "vad_prob_start = %f\n", member->vad_prob_start);
|
|
} else if ( strncasecmp(key, arg_vad_prob_continue, sizeof(arg_vad_prob_continue) - 1) == 0 )
|
|
{
|
|
member->vad_prob_continue = strtof(value, (char **)NULL);
|
|
ast_log(AST_CONF_DEBUG, "vad_prob_continue = %f\n", member->vad_prob_continue);
|
|
#ifdef VIDEO
|
|
} else if ( strncasecmp(key, arg_video_start_timeout, sizeof(arg_video_start_timeout) - 1) == 0 )
|
|
{
|
|
member->video_start_timeout = strtol(value, (char **)NULL, 10);
|
|
ast_log(AST_CONF_DEBUG, "video_start_timeout = %d\n", member->video_start_timeout);
|
|
} else if ( strncasecmp(key, arg_video_stop_timeout, sizeof(arg_video_stop_timeout) - 1) == 0 )
|
|
{
|
|
member->video_stop_timeout = strtol(value, (char **)NULL, 10);
|
|
ast_log(AST_CONF_DEBUG, "video_stop_timeout = %d\n", member->video_stop_timeout);
|
|
#endif
|
|
} else if ( strncasecmp(key, arg_max_users, sizeof(arg_max_users) - 1) == 0 )
|
|
{
|
|
member->max_users = strtol(value, (char **)NULL, 10);
|
|
ast_log(AST_CONF_DEBUG, "max_users = %d\n", member->max_users);
|
|
} else if ( strncasecmp(key, arg_conf_type, sizeof(arg_conf_type) - 1) == 0 )
|
|
{
|
|
member->type = malloc( strlen( value ) + 1 ) ;
|
|
strcpy( member->type, value ) ;
|
|
ast_log(AST_CONF_DEBUG, "type = %s\n", member->type);
|
|
} else
|
|
{
|
|
ast_log(LOG_WARNING, "unknown parameter %s with value %s\n", key, value);
|
|
}
|
|
}
|
|
|
|
//
|
|
// initialize member with default values
|
|
//
|
|
|
|
// keep pointer to member's channel
|
|
member->chan = chan ;
|
|
|
|
// copy the channel name
|
|
member->channel_name = malloc( strlen( chan->name ) + 1 ) ;
|
|
strcpy( member->channel_name, chan->name ) ;
|
|
|
|
// copy the uniqueid
|
|
member->uniqueid = malloc( strlen( chan->uniqueid ) + 1 ) ;
|
|
strcpy( member->uniqueid, chan->uniqueid ) ;
|
|
|
|
// set default if no type parameter
|
|
if (!member->type) {
|
|
member->type = malloc( strlen( AST_CONF_TYPE_DEFAULT ) + 1 ) ;
|
|
strcpy( member->type, AST_CONF_TYPE_DEFAULT ) ;
|
|
ast_log(AST_CONF_DEBUG, "type = %s\n", member->type);
|
|
}
|
|
|
|
// ( default can be overridden by passed flags )
|
|
member->mute_audio = 0;
|
|
#ifdef VIDEO
|
|
member->mute_video = 0;
|
|
#endif
|
|
member->talk_volume = 0;
|
|
member->listen_volume = 0;
|
|
member->norecv_audio = 0;
|
|
#ifdef VIDEO
|
|
member->norecv_video = 0;
|
|
member->no_camera = 0;
|
|
#endif
|
|
// moderator?
|
|
member->ismoderator = 0;
|
|
member->kick_conferees = 0;
|
|
|
|
// ready flag
|
|
member->ready_for_outgoing = 0 ;
|
|
|
|
// incoming frame queue
|
|
member->inFrames = NULL ;
|
|
member->inFramesTail = NULL ;
|
|
member->inFramesCount = 0 ;
|
|
#ifdef VIDEO
|
|
member->inVideoFrames = NULL ;
|
|
member->inVideoFramesTail = NULL ;
|
|
member->inVideoFramesCount = 0 ;
|
|
#endif
|
|
#ifdef DTMF
|
|
member->inDTMFFrames = NULL ;
|
|
member->inDTMFFramesTail = NULL ;
|
|
member->inDTMFFramesCount = 0 ;
|
|
#endif
|
|
#ifdef TEXT
|
|
member->inTextFrames = NULL ;
|
|
member->inTextFramesTail = NULL ;
|
|
member->inTextFramesCount = 0 ;
|
|
#endif
|
|
#ifdef VIDEO
|
|
member->conference = 1; // we have switched req_id
|
|
member->dtmf_switch = 0; // no dtmf switch by default
|
|
#endif
|
|
member->dtmf_relay = 0; // no dtmf relay by default
|
|
|
|
// start of day video ids
|
|
#ifdef VIDEO
|
|
member->req_id = -1;
|
|
#endif
|
|
member->id = -1;
|
|
|
|
member->first_frame_received = 0; // cause a FIR after NAT delay
|
|
|
|
// last frame caching
|
|
member->inFramesRepeatLast = 0 ;
|
|
member->inFramesLast = NULL ;
|
|
member->okayToCacheLast = 0 ;
|
|
|
|
// outgoing frame queue
|
|
member->outFrames = NULL ;
|
|
member->outFramesTail = NULL ;
|
|
member->outFramesCount = 0 ;
|
|
#ifdef VIDEO
|
|
member->outVideoFrames = NULL ;
|
|
member->outVideoFramesTail = NULL ;
|
|
member->outVideoFramesCount = 0 ;
|
|
#endif
|
|
#ifdef DTMF
|
|
member->outDTMFFrames = NULL ;
|
|
member->outDTMFFramesTail = NULL ;
|
|
member->outDTMFFramesCount = 0 ;
|
|
#endif
|
|
#ifdef TEXT
|
|
member->outTextFrames = NULL ;
|
|
member->outTextFramesTail = NULL ;
|
|
member->outTextFramesCount = 0 ;
|
|
#endif
|
|
// ( not currently used )
|
|
// member->samplesperframe = AST_CONF_BLOCK_SAMPLES ;
|
|
|
|
// used for determining need to mix frames
|
|
// and for management interface notification
|
|
// and for VAD based video switching
|
|
member->speaking_state_notify = 0 ;
|
|
member->speaking_state = 0 ;
|
|
member->local_speaking_state = 0;
|
|
member->last_state_change = (struct timeval){0, 0};
|
|
member->speaker_count = 0;
|
|
#ifdef VIDEO
|
|
member->driven_member = NULL;
|
|
|
|
member->video_broadcast_active = 0;
|
|
member->last_video_frame_time = (struct timeval){0, 0};
|
|
|
|
member->video_started = 0;
|
|
#endif
|
|
// linked-list pointers
|
|
member->next = NULL ;
|
|
#ifndef VIDEO
|
|
member->prev = NULL ;
|
|
#endif
|
|
// account data
|
|
member->frames_in = 0 ;
|
|
member->frames_in_dropped = 0 ;
|
|
member->frames_out = 0 ;
|
|
member->frames_out_dropped = 0 ;
|
|
#ifdef VIDEO
|
|
member->video_frames_in = 0 ;
|
|
member->video_frames_in_dropped = 0 ;
|
|
member->video_frames_out = 0 ;
|
|
member->video_frames_out_dropped = 0 ;
|
|
#endif
|
|
#ifdef DTMF
|
|
member->dtmf_frames_in = 0 ;
|
|
member->dtmf_frames_in_dropped = 0 ;
|
|
member->dtmf_frames_out = 0 ;
|
|
member->dtmf_frames_out_dropped = 0 ;
|
|
#endif
|
|
#ifdef TEXT
|
|
member->text_frames_in = 0 ;
|
|
member->text_frames_in_dropped = 0 ;
|
|
member->text_frames_out = 0 ;
|
|
member->text_frames_out_dropped = 0 ;
|
|
#endif
|
|
// for counting sequentially dropped frames
|
|
member->sequential_drops = 0 ;
|
|
member->since_dropped = 0 ;
|
|
|
|
// flags
|
|
member->kick_flag = 0;
|
|
member->max_users_flag = 0;
|
|
|
|
// record start time
|
|
// init dropped frame timestamps
|
|
// init state change timestamp
|
|
member->time_entered =
|
|
member->last_in_dropped =
|
|
member->last_out_dropped =
|
|
member->last_state_change = ast_tvnow();
|
|
|
|
//
|
|
// parse passed flags
|
|
//
|
|
|
|
// silence detection flags w/ defaults
|
|
member->vad_flag = 0 ;
|
|
member->denoise_flag = 0 ;
|
|
member->agc_flag = 0 ;
|
|
|
|
// is this member using the telephone?
|
|
member->via_telephone = 0 ;
|
|
|
|
// temp pointer to flags string
|
|
char* flags = member->flags ;
|
|
|
|
int i;
|
|
|
|
for ( i = 0 ; i < strlen( flags ) ; ++i )
|
|
{
|
|
#ifdef VIDEO
|
|
if (flags[i] >= (int)'0' && flags[i] <= (int)'9')
|
|
{
|
|
if (member->req_id < 0)
|
|
{
|
|
member->req_id = flags[i] - (int)'0';
|
|
}
|
|
else
|
|
{
|
|
int newid = flags[i] - (int)'0';
|
|
// need to boot anyone with this id already
|
|
// will happen in add_member
|
|
member->id = newid;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
// allowed flags are C, c, L, l, V, D, A, C, X, R, T, t, M, S, z, o, F
|
|
// mute/no_recv options
|
|
switch ( flags[i] )
|
|
{
|
|
#ifdef VIDEO
|
|
case 'C':
|
|
member->mute_video = 1;
|
|
break ;
|
|
case 'c':
|
|
member->norecv_video = 1;
|
|
break ;
|
|
#endif
|
|
case 'L':
|
|
member->mute_audio = 1;
|
|
break ;
|
|
case 'l':
|
|
member->norecv_audio = 1;
|
|
break;
|
|
|
|
// speex preprocessing options
|
|
case 'V':
|
|
member->vad_flag = 1 ;
|
|
break ;
|
|
case 'D':
|
|
member->denoise_flag = 1 ;
|
|
break ;
|
|
case 'A':
|
|
member->agc_flag = 1 ;
|
|
break ;
|
|
|
|
// dtmf/moderator/video switching options
|
|
#ifdef VIDEO
|
|
case 'X':
|
|
member->dtmf_switch = 1;
|
|
break;
|
|
#endif
|
|
case 'R':
|
|
member->dtmf_relay = 1;
|
|
break;
|
|
#ifdef VIDEO
|
|
case 'S':
|
|
member->vad_switch = 1;
|
|
break;
|
|
case 'F':
|
|
member->force_vad_switch = 1;
|
|
break;
|
|
#endif
|
|
case 'M':
|
|
member->ismoderator = 1;
|
|
break;
|
|
#ifdef VIDEO
|
|
case 'N':
|
|
member->no_camera = 1;
|
|
break;
|
|
#endif
|
|
#ifdef TEXT
|
|
case 't':
|
|
member->does_text = 1;
|
|
break;
|
|
#endif
|
|
#ifdef VIDEO
|
|
case 'z':
|
|
member->vad_linger = 1;
|
|
break;
|
|
case 'o':
|
|
member->does_chat_mode = 1;
|
|
break;
|
|
#endif
|
|
case 'x':
|
|
member->kick_conferees = 1;
|
|
break;
|
|
|
|
//Telephone connection
|
|
case 'a':
|
|
member->vad_flag = 1 ;
|
|
case 'T':
|
|
member->via_telephone = 1;
|
|
break;
|
|
|
|
default:
|
|
break ;
|
|
}
|
|
}
|
|
}
|
|
|
|
// set the dsp to null so silence detection is disabled by default
|
|
member->dsp = NULL ;
|
|
|
|
#if ( SILDET == 2 )
|
|
//
|
|
// configure silence detection and preprocessing
|
|
// if the user is coming in via the telephone,
|
|
// and is not listen-only
|
|
//
|
|
if ( member->via_telephone == 1)
|
|
{
|
|
// create a speex preprocessor
|
|
member->dsp = speex_preprocess_state_init( AST_CONF_BLOCK_SAMPLES, AST_CONF_SAMPLE_RATE ) ;
|
|
|
|
if ( member->dsp == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to initialize member dsp, channel => %s\n", chan->name ) ;
|
|
}
|
|
else
|
|
{
|
|
ast_log( AST_CONF_DEBUG, "member dsp initialized, channel => %s, v => %d, d => %d, a => %d\n",
|
|
chan->name, member->vad_flag, member->denoise_flag, member->agc_flag ) ;
|
|
|
|
// set speex preprocessor options
|
|
speex_preprocess_ctl( member->dsp, SPEEX_PREPROCESS_SET_VAD, &(member->vad_flag) ) ;
|
|
speex_preprocess_ctl( member->dsp, SPEEX_PREPROCESS_SET_DENOISE, &(member->denoise_flag) ) ;
|
|
speex_preprocess_ctl( member->dsp, SPEEX_PREPROCESS_SET_AGC, &(member->agc_flag) ) ;
|
|
|
|
speex_preprocess_ctl( member->dsp, SPEEX_PREPROCESS_SET_PROB_START, &member->vad_prob_start ) ;
|
|
speex_preprocess_ctl( member->dsp, SPEEX_PREPROCESS_SET_PROB_CONTINUE, &member->vad_prob_continue ) ;
|
|
|
|
ast_log( AST_CONF_DEBUG, "speech_prob_start => %f, speech_prob_continue => %f\n",
|
|
member->dsp->speech_prob_start, member->dsp->speech_prob_continue ) ;
|
|
}
|
|
}
|
|
#endif
|
|
//
|
|
// read, write, and translation options
|
|
//
|
|
|
|
// set member's audio formats, taking dsp preprocessing into account
|
|
// ( chan->nativeformats, AST_FORMAT_SLINEAR, AST_FORMAT_ULAW, AST_FORMAT_GSM )
|
|
member->read_format = ( member->dsp == NULL ) ? chan->nativeformats : AST_FORMAT_SLINEAR ;
|
|
|
|
member->write_format = chan->nativeformats;
|
|
|
|
// 1.2 or 1.3+
|
|
#ifdef AST_FORMAT_AUDIO_MASK
|
|
|
|
member->read_format &= AST_FORMAT_AUDIO_MASK;
|
|
member->write_format &= AST_FORMAT_AUDIO_MASK;
|
|
#endif
|
|
|
|
// translation paths ( ast_translator_build_path() returns null if formats match )
|
|
member->to_slinear = ast_translator_build_path( AST_FORMAT_SLINEAR, member->read_format ) ;
|
|
member->from_slinear = ast_translator_build_path( member->write_format, AST_FORMAT_SLINEAR ) ;
|
|
|
|
ast_log( AST_CONF_DEBUG, "AST_FORMAT_SLINEAR => %d\n", AST_FORMAT_SLINEAR ) ;
|
|
|
|
// index for converted_frames array
|
|
switch ( member->write_format )
|
|
{
|
|
case AST_FORMAT_SLINEAR:
|
|
member->write_format_index = AC_SLINEAR_INDEX ;
|
|
break ;
|
|
|
|
case AST_FORMAT_ULAW:
|
|
member->write_format_index = AC_ULAW_INDEX ;
|
|
break ;
|
|
|
|
case AST_FORMAT_ALAW:
|
|
member->write_format_index = AC_ALAW_INDEX ;
|
|
break ;
|
|
|
|
case AST_FORMAT_GSM:
|
|
member->write_format_index = AC_GSM_INDEX ;
|
|
break ;
|
|
|
|
case AST_FORMAT_SPEEX:
|
|
member->write_format_index = AC_SPEEX_INDEX;
|
|
break;
|
|
|
|
#ifdef AC_USE_G729A
|
|
case AST_FORMAT_G729A:
|
|
member->write_format_index = AC_G729A_INDEX;
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
member->write_format_index = 0 ;
|
|
}
|
|
|
|
// index for converted_frames array
|
|
switch ( member->read_format )
|
|
{
|
|
case AST_FORMAT_SLINEAR:
|
|
member->read_format_index = AC_SLINEAR_INDEX ;
|
|
break ;
|
|
|
|
case AST_FORMAT_ULAW:
|
|
member->read_format_index = AC_ULAW_INDEX ;
|
|
break ;
|
|
|
|
case AST_FORMAT_ALAW:
|
|
member->read_format_index = AC_ALAW_INDEX ;
|
|
break ;
|
|
|
|
case AST_FORMAT_GSM:
|
|
member->read_format_index = AC_GSM_INDEX ;
|
|
break ;
|
|
|
|
case AST_FORMAT_SPEEX:
|
|
member->read_format_index = AC_SPEEX_INDEX;
|
|
break;
|
|
|
|
#ifdef AC_USE_G729A
|
|
case AST_FORMAT_G729A:
|
|
member->read_format_index = AC_G729A_INDEX;
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
member->read_format_index = 0 ;
|
|
}
|
|
|
|
// smoother defaults.
|
|
member->smooth_multiple =1;
|
|
member->smooth_size_in = -1;
|
|
member->smooth_size_out = -1;
|
|
member->inSmoother= NULL;
|
|
member->outPacker= NULL;
|
|
|
|
switch (member->read_format){
|
|
/* these assumptions may be incorrect */
|
|
case AST_FORMAT_ULAW:
|
|
case AST_FORMAT_ALAW:
|
|
member->smooth_size_in = 160; //bytes
|
|
member->smooth_size_out = 160; //samples
|
|
break;
|
|
case AST_FORMAT_GSM:
|
|
/*
|
|
member->smooth_size_in = 33; //bytes
|
|
member->smooth_size_out = 160;//samples
|
|
*/
|
|
break;
|
|
case AST_FORMAT_SPEEX:
|
|
case AST_FORMAT_G729A:
|
|
/* this assumptions are wrong
|
|
member->smooth_multiple = 2 ; // for testing, force to dual frame
|
|
member->smooth_size_in = 39; // bytes
|
|
member->smooth_size_out = 160; // samples
|
|
*/
|
|
break;
|
|
case AST_FORMAT_SLINEAR:
|
|
member->smooth_size_in = 320; //bytes
|
|
member->smooth_size_out = 160; //samples
|
|
break;
|
|
default:
|
|
member->inSmoother = NULL; //don't use smoother for this type.
|
|
//ast_log( AST_CONF_DEBUG, "smoother is NULL for member->read_format => %d\n", member->read_format);
|
|
}
|
|
|
|
if (member->smooth_size_in > 0){
|
|
member->inSmoother = ast_smoother_new(member->smooth_size_in);
|
|
ast_log( AST_CONF_DEBUG, "created smoother(%d) for %d\n", member->smooth_size_in , member->read_format);
|
|
}
|
|
|
|
//
|
|
// finish up
|
|
//
|
|
|
|
ast_log( AST_CONF_DEBUG, "created member, type => %s, priority => %d, readformat => %d\n",
|
|
member->type, member->priority, chan->readformat ) ;
|
|
|
|
return member ;
|
|
}
|
|
|
|
struct ast_conf_member* delete_member( struct ast_conf_member* member )
|
|
{
|
|
// !!! NO RETURN TEST !!!
|
|
// do { sleep(1) ; } while (1) ;
|
|
|
|
// !!! CRASH TEST !!!
|
|
// *((int *)0) = 0;
|
|
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to the delete null member\n" ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
ast_mutex_lock ( &member->lock ) ;
|
|
|
|
member->delete_flag = 1 ;
|
|
if ( member->use_count )
|
|
ast_cond_wait ( &member->delete_var, &member->lock ) ;
|
|
|
|
ast_mutex_unlock ( &member->lock ) ;
|
|
|
|
#ifdef VIDEO
|
|
// If member is driving another member, make sure its speaker count is correct
|
|
if ( member->driven_member != NULL && member->speaking_state == 1 )
|
|
decrement_speaker_count(member->driven_member, 1);
|
|
#endif
|
|
//
|
|
// clean up member flags
|
|
//
|
|
|
|
if ( member->flags != NULL )
|
|
{
|
|
// !!! DEBUGING !!!
|
|
ast_log( AST_CONF_DEBUG, "freeing member flags, name => %s\n",
|
|
member->channel_name ) ;
|
|
free( member->flags ) ;
|
|
}
|
|
|
|
//
|
|
// delete the members frames
|
|
//
|
|
|
|
conf_frame* cf ;
|
|
|
|
// !!! DEBUGING !!!
|
|
ast_log( AST_CONF_DEBUG, "deleting member input frames, name => %s\n",
|
|
member->channel_name ) ;
|
|
|
|
// incoming frames
|
|
cf = member->inFrames ;
|
|
|
|
while ( cf != NULL )
|
|
{
|
|
cf = delete_conf_frame( cf ) ;
|
|
}
|
|
|
|
if (member->inSmoother != NULL)
|
|
ast_smoother_free(member->inSmoother);
|
|
#ifdef VIDEO
|
|
cf = member->inVideoFrames ;
|
|
|
|
while ( cf != NULL )
|
|
{
|
|
cf = delete_conf_frame( cf ) ;
|
|
}
|
|
#endif
|
|
// !!! DEBUGING !!!
|
|
ast_log( AST_CONF_DEBUG, "deleting member output frames, name => %s\n",
|
|
member->channel_name ) ;
|
|
|
|
// outgoing frames
|
|
cf = member->outFrames ;
|
|
|
|
while ( cf != NULL )
|
|
{
|
|
cf = delete_conf_frame( cf ) ;
|
|
}
|
|
#ifdef VIDEO
|
|
cf = member->outVideoFrames ;
|
|
|
|
while ( cf != NULL )
|
|
{
|
|
cf = delete_conf_frame( cf ) ;
|
|
}
|
|
#endif
|
|
#if ( SILDET == 2 )
|
|
if ( member->dsp != NULL )
|
|
{
|
|
// !!! DEBUGING !!!
|
|
ast_log( AST_CONF_DEBUG, "destroying member preprocessor, name => %s\n",
|
|
member->channel_name ) ;
|
|
speex_preprocess_state_destroy( member->dsp ) ;
|
|
}
|
|
#endif
|
|
|
|
// !!! DEBUGING !!!
|
|
ast_log( AST_CONF_DEBUG, "freeing member translator paths, name => %s\n",
|
|
member->channel_name ) ;
|
|
|
|
// free the mixing translators
|
|
ast_translator_free_path( member->to_slinear ) ;
|
|
ast_translator_free_path( member->from_slinear ) ;
|
|
|
|
// get a pointer to the next
|
|
// member so we can return it
|
|
struct ast_conf_member* nm = member->next ;
|
|
|
|
// !!! DEBUGING !!!
|
|
ast_log( AST_CONF_DEBUG, "freeing member channel name, name => %s\n",
|
|
member->channel_name ) ;
|
|
|
|
// free the member's copy for the channel name
|
|
free( member->channel_name ) ;
|
|
|
|
// free the member's copy of the uniqueid
|
|
free( member->uniqueid ) ;
|
|
|
|
// free the member's copy of the conference name
|
|
free(member->conf_name);
|
|
|
|
// free the member's copy of the conference type
|
|
free(member->type);
|
|
|
|
// free the member's memory
|
|
free(member->callerid);
|
|
free(member->callername);
|
|
|
|
// clear all sounds
|
|
struct ast_conf_soundq *sound = member->soundq;
|
|
struct ast_conf_soundq *next;
|
|
|
|
while ( sound )
|
|
{
|
|
next = sound->next;
|
|
if ( sound->stream )
|
|
ast_closestream( sound->stream );
|
|
free( sound );
|
|
sound = next;
|
|
}
|
|
|
|
// !!! DEBUGING !!!
|
|
ast_log( AST_CONF_DEBUG, "freeing member\n" ) ;
|
|
|
|
free( member ) ;
|
|
member = NULL ;
|
|
|
|
return nm ;
|
|
}
|
|
|
|
//
|
|
// incoming frame functions
|
|
//
|
|
#ifdef VIDEO
|
|
conf_frame* get_incoming_video_frame( struct ast_conf_member *member )
|
|
{
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to get frame from null member\n" ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
if ( member->inVideoFramesCount == 0 )
|
|
{
|
|
ast_mutex_unlock(&member->lock);
|
|
return NULL ;
|
|
}
|
|
|
|
//
|
|
// return the next frame in the queue
|
|
//
|
|
|
|
conf_frame* cfr = NULL ;
|
|
|
|
// get first frame in line
|
|
cfr = member->inVideoFramesTail ;
|
|
|
|
// if it's the only frame, reset the queue,
|
|
// else, move the second frame to the front
|
|
if ( member->inVideoFramesTail == member->inVideoFrames )
|
|
{
|
|
member->inVideoFramesTail = NULL ;
|
|
member->inVideoFrames = NULL ;
|
|
}
|
|
else
|
|
{
|
|
// move the pointer to the next frame
|
|
member->inVideoFramesTail = member->inVideoFramesTail->prev ;
|
|
|
|
// reset it's 'next' pointer
|
|
if ( member->inVideoFramesTail != NULL )
|
|
member->inVideoFramesTail->next = NULL ;
|
|
}
|
|
|
|
// separate the conf frame from the list
|
|
cfr->next = NULL ;
|
|
cfr->prev = NULL ;
|
|
|
|
// decrement frame count
|
|
member->inVideoFramesCount-- ;
|
|
|
|
ast_mutex_unlock(&member->lock);
|
|
return cfr ;
|
|
|
|
}
|
|
#endif
|
|
#ifdef DTMF
|
|
conf_frame* get_incoming_dtmf_frame( struct ast_conf_member *member )
|
|
{
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to get frame from null member\n" ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
if ( member->inDTMFFramesCount == 0 )
|
|
{
|
|
ast_mutex_unlock(&member->lock);
|
|
return NULL ;
|
|
}
|
|
|
|
//
|
|
// return the next frame in the queue
|
|
//
|
|
|
|
conf_frame* cfr = NULL ;
|
|
|
|
// get first frame in line
|
|
cfr = member->inDTMFFramesTail ;
|
|
|
|
// if it's the only frame, reset the queue,
|
|
// else, move the second frame to the front
|
|
if ( member->inDTMFFramesTail == member->inDTMFFrames )
|
|
{
|
|
member->inDTMFFramesTail = NULL ;
|
|
member->inDTMFFrames = NULL ;
|
|
}
|
|
else
|
|
{
|
|
// move the pointer to the next frame
|
|
member->inDTMFFramesTail = member->inDTMFFramesTail->prev ;
|
|
|
|
// reset it's 'next' pointer
|
|
if ( member->inDTMFFramesTail != NULL )
|
|
member->inDTMFFramesTail->next = NULL ;
|
|
}
|
|
|
|
// separate the conf frame from the list
|
|
cfr->next = NULL ;
|
|
cfr->prev = NULL ;
|
|
|
|
// decriment frame count
|
|
member->inDTMFFramesCount-- ;
|
|
|
|
ast_mutex_unlock(&member->lock);
|
|
return cfr ;
|
|
|
|
}
|
|
#endif
|
|
|
|
conf_frame* get_incoming_frame( struct ast_conf_member *member )
|
|
{
|
|
conf_frame *cf_result;
|
|
//
|
|
// sanity checks
|
|
//
|
|
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to get frame from null member\n" ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
//
|
|
// repeat last frame a couple times to smooth transition
|
|
//
|
|
|
|
#ifdef AST_CONF_CACHE_LAST_FRAME
|
|
if ( member->inFramesCount == 0 )
|
|
{
|
|
// nothing to do if there's no cached frame
|
|
if ( member->inFramesLast == NULL ) {
|
|
ast_mutex_unlock(&member->lock);
|
|
return NULL ;
|
|
}
|
|
|
|
// turn off 'okay to cache' flag
|
|
member->okayToCacheLast = 0 ;
|
|
|
|
if ( member->inFramesRepeatLast >= AST_CONF_CACHE_LAST_FRAME )
|
|
{
|
|
// already used this frame AST_CONF_CACHE_LAST_FRAME times
|
|
|
|
// reset repeat count
|
|
member->inFramesRepeatLast = 0 ;
|
|
|
|
// clear the cached frame
|
|
delete_conf_frame( member->inFramesLast ) ;
|
|
member->inFramesLast = NULL ;
|
|
|
|
// return null
|
|
ast_mutex_unlock(&member->lock);
|
|
return NULL ;
|
|
}
|
|
else
|
|
{
|
|
ast_log( AST_CONF_DEBUG, "repeating cached frame, channel => %s, inFramesRepeatLast => %d\n",
|
|
member->channel_name, member->inFramesRepeatLast ) ;
|
|
|
|
// increment counter
|
|
member->inFramesRepeatLast++ ;
|
|
|
|
// return a copy of the cached frame
|
|
cf_result = copy_conf_frame( member->inFramesLast ) ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return cf_result;
|
|
}
|
|
}
|
|
else if ( member->okayToCacheLast == 0 && member->inFramesCount >= 3 )
|
|
{
|
|
ast_log( AST_CONF_DEBUG, "enabling cached frame, channel => %s, incoming => %d, outgoing => %d\n",
|
|
member->channel_name, member->inFramesCount, member->outFramesCount ) ;
|
|
|
|
// turn on 'okay to cache' flag
|
|
member->okayToCacheLast = 1 ;
|
|
}
|
|
#else
|
|
if ( member->inFramesCount == 0 ) {
|
|
ast_mutex_unlock(&member->lock);
|
|
return NULL ;
|
|
}
|
|
#endif // AST_CONF_CACHE_LAST_FRAME
|
|
|
|
//
|
|
// return the next frame in the queue
|
|
//
|
|
|
|
conf_frame* cfr = NULL ;
|
|
|
|
// get first frame in line
|
|
cfr = member->inFramesTail ;
|
|
|
|
// if it's the only frame, reset the queue,
|
|
// else, move the second frame to the front
|
|
if ( member->inFramesTail == member->inFrames )
|
|
{
|
|
member->inFramesTail = NULL ;
|
|
member->inFrames = NULL ;
|
|
}
|
|
else
|
|
{
|
|
// move the pointer to the next frame
|
|
member->inFramesTail = member->inFramesTail->prev ;
|
|
|
|
// reset it's 'next' pointer
|
|
if ( member->inFramesTail != NULL )
|
|
member->inFramesTail->next = NULL ;
|
|
}
|
|
|
|
// separate the conf frame from the list
|
|
cfr->next = NULL ;
|
|
cfr->prev = NULL ;
|
|
|
|
// decriment frame count
|
|
member->inFramesCount-- ;
|
|
|
|
#ifdef AST_CONF_CACHE_LAST_FRAME
|
|
// copy frame if queue is now empty
|
|
if (
|
|
member->inFramesCount == 0
|
|
&& member->okayToCacheLast == 1
|
|
)
|
|
{
|
|
// reset repeat count
|
|
member->inFramesRepeatLast = 0 ;
|
|
|
|
// clear cached frame
|
|
if ( member->inFramesLast != NULL )
|
|
{
|
|
delete_conf_frame( member->inFramesLast ) ;
|
|
member->inFramesLast = NULL ;
|
|
}
|
|
|
|
// cache new frame
|
|
member->inFramesLast = copy_conf_frame( cfr ) ;
|
|
}
|
|
#endif // AST_CONF_CACHE_LAST_FRAME
|
|
|
|
ast_mutex_unlock(&member->lock);
|
|
return cfr ;
|
|
}
|
|
#ifdef VIDEO
|
|
int queue_incoming_video_frame( struct ast_conf_member* member, const struct ast_frame* fr )
|
|
{
|
|
// check on frame
|
|
if ( fr == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to queue null frame\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
// check on member
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to queue frame for null member\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
// lock the member
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
if (!member->first_frame_received)
|
|
{
|
|
// nat=yes will be correct now
|
|
member->first_frame_received = 1;
|
|
member->conference = 1;
|
|
}
|
|
|
|
// We have to drop if the queue is full!
|
|
if ( member->inVideoFramesCount >= AST_CONF_MAX_VIDEO_QUEUE )
|
|
{
|
|
ast_log(
|
|
AST_CONF_DEBUG,
|
|
"unable to queue incoming VIDEO frame, channel => %s, incoming => %d, outgoing => %d\n",
|
|
member->channel_name, member->inVideoFramesCount, member->outVideoFramesCount
|
|
) ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return -1 ;
|
|
}
|
|
|
|
//
|
|
// create new conf frame from passed data frame
|
|
//
|
|
|
|
// ( member->inFrames may be null at this point )
|
|
conf_frame* cfr = create_conf_frame( member, member->inVideoFrames, fr ) ;
|
|
|
|
if ( cfr == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to malloc conf_frame\n" ) ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return -1 ;
|
|
}
|
|
|
|
// copy frame data pointer to conf frame
|
|
// cfr->fr = fr ;
|
|
|
|
//
|
|
// add new frame to speaking members incoming frame queue
|
|
// ( i.e. save this frame data, so we can distribute it in conference_exec later )
|
|
//
|
|
|
|
if ( member->inVideoFrames == NULL )
|
|
{
|
|
// this is the first frame in the buffer
|
|
member->inVideoFramesTail = cfr ;
|
|
member->inVideoFrames = cfr ;
|
|
}
|
|
else
|
|
{
|
|
// put the new frame at the head of the list
|
|
member->inVideoFrames = cfr ;
|
|
}
|
|
|
|
// increment member frame count
|
|
member->inVideoFramesCount++ ;
|
|
|
|
ast_mutex_unlock(&member->lock);
|
|
|
|
// Everything has gone okay!
|
|
return 0;
|
|
}
|
|
#endif
|
|
#ifdef DTMF
|
|
int queue_incoming_dtmf_frame( struct ast_conf_member* member, const struct ast_frame* fr )
|
|
{
|
|
//ast_log( AST_CONF_DEBUG, "queue incoming video frame\n");
|
|
|
|
// check on frame
|
|
if ( fr == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to queue null frame\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
// check on member
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to queue frame for null member\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
// We have to drop if the queue is full!
|
|
if ( member->inDTMFFramesCount >= AST_CONF_MAX_DTMF_QUEUE )
|
|
{
|
|
ast_log(
|
|
AST_CONF_DEBUG,
|
|
"unable to queue incoming DTMF frame, channel => %s, incoming => %d, outgoing => %d\n",
|
|
member->channel_name, member->inDTMFFramesCount, member->outDTMFFramesCount
|
|
) ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return -1 ;
|
|
}
|
|
|
|
//
|
|
// create new conf frame from passed data frame
|
|
//
|
|
|
|
// ( member->inFrames may be null at this point )
|
|
conf_frame* cfr = create_conf_frame( member, member->inDTMFFrames, fr ) ;
|
|
|
|
if ( cfr == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to malloc conf_frame\n" ) ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return -1 ;
|
|
}
|
|
|
|
// copy frame data pointer to conf frame
|
|
// cfr->fr = fr ;
|
|
|
|
//
|
|
// add new frame to speaking members incoming frame queue
|
|
// ( i.e. save this frame data, so we can distribute it in conference_exec later )
|
|
//
|
|
|
|
if ( member->inDTMFFrames == NULL )
|
|
{
|
|
// this is the first frame in the buffer
|
|
member->inDTMFFramesTail = cfr ;
|
|
member->inDTMFFrames = cfr ;
|
|
}
|
|
else
|
|
{
|
|
// put the new frame at the head of the list
|
|
member->inDTMFFrames = cfr ;
|
|
}
|
|
|
|
// increment member frame count
|
|
member->inDTMFFramesCount++ ;
|
|
|
|
ast_mutex_unlock(&member->lock);
|
|
|
|
// Everything has gone okay!
|
|
return 0;
|
|
}
|
|
#endif
|
|
int queue_incoming_frame( struct ast_conf_member* member, struct ast_frame* fr )
|
|
{
|
|
//
|
|
// sanity checks
|
|
//
|
|
|
|
// check on frame
|
|
if ( fr == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to queue null frame\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
// check on member
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to queue frame for null member\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
if ( member->inFramesCount > member->inFramesNeeded )
|
|
{
|
|
if ( member->inFramesCount > AST_CONF_QUEUE_DROP_THRESHOLD )
|
|
{
|
|
struct timeval curr = ast_tvnow();
|
|
|
|
// time since last dropped frame
|
|
long diff = ast_tvdiff_ms(curr, member->last_in_dropped);
|
|
|
|
// number of milliseconds which must pass between frame drops
|
|
// ( 15 frames => -100ms, 10 frames => 400ms, 5 frames => 900ms, 0 frames => 1400ms, etc. )
|
|
long time_limit = 1000 - ( ( member->inFramesCount - AST_CONF_QUEUE_DROP_THRESHOLD ) * 100 ) ;
|
|
|
|
if ( diff >= time_limit )
|
|
{
|
|
// count sequential drops
|
|
member->sequential_drops++ ;
|
|
|
|
ast_log(
|
|
AST_CONF_DEBUG,
|
|
"dropping frame from input buffer, channel => %s, incoming => %d, outgoing => %d\n",
|
|
member->channel_name, member->inFramesCount, member->outFramesCount
|
|
) ;
|
|
|
|
// accounting: count dropped incoming frames
|
|
member->frames_in_dropped++ ;
|
|
|
|
// reset frames since dropped
|
|
member->since_dropped = 0 ;
|
|
|
|
// delete the frame
|
|
delete_conf_frame( get_incoming_frame( member ) ) ;
|
|
|
|
member->last_in_dropped = ast_tvnow();
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
ast_log(
|
|
AST_CONF_DEBUG,
|
|
"input buffer larger than drop threshold, channel => %s, incoming => %d, outgoing => %d\n",
|
|
member->channel_name, member->inFramesCount, member->outFramesCount
|
|
) ;
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// if we have to drop frames, we'll drop new frames
|
|
// because it's easier ( and doesn't matter much anyway ).
|
|
//
|
|
|
|
if ( member->inFramesCount >= AST_CONF_MAX_QUEUE )
|
|
{
|
|
// count sequential drops
|
|
member->sequential_drops++ ;
|
|
|
|
ast_log(
|
|
AST_CONF_DEBUG,
|
|
"unable to queue incoming frame, channel => %s, incoming => %d, outgoing => %d\n",
|
|
member->channel_name, member->inFramesCount, member->outFramesCount
|
|
) ;
|
|
|
|
// accounting: count dropped incoming frames
|
|
member->frames_in_dropped++ ;
|
|
|
|
// reset frames since dropped
|
|
member->since_dropped = 0 ;
|
|
|
|
ast_mutex_unlock(&member->lock);
|
|
return -1 ;
|
|
}
|
|
|
|
// reset sequential drops
|
|
member->sequential_drops = 0 ;
|
|
|
|
// increment frames since dropped
|
|
member->since_dropped++ ;
|
|
|
|
//
|
|
// create new conf frame from passed data frame
|
|
//
|
|
|
|
// ( member->inFrames may be null at this point )
|
|
if (member->inSmoother == NULL ){
|
|
conf_frame* cfr = create_conf_frame( member, member->inFrames, fr ) ;
|
|
if ( cfr == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to malloc conf_frame\n" ) ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return -1 ;
|
|
}
|
|
|
|
//
|
|
// add new frame to speaking members incoming frame queue
|
|
// ( i.e. save this frame data, so we can distribute it in conference_exec later )
|
|
//
|
|
|
|
if ( member->inFrames == NULL ) {
|
|
member->inFramesTail = cfr ;
|
|
}
|
|
member->inFrames = cfr ;
|
|
member->inFramesCount++ ;
|
|
} else {
|
|
//feed frame(fr) into the smoother
|
|
|
|
// smoother tmp frame
|
|
struct ast_frame *sfr;
|
|
int multiple = 1;
|
|
int i=0;
|
|
|
|
#if 0
|
|
if ( (member->smooth_size_in > 0 ) && (member->smooth_size_in * member->smooth_multiple != fr->datalen) )
|
|
{
|
|
ast_log( AST_CONF_DEBUG, "resetting smooth_size_in. old size=> %d, multiple =>%d, datalen=> %d\n", member->smooth_size_in, member->smooth_multiple, fr->datalen );
|
|
if ( fr->datalen % member->smooth_multiple != 0) {
|
|
// if datalen not divisible by smooth_multiple, assume we're just getting normal encoding.
|
|
// ast_log(AST_CONF_DEBUG,"smooth_multiple does not divide datalen. changing smooth size from %d to %d, multiple => 1\n", member->smooth_size_in, fr->datalen);
|
|
member->smooth_size_in = fr->datalen;
|
|
member->smooth_multiple = 1;
|
|
} else {
|
|
// assume a fixed multiple, so divide into datalen.
|
|
int newsmooth = fr->datalen / member->smooth_multiple ;
|
|
// ast_log(AST_CONF_DEBUG,"datalen is divisible by smooth_multiple, changing smooth size from %d to %d\n", member->smooth_size_in, newsmooth);
|
|
member->smooth_size_in = newsmooth;
|
|
}
|
|
|
|
//free input smoother.
|
|
if (member->inSmoother != NULL)
|
|
ast_smoother_free(member->inSmoother);
|
|
|
|
//make new input smoother.
|
|
member->inSmoother = ast_smoother_new(member->smooth_size_in);
|
|
}
|
|
#endif
|
|
|
|
ast_smoother_feed( member->inSmoother, fr );
|
|
ast_log (AST_CONF_DEBUG, "SMOOTH:Feeding frame into inSmoother, timestamp => %ld.%ld\n", fr->delivery.tv_sec, fr->delivery.tv_usec);
|
|
|
|
if ( multiple > 1 )
|
|
fr->samples /= multiple;
|
|
|
|
// read smoothed version of frames, add to queue
|
|
while( ( sfr = ast_smoother_read( member->inSmoother ) ) ){
|
|
|
|
++i;
|
|
ast_log( AST_CONF_DEBUG , "\treading new frame [%d] from smoother, inFramesCount[%d], \n\tsfr->frametype -> %d , sfr->subclass -> %d , sfr->datalen => %d sfr->samples => %d\n", i , member->inFramesCount , sfr->frametype, sfr->subclass, sfr->datalen, sfr->samples);
|
|
ast_log (AST_CONF_DEBUG, "SMOOTH:Reading frame from inSmoother, i=>%d, timestamp => %ld.%ld\n",i, sfr->delivery.tv_sec, sfr->delivery.tv_usec);
|
|
conf_frame* cfr = create_conf_frame( member, member->inFrames, sfr ) ;
|
|
if ( cfr == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to malloc conf_frame\n" ) ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return -1 ;
|
|
}
|
|
|
|
//
|
|
// add new frame to speaking members incoming frame queue
|
|
// ( i.e. save this frame data, so we can distribute it in conference_exec later )
|
|
//
|
|
|
|
if ( member->inFrames == NULL ) {
|
|
member->inFramesTail = cfr ;
|
|
}
|
|
member->inFrames = cfr ;
|
|
member->inFramesCount++ ;
|
|
}
|
|
}
|
|
ast_mutex_unlock(&member->lock);
|
|
return 0 ;
|
|
}
|
|
|
|
//
|
|
// outgoing frame functions
|
|
//
|
|
|
|
conf_frame* get_outgoing_frame( struct ast_conf_member *member )
|
|
{
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to get frame from null member\n" ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
conf_frame* cfr ;
|
|
|
|
// ast_log( AST_CONF_DEBUG, "getting member frames, count => %d\n", member->outFramesCount ) ;
|
|
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
if ( member->outFramesCount > AST_CONF_MIN_QUEUE )
|
|
{
|
|
cfr = member->outFramesTail ;
|
|
|
|
// if it's the only frame, reset the queu,
|
|
// else, move the second frame to the front
|
|
if ( member->outFramesTail == member->outFrames )
|
|
{
|
|
member->outFrames = NULL ;
|
|
member->outFramesTail = NULL ;
|
|
}
|
|
else
|
|
{
|
|
// move the pointer to the next frame
|
|
member->outFramesTail = member->outFramesTail->prev ;
|
|
|
|
// reset it's 'next' pointer
|
|
if ( member->outFramesTail != NULL )
|
|
member->outFramesTail->next = NULL ;
|
|
}
|
|
|
|
// separate the conf frame from the list
|
|
cfr->next = NULL ;
|
|
cfr->prev = NULL ;
|
|
|
|
// decriment frame count
|
|
member->outFramesCount-- ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return cfr ;
|
|
}
|
|
ast_mutex_unlock(&member->lock);
|
|
return NULL ;
|
|
}
|
|
|
|
int __queue_outgoing_frame( struct ast_conf_member* member, const struct ast_frame* fr, struct timeval delivery )
|
|
{
|
|
// accounting: count the number of outgoing frames for this member
|
|
member->frames_out++ ;
|
|
|
|
//
|
|
// we have to drop frames, so we'll drop new frames
|
|
// because it's easier ( and doesn't matter much anyway ).
|
|
//
|
|
if ( member->outFramesCount >= AST_CONF_MAX_QUEUE )
|
|
{
|
|
ast_log(
|
|
AST_CONF_DEBUG,
|
|
"unable to queue outgoing frame, channel => %s, incoming => %d, outgoing => %d\n",
|
|
member->channel_name, member->inFramesCount, member->outFramesCount
|
|
) ;
|
|
|
|
// accounting: count dropped outgoing frames
|
|
member->frames_out_dropped++ ;
|
|
return -1 ;
|
|
}
|
|
|
|
//
|
|
// create new conf frame from passed data frame
|
|
//
|
|
|
|
conf_frame* cfr = create_conf_frame( member, member->outFrames, fr ) ;
|
|
|
|
if ( cfr == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to create new conf frame\n" ) ;
|
|
|
|
// accounting: count dropped outgoing frames
|
|
member->frames_out_dropped++ ;
|
|
return -1 ;
|
|
}
|
|
|
|
// set delivery timestamp
|
|
cfr->fr->delivery = delivery ;
|
|
|
|
//
|
|
// add new frame to speaking members incoming frame queue
|
|
// ( i.e. save this frame data, so we can distribute it in conference_exec later )
|
|
//
|
|
|
|
if ( member->outFrames == NULL ) {
|
|
member->outFramesTail = cfr ;
|
|
}
|
|
member->outFrames = cfr ;
|
|
member->outFramesCount++ ;
|
|
|
|
// return success
|
|
return 0 ;
|
|
}
|
|
|
|
int queue_outgoing_frame( struct ast_conf_member* member, const struct ast_frame* fr, struct timeval delivery )
|
|
{
|
|
// check on frame
|
|
if ( fr == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to queue null frame\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
// check on member
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to queue frame for null member\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
if ( ( member->outPacker == NULL ) && ( member->smooth_multiple > 1 ) && ( member->smooth_size_out > 0 ) ){
|
|
//ast_log (AST_CONF_DEBUG, "creating outPacker with size => %d \n\t( multiple => %d ) * ( size => %d )\n", member->smooth_multiple * member-> smooth_size_out, member->smooth_multiple , member->smooth_size_out);
|
|
member->outPacker = ast_packer_new( member->smooth_multiple * member->smooth_size_out);
|
|
}
|
|
|
|
if (member->outPacker == NULL ){
|
|
return __queue_outgoing_frame( member, fr, delivery ) ;
|
|
}
|
|
else
|
|
{
|
|
struct ast_frame *sfr;
|
|
int exitval = 0;
|
|
//ast_log (AST_CONF_DEBUG, "sending fr into outPacker, datalen=>%d, samples=>%d\n",fr->datalen, fr->samples);
|
|
ast_packer_feed( member->outPacker , fr );
|
|
while( (sfr = ast_packer_read( member->outPacker ) ) )
|
|
{
|
|
//ast_log (AST_CONF_DEBUG, "read sfr from outPacker, datalen=>%d, samples=>%d\n",sfr->datalen, sfr->samples);
|
|
if ( __queue_outgoing_frame( member, sfr, delivery ) == -1 ) {
|
|
exitval = -1;
|
|
}
|
|
}
|
|
|
|
return exitval;
|
|
}
|
|
}
|
|
|
|
//
|
|
// outgoing frame functions
|
|
//
|
|
#ifdef VIDEO
|
|
conf_frame* get_outgoing_video_frame( struct ast_conf_member *member )
|
|
{
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to get frame from null member\n" ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
conf_frame* cfr ;
|
|
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
// ast_log( AST_CONF_DEBUG, "getting member frames, count => %d\n", member->outFramesCount ) ;
|
|
|
|
if ( member->outVideoFramesCount > AST_CONF_MIN_QUEUE )
|
|
{
|
|
cfr = member->outVideoFramesTail ;
|
|
|
|
// if it's the only frame, reset the queu,
|
|
// else, move the second frame to the front
|
|
if ( member->outVideoFramesTail == member->outVideoFrames )
|
|
{
|
|
member->outVideoFrames = NULL ;
|
|
member->outVideoFramesTail = NULL ;
|
|
}
|
|
else
|
|
{
|
|
// move the pointer to the next frame
|
|
member->outVideoFramesTail = member->outVideoFramesTail->prev ;
|
|
|
|
// reset it's 'next' pointer
|
|
if ( member->outVideoFramesTail != NULL )
|
|
member->outVideoFramesTail->next = NULL ;
|
|
}
|
|
|
|
// separate the conf frame from the list
|
|
cfr->next = NULL ;
|
|
cfr->prev = NULL ;
|
|
|
|
// decriment frame count
|
|
member->outVideoFramesCount-- ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return cfr ;
|
|
}
|
|
|
|
ast_mutex_unlock(&member->lock);
|
|
return NULL ;
|
|
}
|
|
|
|
|
|
|
|
int queue_outgoing_video_frame( struct ast_conf_member* member, const struct ast_frame* fr, struct timeval delivery )
|
|
{
|
|
// check on frame
|
|
if ( fr == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to queue null frame\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
// check on member
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to queue frame for null member\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
// accounting: count the number of outgoing frames for this member
|
|
member->video_frames_out++ ;
|
|
|
|
//
|
|
// we have to drop frames, so we'll drop new frames
|
|
// because it's easier ( and doesn't matter much anyway ).
|
|
//
|
|
if ( member->outVideoFramesCount >= AST_CONF_MAX_VIDEO_QUEUE)
|
|
{
|
|
ast_log(
|
|
AST_CONF_DEBUG,
|
|
"unable to queue outgoing VIDEO frame, channel => %s, incoming => %d, outgoing => %d\n",
|
|
member->channel_name, member->inVideoFramesCount, member->outVideoFramesCount
|
|
) ;
|
|
|
|
// accounting: count dropped outgoing frames
|
|
member->video_frames_out_dropped++ ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return -1 ;
|
|
}
|
|
|
|
//
|
|
// create new conf frame from passed data frame
|
|
//
|
|
|
|
conf_frame* cfr = create_conf_frame( member, member->outVideoFrames, fr ) ;
|
|
|
|
if ( cfr == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to create new conf frame\n" ) ;
|
|
|
|
// accounting: count dropped outgoing frames
|
|
member->video_frames_out_dropped++ ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return -1 ;
|
|
}
|
|
|
|
// set delivery timestamp
|
|
#ifdef VIDEO_SETTIMESTAMP
|
|
cfr->fr->delivery = delivery ;
|
|
#else
|
|
cfr->fr->delivery.tv_sec = 0;
|
|
cfr->fr->delivery.tv_usec = 0;
|
|
#endif
|
|
//ast_log (LOG_WARNING,"%d\n",cfr->fr->seqno);
|
|
|
|
#ifdef RTP_SEQNO_ZERO
|
|
cfr->fr->seqno = 0;
|
|
#endif
|
|
|
|
if ( member->outVideoFrames == NULL )
|
|
{
|
|
// this is the first frame in the buffer
|
|
member->outVideoFramesTail = cfr ;
|
|
member->outVideoFrames = cfr ;
|
|
}
|
|
else
|
|
{
|
|
// put the new frame at the head of the list
|
|
member->outVideoFrames = cfr ;
|
|
}
|
|
|
|
// increment member frame count
|
|
member->outVideoFramesCount++ ;
|
|
|
|
ast_mutex_unlock(&member->lock);
|
|
|
|
// return success
|
|
return 0 ;
|
|
}
|
|
#endif
|
|
#ifdef DTMF
|
|
conf_frame* get_outgoing_dtmf_frame( struct ast_conf_member *member )
|
|
{
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to get frame from null member\n" ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
conf_frame* cfr ;
|
|
|
|
// ast_log( AST_CONF_DEBUG, "getting member frames, count => %d\n", member->outFramesCount ) ;
|
|
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
if ( member->outDTMFFramesCount > AST_CONF_MIN_QUEUE )
|
|
{
|
|
cfr = member->outDTMFFramesTail ;
|
|
|
|
// if it's the only frame, reset the queu,
|
|
// else, move the second frame to the front
|
|
if ( member->outDTMFFramesTail == member->outDTMFFrames )
|
|
{
|
|
member->outDTMFFrames = NULL ;
|
|
member->outDTMFFramesTail = NULL ;
|
|
}
|
|
else
|
|
{
|
|
// move the pointer to the next frame
|
|
member->outDTMFFramesTail = member->outDTMFFramesTail->prev ;
|
|
|
|
// reset it's 'next' pointer
|
|
if ( member->outDTMFFramesTail != NULL )
|
|
member->outDTMFFramesTail->next = NULL ;
|
|
}
|
|
|
|
// separate the conf frame from the list
|
|
cfr->next = NULL ;
|
|
cfr->prev = NULL ;
|
|
|
|
// decriment frame count
|
|
member->outDTMFFramesCount-- ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return cfr ;
|
|
}
|
|
ast_mutex_unlock(&member->lock);
|
|
return NULL ;
|
|
}
|
|
#endif
|
|
#ifdef TEXT
|
|
conf_frame* get_outgoing_text_frame( struct ast_conf_member *member )
|
|
{
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to get frame from null member\n" ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
conf_frame* cfr ;
|
|
|
|
// ast_log( AST_CONF_DEBUG, "getting member frames, count => %d\n", member->outFramesCount ) ;
|
|
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
if ( member->outTextFramesCount > AST_CONF_MIN_QUEUE )
|
|
{
|
|
cfr = member->outTextFramesTail ;
|
|
|
|
// if it's the only frame, reset the queu,
|
|
// else, move the second frame to the front
|
|
if ( member->outTextFramesTail == member->outTextFrames )
|
|
{
|
|
member->outTextFrames = NULL ;
|
|
member->outTextFramesTail = NULL ;
|
|
}
|
|
else
|
|
{
|
|
// move the pointer to the next frame
|
|
member->outTextFramesTail = member->outTextFramesTail->prev ;
|
|
|
|
// reset it's 'next' pointer
|
|
if ( member->outTextFramesTail != NULL )
|
|
member->outTextFramesTail->next = NULL ;
|
|
}
|
|
|
|
// separate the conf frame from the list
|
|
cfr->next = NULL ;
|
|
cfr->prev = NULL ;
|
|
|
|
// decriment frame count
|
|
member->outTextFramesCount-- ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return cfr ;
|
|
}
|
|
ast_mutex_unlock(&member->lock);
|
|
return NULL ;
|
|
}
|
|
#endif
|
|
#ifdef DTMF
|
|
int queue_outgoing_dtmf_frame( struct ast_conf_member* member, const struct ast_frame* fr )
|
|
{
|
|
// check on frame
|
|
if ( fr == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to queue null frame\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
// check on member
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to queue frame for null member\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
// accounting: count the number of outgoing frames for this member
|
|
member->dtmf_frames_out++ ;
|
|
|
|
//
|
|
// we have to drop frames, so we'll drop new frames
|
|
// because it's easier ( and doesn't matter much anyway ).
|
|
//
|
|
if ( member->outDTMFFramesCount >= AST_CONF_MAX_DTMF_QUEUE)
|
|
{
|
|
ast_log(
|
|
AST_CONF_DEBUG,
|
|
"unable to queue outgoing DTMF frame, channel => %s, incoming => %d, outgoing => %d\n",
|
|
member->channel_name, member->inDTMFFramesCount, member->outDTMFFramesCount
|
|
) ;
|
|
|
|
// accounting: count dropped outgoing frames
|
|
member->dtmf_frames_out_dropped++ ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return -1 ;
|
|
}
|
|
|
|
//
|
|
// create new conf frame from passed data frame
|
|
//
|
|
|
|
conf_frame* cfr = create_conf_frame( member, member->outDTMFFrames, fr ) ;
|
|
|
|
if ( cfr == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to create new conf frame\n" ) ;
|
|
|
|
// accounting: count dropped outgoing frames
|
|
member->dtmf_frames_out_dropped++ ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return -1 ;
|
|
}
|
|
|
|
#ifdef RTP_SEQNO_ZERO
|
|
cfr->fr->seqno = 0;
|
|
#endif
|
|
|
|
if ( member->outDTMFFrames == NULL )
|
|
{
|
|
// this is the first frame in the buffer
|
|
member->outDTMFFramesTail = cfr ;
|
|
member->outDTMFFrames = cfr ;
|
|
}
|
|
else
|
|
{
|
|
// put the new frame at the head of the list
|
|
member->outDTMFFrames = cfr ;
|
|
}
|
|
|
|
// increment member frame count
|
|
member->outDTMFFramesCount++ ;
|
|
|
|
ast_mutex_unlock(&member->lock);
|
|
// return success
|
|
return 0 ;
|
|
}
|
|
#endif
|
|
#ifdef TEXT
|
|
int queue_outgoing_text_frame( struct ast_conf_member* member, const struct ast_frame* fr)
|
|
{
|
|
// check on frame
|
|
if ( fr == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to queue null frame\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
// check on member
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to queue frame for null member\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
// accounting: count the number of outgoing frames for this member
|
|
member->text_frames_out++ ;
|
|
|
|
//
|
|
// we have to drop frames, so we'll drop new frames
|
|
// because it's easier ( and doesn't matter much anyway ).
|
|
//
|
|
if ( member->outTextFramesCount >= AST_CONF_MAX_TEXT_QUEUE)
|
|
{
|
|
ast_log(
|
|
AST_CONF_DEBUG,
|
|
"unable to queue outgoing text frame, channel => %s, incoming => %d, outgoing => %d\n",
|
|
member->channel_name, member->inTextFramesCount, member->outTextFramesCount
|
|
) ;
|
|
|
|
// accounting: count dropped outgoing frames
|
|
member->text_frames_out_dropped++ ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return -1 ;
|
|
}
|
|
|
|
//
|
|
// create new conf frame from passed data frame
|
|
//
|
|
|
|
conf_frame* cfr = create_conf_frame( member, member->outTextFrames, fr ) ;
|
|
|
|
if ( cfr == NULL )
|
|
{
|
|
ast_log( LOG_ERROR, "unable to create new conf frame\n" ) ;
|
|
|
|
// accounting: count dropped outgoing frames
|
|
member->text_frames_out_dropped++ ;
|
|
ast_mutex_unlock(&member->lock);
|
|
return -1 ;
|
|
}
|
|
|
|
#ifdef RTP_SEQNO_ZERO
|
|
cfr->fr->seqno = 0;
|
|
#endif
|
|
|
|
if ( member->outTextFrames == NULL )
|
|
{
|
|
// this is the first frame in the buffer
|
|
member->outTextFramesTail = cfr ;
|
|
member->outTextFrames = cfr ;
|
|
}
|
|
else
|
|
{
|
|
// put the new frame at the head of the list
|
|
member->outTextFrames = cfr ;
|
|
}
|
|
|
|
// increment member frame count
|
|
member->outTextFramesCount++ ;
|
|
|
|
ast_mutex_unlock(&member->lock);
|
|
// return success
|
|
return 0 ;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// manager functions
|
|
//
|
|
|
|
void send_state_change_notifications( struct ast_conf_member* member )
|
|
{
|
|
// ast_log( AST_CONF_DEBUG, "sending state change notification\n" ) ;
|
|
|
|
// loop through list of members, sending state changes
|
|
while ( member != NULL )
|
|
{
|
|
// has the state changed since last time through this loop?
|
|
if ( member->speaking_state_notify )
|
|
{
|
|
manager_event(
|
|
EVENT_FLAG_CALL,
|
|
"ConferenceState",
|
|
"ConferenceName: %s\r\n"
|
|
"Channel: %s\r\n"
|
|
"Flags: %s\r\n"
|
|
"State: %s\r\n",
|
|
member->conf_name,
|
|
member->channel_name,
|
|
member->flags,
|
|
( ( member->speaking_state == 1 ) ? "speaking" : "silent" )
|
|
) ;
|
|
|
|
ast_log( AST_CONF_DEBUG, "member state changed, channel => %s, state => %d, incoming => %d, outgoing => %d\n",
|
|
member->channel_name, member->speaking_state, member->inFramesCount, member->outFramesCount ) ;
|
|
|
|
member->speaking_state_notify = 0;
|
|
}
|
|
|
|
// move the pointer to the next member
|
|
member = member->next ;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
//
|
|
// ast_packer, adapted from ast_smoother
|
|
// pack multiple frames together into one packet on the wire.
|
|
//
|
|
|
|
#define PACKER_SIZE 8000
|
|
#define PACKER_QUEUE 10 // store at most 10 complete packets in the queue
|
|
|
|
struct ast_packer {
|
|
int framesize; // number of frames per packet on the wire.
|
|
int size;
|
|
int packet_index;
|
|
int format;
|
|
int readdata;
|
|
int optimizablestream;
|
|
int flags;
|
|
float samplesperbyte;
|
|
struct ast_frame f;
|
|
struct timeval delivery;
|
|
char data[PACKER_SIZE];
|
|
char framedata[PACKER_SIZE + AST_FRIENDLY_OFFSET];
|
|
int samples;
|
|
int sample_queue[PACKER_QUEUE];
|
|
int len_queue[PACKER_QUEUE];
|
|
struct ast_frame *opt;
|
|
int len;
|
|
};
|
|
|
|
void ast_packer_reset(struct ast_packer *s, int framesize)
|
|
{
|
|
memset(s, 0, sizeof(struct ast_packer));
|
|
s->framesize = framesize;
|
|
s->packet_index=0;
|
|
s->len=0;
|
|
}
|
|
|
|
struct ast_packer *ast_packer_new(int framesize)
|
|
{
|
|
struct ast_packer *s;
|
|
if (framesize < 1)
|
|
return NULL;
|
|
s = malloc(sizeof(struct ast_packer));
|
|
if (s)
|
|
ast_packer_reset(s, framesize);
|
|
return s;
|
|
}
|
|
|
|
int ast_packer_get_flags(struct ast_packer *s)
|
|
{
|
|
return s->flags;
|
|
}
|
|
|
|
void ast_packer_set_flags(struct ast_packer *s, int flags)
|
|
{
|
|
s->flags = flags;
|
|
}
|
|
|
|
int ast_packer_feed(struct ast_packer *s, const struct ast_frame *f)
|
|
{
|
|
if (f->frametype != AST_FRAME_VOICE) {
|
|
ast_log(LOG_WARNING, "Huh? Can't pack a non-voice frame!\n");
|
|
return -1;
|
|
}
|
|
if (!s->format) {
|
|
s->format = f->subclass;
|
|
s->samples=0;
|
|
} else if (s->format != f->subclass) {
|
|
ast_log(LOG_WARNING, "Packer was working on %d format frames, now trying to feed %d?\n", s->format, f->subclass);
|
|
return -1;
|
|
}
|
|
if (s->len + f->datalen > PACKER_SIZE) {
|
|
ast_log(LOG_WARNING, "Out of packer space\n");
|
|
return -1;
|
|
}
|
|
if (s->packet_index >= PACKER_QUEUE ){
|
|
ast_log(LOG_WARNING, "Out of packer queue space\n");
|
|
return -1;
|
|
}
|
|
|
|
memcpy(s->data + s->len, CASTDATA2PTR(f->data, void), f->datalen);
|
|
/* If either side is empty, reset the delivery time */
|
|
if (!s->len || (!f->delivery.tv_sec && !f->delivery.tv_usec) ||
|
|
(!s->delivery.tv_sec && !s->delivery.tv_usec))
|
|
s->delivery = f->delivery;
|
|
s->len += f->datalen;
|
|
//packer stuff
|
|
s->len_queue[s->packet_index] += f->datalen;
|
|
s->sample_queue[s->packet_index] += f->samples;
|
|
s->samples += f->samples;
|
|
|
|
if (s->samples > s->framesize )
|
|
++s->packet_index;
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct ast_frame *ast_packer_read(struct ast_packer *s)
|
|
{
|
|
struct ast_frame *opt;
|
|
int len;
|
|
/* IF we have an optimization frame, send it */
|
|
if (s->opt) {
|
|
opt = s->opt;
|
|
s->opt = NULL;
|
|
return opt;
|
|
}
|
|
|
|
/* Make sure we have enough data */
|
|
if (s->samples < s->framesize ){
|
|
return NULL;
|
|
}
|
|
len = s->len_queue[0];
|
|
if (len > s->len)
|
|
len = s->len;
|
|
/* Make frame */
|
|
s->f.frametype = AST_FRAME_VOICE;
|
|
s->f.subclass = s->format;
|
|
SETDATA2PTR(s->f.data, s->framedata + AST_FRIENDLY_OFFSET);
|
|
s->f.offset = AST_FRIENDLY_OFFSET;
|
|
s->f.datalen = len;
|
|
s->f.samples = s->sample_queue[0];
|
|
s->f.delivery = s->delivery;
|
|
/* Fill Data */
|
|
memcpy(CASTDATA2PTR(s->f.data, void), s->data, len);
|
|
s->len -= len;
|
|
/* Move remaining data to the front if applicable */
|
|
if (s->len) {
|
|
/* In principle this should all be fine because if we are sending
|
|
G.729 VAD, the next timestamp will take over anyawy */
|
|
memmove(s->data, s->data + len, s->len);
|
|
if (s->delivery.tv_sec || s->delivery.tv_usec) {
|
|
/* If we have delivery time, increment it, otherwise, leave it at 0 */
|
|
s->delivery.tv_sec += s->sample_queue[0] / 8000.0;
|
|
s->delivery.tv_usec += (((int)(s->sample_queue[0])) % 8000) * 125;
|
|
if (s->delivery.tv_usec > 1000000) {
|
|
s->delivery.tv_usec -= 1000000;
|
|
s->delivery.tv_sec += 1;
|
|
}
|
|
}
|
|
}
|
|
int j;
|
|
s->samples -= s->sample_queue[0];
|
|
if( s->packet_index > 0 ){
|
|
for (j=0; j<s->packet_index -1 ; j++){
|
|
s->len_queue[j]=s->len_queue[j+1];
|
|
s->sample_queue[j]=s->sample_queue[j+1];
|
|
}
|
|
s->len_queue[s->packet_index]=0;
|
|
s->sample_queue[s->packet_index]=0;
|
|
s->packet_index--;
|
|
} else {
|
|
s->len_queue[0]=0;
|
|
s->sample_queue[0]=0;
|
|
}
|
|
|
|
|
|
/* Return frame */
|
|
return &s->f;
|
|
}
|
|
|
|
void ast_packer_free(struct ast_packer *s)
|
|
{
|
|
free(s);
|
|
}
|
|
|
|
int queue_frame_for_listener(
|
|
struct ast_conference* conf,
|
|
struct ast_conf_member* member,
|
|
conf_frame* frame
|
|
)
|
|
{
|
|
//
|
|
// check inputs
|
|
//
|
|
|
|
if ( conf == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to queue listener frame with null conference\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to queue listener frame with null member\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
//
|
|
// loop over spoken frames looking for member's appropriate match
|
|
//
|
|
|
|
short found_flag = 0 ;
|
|
struct ast_frame* qf ;
|
|
|
|
for ( ; frame != NULL ; frame = frame->next )
|
|
{
|
|
// we're looking for a null or matching member
|
|
if ( frame->member != NULL && frame->member != member )
|
|
continue ;
|
|
|
|
if ( frame->fr == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unknown error queueing frame for listener, frame->fr == NULL\n" ) ;
|
|
continue ;
|
|
}
|
|
|
|
// first, try for a pre-converted frame
|
|
qf = (member->listen_volume == 0 ? frame->converted[ member->write_format_index ] : 0);
|
|
|
|
// convert ( and store ) the frame
|
|
if ( qf == NULL )
|
|
{
|
|
// make a copy of the slinear version of the frame
|
|
qf = ast_frdup( frame->fr ) ;
|
|
|
|
if (member->listen_volume != 0)
|
|
{
|
|
ast_frame_adjust_volume(qf, member->listen_volume);
|
|
}
|
|
|
|
if ( qf == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to duplicate frame\n" ) ;
|
|
continue ;
|
|
}
|
|
|
|
// convert using the conference's translation path
|
|
qf = convert_frame_from_slinear( conf->from_slinear_paths[ member->write_format_index ], qf ) ;
|
|
|
|
// store the converted frame
|
|
// ( the frame will be free'd next time through the loop )
|
|
if (member->listen_volume == 0)
|
|
{
|
|
frame->converted[ member->write_format_index ] = qf ;
|
|
}
|
|
}
|
|
|
|
if ( qf != NULL )
|
|
{
|
|
// duplicate the frame before queue'ing it
|
|
// ( since this member doesn't own this _shared_ frame )
|
|
// qf = ast_frdup( qf ) ;
|
|
|
|
|
|
|
|
if ( queue_outgoing_frame( member, qf, conf->delivery_time ) != 0 )
|
|
{
|
|
// free the new frame if it couldn't be queue'd
|
|
// XXX NEILS - WOULD BE FREED IN CLEANUPast_frfree( qf ) ;
|
|
//qf = NULL ;
|
|
}
|
|
|
|
if (member->listen_volume != 0)
|
|
{
|
|
// free frame ( the translator's copy )
|
|
ast_frfree( qf ) ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ast_log( LOG_WARNING, "unable to translate outgoing listener frame, channel => %s\n", member->channel_name ) ;
|
|
}
|
|
|
|
// set found flag
|
|
found_flag = 1 ;
|
|
|
|
// break from for loop
|
|
break ;
|
|
}
|
|
|
|
// queue a silent frame
|
|
if ( found_flag == 0 )
|
|
queue_silent_frame( conf, member ) ;
|
|
|
|
return 0 ;
|
|
}
|
|
|
|
|
|
int queue_frame_for_speaker(
|
|
struct ast_conference* conf,
|
|
struct ast_conf_member* member,
|
|
conf_frame* frame
|
|
)
|
|
{
|
|
//
|
|
// check inputs
|
|
//
|
|
|
|
if ( conf == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to queue speaker frame with null conference\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to queue speaker frame with null member\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
//
|
|
// loop over spoken frames looking for member's appropriate match
|
|
//
|
|
|
|
short found_flag = 0 ;
|
|
struct ast_frame* qf ;
|
|
|
|
for ( ; frame != NULL ; frame = frame->next )
|
|
{
|
|
if ( frame->member != member )
|
|
{
|
|
continue ;
|
|
}
|
|
|
|
if ( frame->fr == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to queue speaker frame with null data\n" ) ;
|
|
continue ;
|
|
}
|
|
|
|
//
|
|
// convert and queue frame
|
|
//
|
|
|
|
// short-cut pointer to the ast_frame
|
|
qf = frame->fr ;
|
|
|
|
if ( (qf->subclass == member->write_format) && (member->listen_volume == 0) )
|
|
{
|
|
// frame is already in correct format, so just queue it
|
|
|
|
queue_outgoing_frame( member, qf, conf->delivery_time ) ;
|
|
}
|
|
else
|
|
{
|
|
// make a copy of the slinear version of the frame
|
|
qf = ast_frdup( qf ) ;
|
|
|
|
if (member->listen_volume != 0)
|
|
{
|
|
ast_frame_adjust_volume(frame->fr, member->listen_volume);
|
|
}
|
|
|
|
//
|
|
// convert frame to member's write format
|
|
// ( calling ast_frdup() to make sure the translator's copy sticks around )
|
|
//
|
|
qf = convert_frame_from_slinear( member->from_slinear, qf ) ;
|
|
|
|
if ( qf != NULL )
|
|
{
|
|
// queue frame
|
|
queue_outgoing_frame( member, qf, conf->delivery_time ) ;
|
|
|
|
// free frame ( the translator's copy )
|
|
ast_frfree( qf ) ;
|
|
}
|
|
else
|
|
{
|
|
ast_log( LOG_WARNING, "unable to translate outgoing speaker frame, channel => %s\n", member->channel_name ) ;
|
|
}
|
|
}
|
|
|
|
// set found flag
|
|
found_flag = 1 ;
|
|
|
|
// we found the frame, skip to the next member
|
|
break ;
|
|
}
|
|
|
|
// queue a silent frame
|
|
if ( found_flag == 0 )
|
|
queue_silent_frame( conf, member ) ;
|
|
|
|
return 0 ;
|
|
}
|
|
|
|
|
|
int queue_silent_frame(
|
|
struct ast_conference* conf,
|
|
struct ast_conf_member* member
|
|
)
|
|
{
|
|
int c;
|
|
#ifdef APP_KONFERENCE_DEBUG
|
|
//
|
|
// check inputs
|
|
//
|
|
|
|
if ( conf == NULL )
|
|
{
|
|
ast_log( AST_CONF_DEBUG, "unable to queue silent frame for null conference\n" ) ;
|
|
return -1 ;
|
|
}
|
|
|
|
if ( member == NULL )
|
|
{
|
|
ast_log( AST_CONF_DEBUG, "unable to queue silent frame for null member\n" ) ;
|
|
return -1 ;
|
|
}
|
|
#endif // APP_KONFERENCE_DEBUG
|
|
|
|
//
|
|
// initialize static variables
|
|
//
|
|
|
|
static conf_frame* silent_frame = NULL ;
|
|
static struct ast_frame* qf = NULL ;
|
|
|
|
if ( silent_frame == NULL )
|
|
{
|
|
if ( ( silent_frame = get_silent_frame() ) == NULL )
|
|
{
|
|
ast_log( LOG_WARNING, "unable to initialize static silent frame\n" ) ;
|
|
return -1 ;
|
|
}
|
|
}
|
|
|
|
|
|
// get the appropriate silent frame
|
|
qf = silent_frame->converted[ member->write_format_index ] ;
|
|
|
|
if ( qf == NULL )
|
|
{
|
|
//
|
|
// we need to do this to avoid echo on the speaker's line.
|
|
// translators seem to be single-purpose, i.e. they
|
|
// can't be used simultaneously for multiple audio streams
|
|
//
|
|
|
|
struct ast_trans_pvt* trans = ast_translator_build_path( member->write_format, AST_FORMAT_SLINEAR ) ;
|
|
|
|
if ( trans != NULL )
|
|
{
|
|
// attempt ( five times ) to get a silent frame
|
|
// to make sure we provice the translator with enough data
|
|
for ( c = 0 ; c < 5 ; ++c )
|
|
{
|
|
// translate the frame
|
|
qf = ast_translate( trans, silent_frame->fr, 0 ) ;
|
|
|
|
// break if we get a frame
|
|
if ( qf != NULL ) break ;
|
|
}
|
|
|
|
if ( qf != NULL )
|
|
{
|
|
// isolate the frame so we can keep it around after trans is free'd
|
|
qf = ast_frisolate( qf ) ;
|
|
|
|
// cache the new, isolated frame
|
|
silent_frame->converted[ member->write_format_index ] = qf ;
|
|
}
|
|
|
|
ast_translator_free_path( trans ) ;
|
|
}
|
|
}
|
|
|
|
//
|
|
// queue the frame, if it's not null,
|
|
// otherwise there was an error
|
|
//
|
|
if ( qf != NULL )
|
|
{
|
|
queue_outgoing_frame( member, qf, conf->delivery_time ) ;
|
|
}
|
|
else
|
|
{
|
|
ast_log( LOG_ERROR, "unable to translate outgoing silent frame, channel => %s\n", member->channel_name ) ;
|
|
}
|
|
|
|
return 0 ;
|
|
}
|
|
|
|
|
|
|
|
void member_process_outgoing_frames(struct ast_conference* conf,
|
|
struct ast_conf_member *member,
|
|
struct conf_frame *send_frames)
|
|
{
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
// skip members that are not ready
|
|
if ( member->ready_for_outgoing == 0 )
|
|
{
|
|
ast_mutex_unlock(&member->lock);
|
|
return ;
|
|
}
|
|
|
|
// skip no receive audio clients
|
|
if ( member->norecv_audio )
|
|
{
|
|
ast_mutex_unlock(&member->lock);
|
|
return;
|
|
}
|
|
|
|
if ( member->local_speaking_state == 0 )
|
|
{
|
|
// queue listener frame
|
|
queue_frame_for_listener( conf, member, send_frames ) ;
|
|
}
|
|
else
|
|
{
|
|
// queue speaker frame
|
|
queue_frame_for_speaker( conf, member, send_frames ) ;
|
|
}
|
|
ast_mutex_unlock(&member->lock);
|
|
}
|
|
|
|
// Functions that will increase and decrease speaker_count in a secure way, locking the member mutex if required
|
|
// Will also set speaking_state flag.
|
|
// Returns the previous speaking state
|
|
int increment_speaker_count(struct ast_conf_member *member, int lock)
|
|
{
|
|
int old_state;
|
|
|
|
if ( lock )
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
old_state = member->speaking_state;
|
|
member->speaker_count++;
|
|
member->speaking_state = 1;
|
|
|
|
ast_log(AST_CONF_DEBUG, "Increment speaker count: id=%d, count=%d\n", member->id, member->speaker_count);
|
|
|
|
// If this is a state change, update the timestamp
|
|
if ( old_state == 0 )
|
|
{
|
|
member->speaking_state_notify = 1;
|
|
member->last_state_change = ast_tvnow();
|
|
}
|
|
|
|
if ( lock )
|
|
ast_mutex_unlock(&member->lock);
|
|
|
|
return old_state;
|
|
}
|
|
|
|
int decrement_speaker_count(struct ast_conf_member *member, int lock)
|
|
{
|
|
int old_state;
|
|
|
|
if ( lock )
|
|
ast_mutex_lock(&member->lock);
|
|
|
|
old_state = member->speaking_state;
|
|
if ( member->speaker_count > 0 )
|
|
member->speaker_count--;
|
|
if ( member->speaker_count == 0 )
|
|
member->speaking_state = 0;
|
|
|
|
ast_log(AST_CONF_DEBUG, "Decrement speaker count: id=%d, count=%d\n", member->id, member->speaker_count);
|
|
|
|
// If this is a state change, update the timestamp
|
|
if ( old_state == 1 && member->speaking_state == 0 )
|
|
{
|
|
member->speaking_state_notify = 1;
|
|
member->last_state_change = ast_tvnow();
|
|
}
|
|
|
|
if ( lock )
|
|
ast_mutex_unlock(&member->lock);
|
|
|
|
return old_state;
|
|
}
|
|
|
|
void member_process_spoken_frames(struct ast_conference* conf,
|
|
struct ast_conf_member *member,
|
|
struct conf_frame **spoken_frames,
|
|
long time_diff,
|
|
int *listener_count,
|
|
int *speaker_count
|
|
)
|
|
{
|
|
struct conf_frame *cfr;
|
|
|
|
// acquire member mutex
|
|
TIMELOG(ast_mutex_lock( &member->lock ),1,"conf thread member lock") ;
|
|
|
|
// tell member the number of frames we're going to need ( used to help dropping algorithm )
|
|
member->inFramesNeeded = ( time_diff / AST_CONF_FRAME_INTERVAL ) - 1 ;
|
|
|
|
// !!! TESTING !!!
|
|
if (
|
|
conf->debug_flag == 1
|
|
&& member->inFramesNeeded > 0
|
|
)
|
|
{
|
|
ast_log( AST_CONF_DEBUG, "channel => %s, inFramesNeeded => %d, inFramesCount => %d\n",
|
|
member->channel_name, member->inFramesNeeded, member->inFramesCount ) ;
|
|
}
|
|
|
|
// non-listener member should have frames,
|
|
// unless silence detection dropped them
|
|
cfr = get_incoming_frame( member ) ;
|
|
|
|
// handle retrieved frames
|
|
if ( cfr == NULL || cfr->fr == NULL )
|
|
{
|
|
// Decrement speaker count for us and for driven members
|
|
// This happens only for the first missed frame, since we want to
|
|
// decrement only on state transitions
|
|
if ( member->local_speaking_state == 1 )
|
|
{
|
|
decrement_speaker_count(member, 0);
|
|
member->local_speaking_state = 0;
|
|
// If we're driving another member, decrement its speaker count as well
|
|
#ifdef VIDEO
|
|
if ( member->driven_member != NULL )
|
|
decrement_speaker_count(member->driven_member, 1);
|
|
#endif
|
|
}
|
|
|
|
// count the listeners
|
|
(*listener_count)++ ;
|
|
}
|
|
else
|
|
{
|
|
// append the frame to the list of spoken frames
|
|
if ( *spoken_frames != NULL )
|
|
{
|
|
// add new frame to end of list
|
|
cfr->next = *spoken_frames ;
|
|
(*spoken_frames)->prev = cfr ;
|
|
}
|
|
|
|
// point the list at the new frame
|
|
*spoken_frames = cfr ;
|
|
|
|
// Increment speaker count for us and for driven members
|
|
// This happens only on the first received frame, since we want to
|
|
// increment only on state transitions
|
|
if ( member->local_speaking_state == 0 )
|
|
{
|
|
increment_speaker_count(member, 0);
|
|
member->local_speaking_state = 1;
|
|
#ifdef VIDEO
|
|
// If we're driving another member, increment its speaker count as well
|
|
if ( member->driven_member != NULL )
|
|
increment_speaker_count(member->driven_member, 1);
|
|
#endif
|
|
}
|
|
|
|
// count the speakers
|
|
(*speaker_count)++ ;
|
|
}
|
|
|
|
// release member mutex
|
|
ast_mutex_unlock( &member->lock ) ;
|
|
|
|
return;
|
|
}
|
|
#ifdef VIDEO
|
|
int is_video_eligible(struct ast_conf_member *member)
|
|
{
|
|
if ( member == NULL )
|
|
return 0;
|
|
|
|
return !member->no_camera && !member->mute_video && !member->via_telephone;
|
|
}
|
|
|
|
// Member start and stop video methods
|
|
void start_video(struct ast_conf_member *member)
|
|
{
|
|
if ( member == NULL || member->video_started || !is_video_eligible(member))
|
|
return;
|
|
#ifdef TEXT
|
|
send_text_message_to_member(member, AST_CONF_CONTROL_START_VIDEO);
|
|
#endif
|
|
member->video_started = 1;
|
|
}
|
|
|
|
void stop_video(struct ast_conf_member *member)
|
|
{
|
|
if ( member == NULL || !member->video_started )
|
|
return;
|
|
#ifdef TEXT
|
|
send_text_message_to_member(member, AST_CONF_CONTROL_STOP_VIDEO);
|
|
#endif
|
|
member->video_started = 0;
|
|
|
|
}
|
|
#endif
|