Files
mattf e5caeca898 cleanup of extras
git-svn-id: svn://192.168.202.10@17 3d104415-ff17-0410-8863-d5cf3c621b8a
2006-07-07 15:44:57 +00:00

1947 lines
48 KiB
Plaintext

// $Id: member.c,v 1.9-1 2006/06/01 16:10:10 mflorell Exp $
/*
* app_conference
*
* A channel independent conference application for Asterisk
*
* Copyright (C) 2002, 2003 Junghanns.NET GmbH
* Copyright (C) 2003, 2004 HorizonLive.com, Inc.
*
* Klaus-Peter Junghanns <kapejod@ns1.jnetdns.de>
*
* This program may be modified and distributed under the
* terms of the GNU Public License.
*
*/
#include "member.h"
#include "regex.h"
// process an incoming frame. Returns 0 normally, 1 if hangup was received.
static int process_incoming(struct ast_conference *conf, struct ast_conf_member *member, struct ast_frame *f)
{
int silent_frame = 0;
if (
f->frametype == AST_FRAME_DTMF
&& member->send_dtmf
)
{ // send the DTMF event to the MGR interface..
manager_event(
EVENT_FLAG_CALL,
"ConferenceDTMF",
"Channel: %s\r\n"
"Key: %c\r\n",
member->channel_name,
f->subclass
) ;
ast_frfree(f);
f = NULL;
}
else if ( member->type == 'L' )
{
// this is a listen-only user, ignore the frame
ast_frfree( f ) ;
f = NULL ;
}
else if ( ( f->frametype == AST_FRAME_VOICE ) || (f->frametype == AST_FRAME_DTMF) )
// else if ( f->frametype == AST_FRAME_VOICE )
{
// accounting: count the incoming frame
member->frames_in++ ;
if (f->frametype == AST_FRAME_DTMF)
{
if ( (member->inband_dtmf == -1) || (member->rfc_dtmf == -1) )
{
char *digit = "X" ;
char *dtmfRFC = "X" ;
int conf_count ;
struct ast_conf_member *membertest;
if (f->subclass == 35) { digit = "hash" ; dtmfRFC = "#" ; }
if (f->subclass == 42) { digit = "star" ; dtmfRFC = "*" ; }
if (f->subclass == 48) { digit = "0" ; dtmfRFC = "0" ; }
if (f->subclass == 49) { digit = "1" ; dtmfRFC = "1" ; }
if (f->subclass == 50) { digit = "2" ; dtmfRFC = "2" ; }
if (f->subclass == 51) { digit = "3" ; dtmfRFC = "3" ; }
if (f->subclass == 52) { digit = "4" ; dtmfRFC = "4" ; }
if (f->subclass == 53) { digit = "5" ; dtmfRFC = "5" ; }
if (f->subclass == 54) { digit = "6" ; dtmfRFC = "6" ; }
if (f->subclass == 55) { digit = "7" ; dtmfRFC = "7" ; }
if (f->subclass == 56) { digit = "8" ; dtmfRFC = "8" ; }
if (f->subclass == 57) { digit = "9" ; dtmfRFC = "9" ; }
ast_log( LOG_NOTICE, "DTMF debug frame output to voice %d - %s\n", f->subclass, digit ) ;
int res = 0 ;
int dtmf_invalid = 0 ;
regex_t re;
if(regcomp(&re, digit, REG_EXTENDED|REG_NOSUB) != 0) { dtmf_invalid = 0; }
dtmf_invalid = regexec(&re, "0123456789*#abcdABCDstarhash", (size_t)0, NULL, 0);
regfree(&re);
ast_log( LOG_NOTICE, "Regex debug %d - %s - %s\n", dtmf_invalid, member->channel_name, dtmfRFC) ;
// play DTMF for other members of the conference
membertest = conf->memberlist ;
conf_count = conf->membercount ;
// ast_log( LOG_NOTICE, "2 DTMF debug frame output to voice, conf count %d\n", conf_count) ;
while (membertest != NULL)
{
if( (!strcmp(membertest->channel_name, member->channel_name)) || ( digit == "X" ) )
{
ast_log( LOG_ERROR, "skipping playing DTMF tone on %s\n", membertest->channel_name ) ;
}
else
{
if (member->inband_dtmf == -1)
{
if (!basic_play_sound ( membertest->channel_name, digit ))
{
ast_log( LOG_ERROR, "playing DTMF tone FAILED on %s\n", membertest->channel_name ) ;
}
}
if (member->rfc_dtmf == -1)
{
ast_log( LOG_NOTICE, "START sending DTMF signal on %s\n", membertest->channel_name ) ;
if(dtmf_invalid > 0)
{
ast_log(LOG_WARNING, "Illegal DTMF character '%s' in string. (0-9*#aAbBcCdD allowed)\n", digit);
}
else
{
res = ast_dtmf_stream(membertest->chan,NULL,digit,250);
if (res)
{
ast_log( LOG_NOTICE, "DTMF signal %s on %s sent\n", digit, membertest->channel_name ) ;
}
}
}
}
membertest = membertest->next;
}
}
}
#ifdef DEBUG_OUTPUT_PCM
// !!! TESTING !!!
if ( member->incoming_fh != NULL )
{
fwrite( f->data, f->datalen, 1, member->incoming_fh ) ;
fflush( member->incoming_fh ) ;
}
#endif
#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
#ifdef DEBUG_USE_TIMELOG
int spx_ret;
TIMELOG(spx_ret = speex_preprocess( member->dsp, f->data, NULL ), 3, "speex_preprocess");
if ( spx_ret == 0 )
#else
if ( speex_preprocess( member->dsp, f->data, NULL ) == 0 )
#endif
{
//
// 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", member->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
// used to debug drop off in received frames
// struct timeval tv ;
// gettimeofday( &tv, NULL ) ;
if ( silent_frame == 1 )
{
// ignore silent frames
// ast_log( AST_CONF_DEBUG, "RECEIVED SILENT FRAME, channel => %s, frames_in => %ld, s => %ld, ms => %ld\n",
// member->channel_name, member->frames_in, tv.tv_sec, tv.tv_usec ) ;
}
else
{
// queue a non-silent frame for mixing
// ast_log( AST_CONF_DEBUG, "RECEIVED VOICE FRAME, channel => %s, frames_in => %ld, s => %ld, ms => %ld\n",
// member->channel_name, member->frames_in, tv.tv_sec, tv.tv_usec ) ;
// acquire member lock
ast_mutex_lock( &member->lock ) ;
//
// queue up the voice frame so the conference
// thread can mix them before sending
//
// struct ast_frame* af = ast_frdup( f ) ;
// if ( queue_incoming_frame( member, af ) != 0 )
if ( queue_incoming_frame( member, f ) != 0 )
{
// free the duplicated frame, if we can't queue it
// ast_log( LOG_NOTICE, "dropped incoming frame, channel => %s\n", chan->name ) ;
// ast_frfree( af ) ;
// af = NULL ;
}
else
{
// everything is going ok
}
// release member mutex
ast_mutex_unlock( &member->lock ) ;
}
// free the original frame
ast_frfree( f ) ;
f = NULL ;
}
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 ;
}
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:
f=ast_readframe(member->soundq->stream);
if(!f) { // we're done with this sound; remove it from the queue, and try again
struct ast_conf_soundq *toboot = member->soundq;
ast_closestream(toboot->stream);
member->soundq = toboot->next;
//ast_log( LOG_WARNING, "finished playing a sound, next = %x\n", member->soundq);
// notify applications via mgr interface that this sound has been played
manager_event(
EVENT_FLAG_CALL,
"ConferenceSoundComplete",
"Channel: %s\r\n"
"Sound: %s\r\n",
member->channel_name,
toboot->name
);
free(toboot);
if(member->soundq) goto again;
// 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;
struct ast_frame *realframe = NULL;
for(;;)
{
// acquire member mutex and grab a frame.
ast_mutex_lock( &member->lock ) ;
cf = get_outgoing_frame( member ) ;
// if there's no frames exit the loop.
if(!cf){
ast_mutex_unlock( &member->lock ) ;
break;
}
f = cf->fr;
// if we're playing sounds, we can just replace the frame with the
// next sound frame, and send it instead
if(member->soundq) {
realframe = f;
f = get_next_soundframe(member, f);
if(!f) { // if we didn't get anything, just revert to "normal"
f = realframe;
realframe = NULL;
}
}
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 ) &&
( delivery_diff != 2 * AST_CONF_FRAME_INTERVAL ) &&
( delivery_diff != 3 * 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 ;
// gettimeofday( &tv, NULL ) ;
// 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
{
// 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 ) ;
}
return 0;
}
//
// main member thread function
//
int member_exec( struct ast_channel* chan, void* data )
{
// struct timeval start, end ;
// gettimeofday( &start, NULL ) ;
struct ast_conference *conf ;
struct ast_conf_member *member, *membertest;
struct ast_frame *f ; // frame received from ast_read()
int left = 0 ;
int res;
ast_log( AST_CONF_DEBUG, "[ $Revision: 1.9 $ ] 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
//
ast_log( AST_CONF_DEBUG, "CHANNEL INFO, CHANNEL => %s, DNID => %s, CALLER_ID => %s, ANI => %s\n",
chan->name, chan->cid.cid_dnid, chan->cid.cid_num, chan->cid.cid_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 ) ;
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 ;
}
// for right now, we'll send everything as slinear
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 = start_conference( member ) ;
if ( conf == NULL )
{
ast_log( LOG_ERROR, "unable to setup member conference\n" ) ;
delete_member( member) ;
return -1 ;
}
#ifdef DEBUG_OUTPUT_PCM
// !!! TESTING !!!
char* incoming_fn = malloc( 512 ) ;
snprintf( incoming_fn, 512, "/tmp/ac_%s_%ld.gsm", chan->dnid, fr_base.tv_usec ) ;
// !!! TESTING !!!
member->incoming_fh = ( member->read_format == 2 )
? fopen( member->incoming_fn, "wb" )
: NULL
;
// !!! TESTING !!!
if ( member->incoming_fh == NULL )
{
ast_log( AST_CONF_DEBUG, "incoming_fh is null, incoming_fn => %s\n", incoming_fn ) ;
}
else
{
ast_log( AST_CONF_DEBUG, "incoming_fh is not null, incoming_fn => %s\n", incoming_fn ) ;
}
// !!! TESTING !!!
free( incoming_fn ) ;
#endif
//
// 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 ;
gettimeofday( &base, NULL ) ;
// tell conference_exec we're ready for frames
member->ready_for_outgoing = 1 ;
int conf_count = 0 ;
int silent_entry = 0 ;
// entry message for other members of the conference
membertest = conf->memberlist ;
conf_count = conf->membercount ;
ast_log( LOG_NOTICE, "Conference Members: %d\n", conf_count ) ;
if (member->quiet_entry_exit == -1)
{
silent_entry = 1 ;
}
ast_log( LOG_NOTICE, "Quiet debug %d - %d\n", member->quiet_entry_exit, silent_entry ) ;
while (membertest != NULL)
{
if (silent_entry < 1)
{
if( (!strcmp(membertest->channel_name, member->channel_name)) && (conf_count < 2) )
{
ast_log( LOG_NOTICE, "skipping entry message on %s\n", membertest->channel_name ) ;
if (!basic_play_sound ( member->channel_name, "conf-onlyperson" ))
{
ast_log( LOG_ERROR, "playing conference welcome message FAILED\n" ) ;
}
}
else
{
if (!basic_play_sound ( membertest->channel_name, "enter" ))
{
ast_log( LOG_ERROR, "playing conference entry message FAILED on %s\n", membertest->channel_name ) ;
}
}
}
else
{
ast_log( LOG_NOTICE, "skipping all entry messages on %s\n", membertest->channel_name ) ;
}
membertest = membertest->next;
}
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, rest => %d\n", chan->name, rest ) ;
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
) ;
}
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 )
{
ast_log( LOG_NOTICE, "unable to read from channel, channel => %s\n", chan->name ) ;
break ;
}
// actually process the frame: break if we got hangup.
if(process_incoming(conf, member, f)) break;
}
//-----------------//
// OUTGOING FRAMES //
//-----------------//
// update the current timestamps
gettimeofday( &curr, NULL ) ;
process_outgoing(member);
// back to process incoming frames
continue ;
}
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 ( member->incoming_fh != NULL )
fclose( member->incoming_fh ) ;
#endif
if ( member != NULL ) member->remove_flag = 1 ;
// gettimeofday( &end, NULL ) ;
// int expected_frames = ( int )( floor( (double)( usecdiff( &end, &start ) / AST_CONF_FRAME_INTERVAL ) ) ) ;
// ast_log( AST_CONF_DEBUG, "expected_frames => %d\n", expected_frames ) ;
return -1 ;
}
// basic sound playing function
int basic_play_sound ( char *channel, char *file )
{
// char *channel, *file;
struct ast_conf_member *play_member;
struct ast_conf_soundq *newsound;
struct ast_conf_soundq **q;
play_member = find_member(channel, 1);
if(!play_member) {
ast_log( LOG_ERROR, "Member %s not found\n", channel) ;
return 0;
}
newsound = calloc(1,sizeof(struct ast_conf_soundq));
newsound->stream = ast_openstream(play_member->chan, file, NULL);
if(!newsound->stream) {
free(newsound);
ast_mutex_unlock(&play_member->lock);
ast_log( LOG_ERROR, "sound file not found\n" ) ;
}
play_member->chan->stream = NULL;
ast_copy_string(newsound->name, file, sizeof(newsound->name));
// append sound to the end of the list.
for(q=&play_member->soundq; *q; q = &((*q)->next)) ;;
*q = newsound;
ast_mutex_unlock(&play_member->lock);
ast_log( LOG_NOTICE, "playing conference message %s\n", file ) ;
return 1;
}
//
// 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 member with passed data values
//
char argstr[80] ;
char *stringp, *token ;
// copy the passed data
strncpy( argstr, data, sizeof(argstr) - 1 ) ;
// point to the copied data
stringp = argstr ;
ast_log( AST_CONF_DEBUG, "attempting to parse passed params, stringp => %s\n", stringp ) ;
// parse the id
if ( ( token = strsep( &stringp, "|" ) ) != NULL )
{
member->id = malloc( strlen( token ) + 1 ) ;
strcpy( member->id, 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 ) ) ;
}
// parse the priority
member->priority = ( token = strsep( &stringp, "|" ) ) != NULL
? atoi( token )
: 0
;
// parse the vad_prob_start
member->vad_prob_start = ( token = strsep( &stringp, "|" ) ) != NULL
? atof( token )
: AST_CONF_PROB_START
;
// parse the vad_prob_continue
member->vad_prob_continue = ( token = strsep( &stringp, "|" ) ) != NULL
? atof( token )
: AST_CONF_PROB_CONTINUE
;
// parse the expected frame size, in samples. ??
// debugging
ast_log(
AST_CONF_DEBUG,
"parsed data params, id => %s, flags => %s, priority => %d, vad_prob_start => %f, vad_prob_continue => %f\n",
member->id, member->flags, member->priority, member->vad_prob_start, member->vad_prob_continue
) ;
//
// 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 ) ;
// ( default can be overridden by passed flags )
// member->type = 'L' ;
member->type = 'S' ;
// ready flag
member->ready_for_outgoing = 0 ;
// incoming frame queue
member->inFrames = NULL ;
member->inFramesTail = NULL ;
member->inFramesCount = 0 ;
// last frame caching
member->inFramesRepeatLast = 0 ;
member->inFramesLast = NULL ;
member->okayToCacheLast = 0 ;
// outgoing frame queue
member->outFrames = NULL ;
member->outFramesTail = NULL ;
member->outFramesCount = 0 ;
member->outPacker = NULL;
// ( not currently used )
// member->samplesperframe = AST_CONF_BLOCK_SAMPLES ;
// used for determining need to mix frames
// and for management interface notification
member->speaking_state_prev = 0 ;
member->speaking_state_notify = 0 ;
member->speaking_state = 0 ;
// linked-list pointer
member->next = NULL ;
// account data
member->frames_in = 0 ;
member->frames_in_dropped = 0 ;
member->frames_out = 0 ;
member->frames_out_dropped = 0 ;
// for counting sequentially dropped frames
member->sequential_drops = 0 ;
member->since_dropped = 0 ;
// flags
member->remove_flag = 0 ;
// record start time
gettimeofday( &member->time_entered, NULL ) ;
// init dropped frame timestamps
gettimeofday( &member->last_in_dropped, NULL ) ;
gettimeofday( &member->last_out_dropped, NULL ) ;
//
// parse passed flags
//
// silence detection flags w/ defaults
int vad_flag = 0 ;
int denoise_flag = 0 ;
int agc_flag = 0 ;
// is this member using the telephone?
int via_telephone = 0 ;
// temp pointer to flags string
// char* flags = member->flags ;
char* flags = member->flags ;
int i;
for ( i = 0 ; i < strlen( flags ) ; ++i )
{
// allowed flags are M, L, S, V, D, A
switch ( flags[i] )
{
// call via telephone
case 'T':
via_telephone = 1 ;
break ;
// member types ( last flag wins )
case 'M':
member->type = 'M' ;
break ;
case 'L':
member->type = 'L' ;
break ;
case 'S':
member->type = 'S' ;
break ;
// speex preprocessing options
case 'V':
vad_flag = 1 ;
break ;
case 'D':
denoise_flag = 1 ;
break ;
case 'A':
agc_flag = 1 ;
break ;
// additional features
case 'd': // Send DTMF manager events..
member->send_dtmf = 1;
break;
case 'q': // Quiet entry and exit from conference
member->quiet_entry_exit = 1;
break;
case 'i': // use Inband DTMF broadcast
member->inband_dtmf = 1;
break;
case 't': // use RFC DTMF broadcast
member->rfc_dtmf = 1;
break;
default:
ast_log( LOG_WARNING, "received invalid flag, chan => %s, flag => %c\n",
chan->name, flags[i] ) ;
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 (
via_telephone == 1
&& member->type != 'L'
)
{
// 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( LOG_NOTICE, "member dsp initialized, channel => %s, v => %d, d => %d, a => %d\n",
chan->name, vad_flag, denoise_flag, agc_flag ) ;
// set speex preprocessor options
speex_preprocess_ctl( member->dsp, SPEEX_PREPROCESS_SET_VAD, &vad_flag ) ;
speex_preprocess_ctl( member->dsp, SPEEX_PREPROCESS_SET_DENOISE, &denoise_flag ) ;
speex_preprocess_ctl( member->dsp, SPEEX_PREPROCESS_SET_AGC, &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
//
// set connection type
//
if ( via_telephone == 1 )
{
member->connection_type = 'T' ;
}
else if ( strncmp( member->channel_name, "SIP", 3 ) == 0 )
{
member->connection_type = 'S' ;
}
else // default to iax
{
member->connection_type = 'X' ;
}
//
// 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 ;
// 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
{
int format, index;
index=-1;
format = member->write_format;
while( format > 0){
index++;
format = format >> 1;
}
member->write_format_index= index;
//ast_log( AST_CONF_DEBUG, "converted write_format [%d] to index[%d]\n", member->write_format, index );
index=-1;
format = member->read_format;
while( format > 0){
index++;
format = format >> 1;
}
member->read_format_index=index;
//ast_log( AST_CONF_DEBUG, "converted read_format [%d] to index[%d]\n", member->read_format, index );
}
// 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:
/* 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 => %c, 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 ;
}
//
// 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);
// !!! 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 ) ;
}
if (member->outPacker != NULL)
ast_packer_free(member->outPacker);
#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 ) ;
// !!! DEBUGING !!!
ast_log( AST_CONF_DEBUG, "freeing member\n" ) ;
// free the member's memory
free( member ) ;
member = NULL ;
return nm ;
}
//
// incoming frame functions
//
conf_frame* get_incoming_frame( struct ast_conf_member *member )
{
//
// sanity checks
//
if ( member == NULL )
{
ast_log( LOG_WARNING, "unable to get frame from null member\n" ) ;
return NULL ;
}
//
// 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 )
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
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
return copy_conf_frame( member->inFramesLast ) ;
}
}
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 )
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
return cfr ;
}
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 ;
}
//
// drop a frame if we've filled half the buffer
// ( no more than once per AST_CONF_QUEUE_DROP_TIME_LIMIT ms )
//
/*
ast_log(
AST_CONF_DEBUG,
"queue frame on channel => %s, fr->subclass => %d, fr->datalen => %d, fr->samples => %d\n",
member->channel_name, fr->subclass, fr->datalen, fr->samples
) ;
*/
if ( member->inFramesCount > member->inFramesNeeded )
{
if ( member->inFramesCount > AST_CONF_QUEUE_DROP_THRESHOLD )
{
struct timeval curr ;
gettimeofday( &curr, NULL ) ;
// time since last dropped frame
long diff = usecdiff( &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 ) ) ;
gettimeofday( &member->last_in_dropped, NULL ) ;
}
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 ;
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" ) ;
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 ( (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);
}
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" ) ;
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++ ;
}
}
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 ) ;
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-- ;
return cfr ;
}
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++ ;
/*
//
// drop a frame if we've filled half the buffer
//
if ( member->outFramesCount > AST_CONF_QUEUE_DROP_THRESHOLD )
{
struct timeval curr ;
gettimeofday( &curr, NULL ) ;
long diff = usecdiff( &curr, &member->last_out_dropped ) ;
if ( diff >= AST_CONF_QUEUE_DROP_TIME_LIMIT )
{
ast_log(
AST_CONF_DEBUG,
"dropping frame from output buffer, channel => %s, incoming => %d, outgoing => %d\n",
member->channel_name, member->inFramesCount, member->outFramesCount
) ;
// accounting: count dropped outgoing frames
member->frames_out_dropped++ ;
// delete the frame
delete_conf_frame( get_outgoing_frame( member ) ) ;
gettimeofday( &member->last_out_dropped, NULL ) ;
}
}
*/
//
// 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;
}
}
//
// 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 != member->speaking_state_prev )
{
manager_event(
EVENT_FLAG_CALL,
"ConferenceState",
"Channel: %s\r\n"
"State: %s\r\n",
member->channel_name,
( ( member->speaking_state_notify == 1 ) ? "speaking" : "silent" )
) ;
#if 0
ast_log( AST_CONF_DEBUG, "member state changed, channel => %s, state => %d, incoming => %d, outgoing => %d\n",
member->channel_name, member->speaking_state_notify, member->inFramesCount, member->outFramesCount ) ;
#endif
// remember current state
member->speaking_state_prev = member->speaking_state_notify ;
// we do not reset the speaking_state_notify flag here
}
// reset notification flag so that
member->speaking_state_notify = 0 ;
// move the pointer to the next member
member = member->next ;
}
return ;
}
//
// meta-info accessors functions
//
short memberIsPhoneClient( struct ast_conf_member* member )
{
if ( member == NULL )
return 0 ;
return ( member->connection_type == 'T' ) ? 1 : 0 ;
}
short memberIsIaxClient( struct ast_conf_member* member )
{
if ( member == NULL )
return 0 ;
return ( member->connection_type == 'X' ) ? 1 : 0 ;
}
short memberIsSIPClient( struct ast_conf_member* member )
{
if ( member == NULL )
return 0 ;
return ( member->connection_type == 'S' ) ? 1 : 0 ;
}
short memberIsModerator( struct ast_conf_member* member )
{
if ( member == NULL )
return 0 ;
return ( member->type == 'M' ) ? 1 : 0 ;
}
short memberIsListener( struct ast_conf_member* member )
{
if ( member == NULL )
return 0 ;
return ( member->type == 'L' ) ? 1 : 0 ;
}
//
// 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, f->data, 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;
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(s->f.data, 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);
}