c022bb8d51
Remove ENABLE_IAX option Add an improved FGCom standalone as part of FG in utils directory
1982 lines
44 KiB
C
1982 lines
44 KiB
C
/*
|
|
* iaxclient: a cross-platform IAX softphone library
|
|
*
|
|
* Copyrights:
|
|
* Copyright (C) 2003-2006, Horizon Wimba, Inc.
|
|
* Copyright (C) 2007, Wimba, Inc.
|
|
*
|
|
* Contributors:
|
|
* Steve Kann <stevek@stevek.com>
|
|
* Michael Van Donselaar <mvand@vandonselaar.org>
|
|
* Shawn Lawrence <shawn.lawrence@terracecomm.com>
|
|
* Frik Strecker <frik@gatherworks.com>
|
|
* Mihai Balea <mihai AT hates DOT ms>
|
|
* Peter Grayson <jpgrayson@gmail.com>
|
|
* Bill Cholewka <bcholew@gmail.com>
|
|
* Erik Bunce <kde@bunce.us>
|
|
*
|
|
* This program is free software, distributed under the terms of
|
|
* the GNU Lesser (Library) General Public License.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
|
|
#if defined(WIN32) || defined(_WIN32_WCE)
|
|
#include <stdlib.h>
|
|
#endif
|
|
|
|
/* Win32 has _vsnprintf instead of vsnprintf */
|
|
#if ! HAVE_VSNPRINTF
|
|
# if HAVE__VSNPRINTF
|
|
# define vsnprintf _vsnprintf
|
|
# endif
|
|
#endif
|
|
|
|
#include "iaxclient_lib.h"
|
|
#include "audio_portaudio.h"
|
|
#include "audio_encode.h"
|
|
#ifdef USE_VIDEO
|
|
#include "video.h"
|
|
#endif
|
|
#include "iax-client.h"
|
|
#include "jitterbuf.h"
|
|
|
|
#include <stdarg.h>
|
|
|
|
#ifdef AUDIO_ALSA
|
|
#include "audio_alsa.h"
|
|
#endif
|
|
|
|
#define IAXC_ERROR IAXC_TEXT_TYPE_ERROR
|
|
#define IAXC_STATUS IAXC_TEXT_TYPE_STATUS
|
|
#define IAXC_NOTICE IAXC_TEXT_TYPE_NOTICE
|
|
|
|
#define DEFAULT_CALLERID_NAME "Not Available"
|
|
#define DEFAULT_CALLERID_NUMBER "7005551212"
|
|
|
|
#undef JB_DEBUGGING
|
|
|
|
/* global test mode flag */
|
|
int test_mode = 0;
|
|
|
|
/* configurable jitterbuffer options */
|
|
static long jb_target_extra = -1;
|
|
|
|
struct iaxc_registration
|
|
{
|
|
struct iax_session *session;
|
|
struct timeval last;
|
|
char host[256];
|
|
char user[256];
|
|
char pass[256];
|
|
long refresh;
|
|
int id;
|
|
struct iaxc_registration *next;
|
|
};
|
|
|
|
static int next_registration_id = 0;
|
|
static struct iaxc_registration *registrations = NULL;
|
|
|
|
static struct iaxc_audio_driver audio_driver;
|
|
|
|
static int audio_format_capability;
|
|
static int audio_format_preferred;
|
|
|
|
// Audio callback behavior
|
|
// By default apps should let iaxclient handle audio
|
|
static unsigned int audio_prefs = 0;
|
|
|
|
void * post_event_handle = NULL;
|
|
int post_event_id = 0;
|
|
|
|
static int minimum_outgoing_framesize = 160; /* 20ms */
|
|
|
|
static MUTEX iaxc_lock;
|
|
static MUTEX event_queue_lock;
|
|
|
|
static int iaxci_bound_port = -1;
|
|
|
|
// default to use port 4569 unless set by iaxc_set_preferred_source_udp_port
|
|
static int source_udp_port = IAX_DEFAULT_PORTNO;
|
|
|
|
int iaxci_audio_output_mode = 0; // Normal
|
|
|
|
int selected_call; // XXX to be protected by mutex?
|
|
struct iaxc_call* calls;
|
|
int max_calls; // number of calls for this library session
|
|
|
|
static void service_network();
|
|
static int service_audio();
|
|
|
|
/* external global networking replacements */
|
|
static iaxc_sendto_t iaxc_sendto = (iaxc_sendto_t)sendto;
|
|
static iaxc_recvfrom_t iaxc_recvfrom = (iaxc_recvfrom_t)recvfrom;
|
|
|
|
|
|
static THREAD main_proc_thread;
|
|
#if defined(WIN32) || defined(_WIN32_WCE)
|
|
static THREADID main_proc_thread_id;
|
|
#endif
|
|
|
|
/* 0 running, 1 should quit, -1 not running */
|
|
static int main_proc_thread_flag = -1;
|
|
|
|
static iaxc_event_callback_t iaxc_event_callback = NULL;
|
|
|
|
// Internal queue of events, waiting to be posted once the library
|
|
// lock is released.
|
|
static iaxc_event *event_queue = NULL;
|
|
|
|
// Lock the library
|
|
static void get_iaxc_lock()
|
|
{
|
|
MUTEXLOCK(&iaxc_lock);
|
|
}
|
|
|
|
int try_iaxc_lock()
|
|
{
|
|
return MUTEXTRYLOCK(&iaxc_lock);
|
|
}
|
|
|
|
// Unlock the library and post any events that were queued in the meantime
|
|
void put_iaxc_lock()
|
|
{
|
|
iaxc_event *prev, *event;
|
|
|
|
MUTEXLOCK(&event_queue_lock);
|
|
event = event_queue;
|
|
event_queue = NULL;
|
|
MUTEXUNLOCK(&event_queue_lock);
|
|
|
|
MUTEXUNLOCK(&iaxc_lock);
|
|
|
|
while (event)
|
|
{
|
|
iaxci_post_event(*event);
|
|
prev = event;
|
|
event = event->next;
|
|
free(prev);
|
|
}
|
|
}
|
|
|
|
EXPORT void iaxc_set_audio_output(int mode)
|
|
{
|
|
iaxci_audio_output_mode = mode;
|
|
}
|
|
|
|
long iaxci_usecdiff(struct timeval * t0, struct timeval * t1)
|
|
{
|
|
return (t0->tv_sec - t1->tv_sec) * 1000000L + t0->tv_usec - t1->tv_usec;
|
|
}
|
|
|
|
long iaxci_msecdiff(struct timeval * t0, struct timeval * t1)
|
|
{
|
|
return iaxci_usecdiff(t0, t1) / 1000L;
|
|
}
|
|
|
|
EXPORT void iaxc_set_event_callback(iaxc_event_callback_t func)
|
|
{
|
|
iaxc_event_callback = func;
|
|
}
|
|
|
|
EXPORT int iaxc_set_event_callpost(void *handle, int id)
|
|
{
|
|
post_event_handle = handle;
|
|
post_event_id = id;
|
|
iaxc_event_callback = iaxci_post_event_callback;
|
|
return 0;
|
|
}
|
|
|
|
EXPORT void iaxc_free_event(iaxc_event *e)
|
|
{
|
|
free(e);
|
|
}
|
|
|
|
EXPORT struct iaxc_ev_levels *iaxc_get_event_levels(iaxc_event *e)
|
|
{
|
|
return &e->ev.levels;
|
|
}
|
|
|
|
EXPORT struct iaxc_ev_text *iaxc_get_event_text(iaxc_event *e)
|
|
{
|
|
return &e->ev.text;
|
|
}
|
|
|
|
EXPORT struct iaxc_ev_call_state *iaxc_get_event_state(iaxc_event *e)
|
|
{
|
|
return &e->ev.call;
|
|
}
|
|
|
|
// Messaging functions
|
|
static void default_message_callback(const char * message)
|
|
{
|
|
//fprintf(stderr, "IAXCLIENT: %s\n", message);
|
|
}
|
|
|
|
// Post Events back to clients
|
|
void iaxci_post_event(iaxc_event e)
|
|
{
|
|
if ( e.type == 0 )
|
|
{
|
|
iaxci_usermsg(IAXC_ERROR,
|
|
"Error: something posted to us an invalid event");
|
|
return;
|
|
}
|
|
|
|
if ( MUTEXTRYLOCK(&iaxc_lock) )
|
|
{
|
|
iaxc_event **tail;
|
|
|
|
/* We could not obtain the lock. Queue the event. */
|
|
MUTEXLOCK(&event_queue_lock);
|
|
tail = &event_queue;
|
|
e.next = NULL;
|
|
while ( *tail )
|
|
tail = &(*tail)->next;
|
|
*tail = (iaxc_event *)malloc(sizeof(iaxc_event));
|
|
memcpy(*tail, &e, sizeof(iaxc_event));
|
|
MUTEXUNLOCK(&event_queue_lock);
|
|
return;
|
|
}
|
|
|
|
/* TODO: This is not the best. Since we were able to get the
|
|
* lock, we decide that it is okay to go ahead and do the
|
|
* callback to the application. This is really nasty because
|
|
* it gives the appearance of serialized callbacks, but in
|
|
* reality, we could callback an application multiple times
|
|
* simultaneously. So, as things stand, an application must
|
|
* do some locking in their callback function to make it
|
|
* reentrant. Barf. More ideally, iaxclient would guarantee
|
|
* serialized callbacks to the application.
|
|
*/
|
|
MUTEXUNLOCK(&iaxc_lock);
|
|
|
|
if ( iaxc_event_callback )
|
|
{
|
|
int rv;
|
|
|
|
rv = iaxc_event_callback(e);
|
|
|
|
if ( e.type == IAXC_EVENT_VIDEO )
|
|
{
|
|
/* We can free the frame data once it is off the
|
|
* event queue and has been processed by the client.
|
|
*/
|
|
free(e.ev.video.data);
|
|
}
|
|
else if ( e.type == IAXC_EVENT_AUDIO )
|
|
{
|
|
free(e.ev.audio.data);
|
|
}
|
|
|
|
if ( rv < 0 )
|
|
default_message_callback(
|
|
"IAXCLIENT: BIG PROBLEM, event callback returned failure!");
|
|
// > 0 means processed
|
|
if ( rv > 0 )
|
|
return;
|
|
|
|
// else, fall through to "defaults"
|
|
}
|
|
|
|
switch ( e.type )
|
|
{
|
|
case IAXC_EVENT_TEXT:
|
|
default_message_callback(e.ev.text.message);
|
|
// others we just ignore too
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
void iaxci_usermsg(int type, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
iaxc_event e;
|
|
|
|
e.type = IAXC_EVENT_TEXT;
|
|
e.ev.text.type = type;
|
|
e.ev.text.callNo = -1;
|
|
va_start(args, fmt);
|
|
vsnprintf(e.ev.text.message, IAXC_EVENT_BUFSIZ, fmt, args);
|
|
va_end(args);
|
|
|
|
iaxci_post_event(e);
|
|
}
|
|
|
|
|
|
void iaxci_do_levels_callback(float input, float output)
|
|
{
|
|
iaxc_event e;
|
|
e.type = IAXC_EVENT_LEVELS;
|
|
e.ev.levels.input = input;
|
|
e.ev.levels.output = output;
|
|
iaxci_post_event(e);
|
|
}
|
|
|
|
void iaxci_do_state_callback(int callNo)
|
|
{
|
|
iaxc_event e;
|
|
if ( callNo < 0 || callNo >= max_calls )
|
|
return;
|
|
e.type = IAXC_EVENT_STATE;
|
|
e.ev.call.callNo = callNo;
|
|
e.ev.call.state = calls[callNo].state;
|
|
e.ev.call.format = calls[callNo].format;
|
|
e.ev.call.vformat = calls[callNo].vformat;
|
|
strncpy(e.ev.call.remote, calls[callNo].remote, IAXC_EVENT_BUFSIZ);
|
|
strncpy(e.ev.call.remote_name, calls[callNo].remote_name, IAXC_EVENT_BUFSIZ);
|
|
strncpy(e.ev.call.local, calls[callNo].local, IAXC_EVENT_BUFSIZ);
|
|
strncpy(e.ev.call.local_context, calls[callNo].local_context, IAXC_EVENT_BUFSIZ);
|
|
iaxci_post_event(e);
|
|
}
|
|
|
|
void iaxci_do_registration_callback(int id, int reply, int msgcount)
|
|
{
|
|
iaxc_event e;
|
|
e.type = IAXC_EVENT_REGISTRATION;
|
|
e.ev.reg.id = id;
|
|
e.ev.reg.reply = reply;
|
|
e.ev.reg.msgcount = msgcount;
|
|
iaxci_post_event(e);
|
|
}
|
|
|
|
void iaxci_do_audio_callback(int callNo, unsigned int ts, int source,
|
|
int encoded, int format, int size, unsigned char *data)
|
|
{
|
|
iaxc_event e;
|
|
|
|
e.type = IAXC_EVENT_AUDIO;
|
|
e.ev.audio.ts = ts;
|
|
e.ev.audio.encoded = encoded;
|
|
assert(source == IAXC_SOURCE_REMOTE || source == IAXC_SOURCE_LOCAL);
|
|
e.ev.audio.source = source;
|
|
e.ev.audio.size = size;
|
|
e.ev.audio.callNo = callNo;
|
|
e.ev.audio.format = format;
|
|
|
|
e.ev.audio.data = (unsigned char *)malloc(size);
|
|
|
|
if ( !e.ev.audio.data )
|
|
{
|
|
iaxci_usermsg(IAXC_ERROR,
|
|
"failed to allocate memory for audio event");
|
|
return;
|
|
}
|
|
|
|
memcpy(e.ev.audio.data, data, size);
|
|
|
|
iaxci_post_event(e);
|
|
}
|
|
|
|
void iaxci_do_dtmf_callback(int callNo, char digit)
|
|
{
|
|
iaxc_event e;
|
|
e.type = IAXC_EVENT_DTMF;
|
|
e.ev.dtmf.callNo = callNo;
|
|
e.ev.dtmf.digit = digit;
|
|
iaxci_post_event(e);
|
|
}
|
|
|
|
static int iaxc_remove_registration_by_id(int id)
|
|
{
|
|
struct iaxc_registration *curr, *prev;
|
|
int count = 0;
|
|
for ( prev = NULL, curr = registrations; curr != NULL;
|
|
prev = curr, curr = curr->next )
|
|
{
|
|
if ( curr->id == id )
|
|
{
|
|
count++;
|
|
if ( curr->session != NULL )
|
|
iax_destroy( curr->session );
|
|
if ( prev != NULL )
|
|
prev->next = curr->next;
|
|
else
|
|
registrations = curr->next;
|
|
free( curr );
|
|
break;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
EXPORT int iaxc_first_free_call()
|
|
{
|
|
int i;
|
|
for ( i = 0; i < max_calls; i++ )
|
|
if ( calls[i].state == IAXC_CALL_STATE_FREE )
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
static void iaxc_clear_call(int toDump)
|
|
{
|
|
// XXX libiax should handle cleanup, I think..
|
|
calls[toDump].state = IAXC_CALL_STATE_FREE;
|
|
calls[toDump].format = 0;
|
|
calls[toDump].vformat = 0;
|
|
calls[toDump].session = NULL;
|
|
iaxci_do_state_callback(toDump);
|
|
}
|
|
|
|
/* select a call. */
|
|
/* XXX Locking?? Start/stop audio?? */
|
|
EXPORT int iaxc_select_call(int callNo)
|
|
{
|
|
// continue if already selected?
|
|
//if ( callNo == selected_call ) return;
|
|
|
|
if ( callNo >= max_calls )
|
|
{
|
|
iaxci_usermsg(IAXC_ERROR, "Error: tried to select out_of_range call %d", callNo);
|
|
return -1;
|
|
}
|
|
|
|
// callNo < 0 means no call selected (i.e. all on hold)
|
|
if ( callNo < 0 )
|
|
{
|
|
if ( selected_call >= 0 )
|
|
{
|
|
calls[selected_call].state &= ~IAXC_CALL_STATE_SELECTED;
|
|
}
|
|
selected_call = callNo;
|
|
return 0;
|
|
}
|
|
|
|
// de-select and notify the old call if not also the new call
|
|
if ( callNo != selected_call )
|
|
{
|
|
if ( selected_call >= 0 )
|
|
{
|
|
calls[selected_call].state &= ~IAXC_CALL_STATE_SELECTED;
|
|
iaxci_do_state_callback(selected_call);
|
|
}
|
|
selected_call = callNo;
|
|
calls[selected_call].state |= IAXC_CALL_STATE_SELECTED;
|
|
}
|
|
|
|
// if it's an incoming call, and ringing, answer it.
|
|
if ( !(calls[selected_call].state & IAXC_CALL_STATE_OUTGOING) &&
|
|
(calls[selected_call].state & IAXC_CALL_STATE_RINGING) )
|
|
{
|
|
iaxc_answer_call(selected_call);
|
|
} else
|
|
{
|
|
// otherwise just update state (answer does this for us)
|
|
iaxci_do_state_callback(selected_call);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* external API accessor */
|
|
EXPORT int iaxc_selected_call()
|
|
{
|
|
return selected_call;
|
|
}
|
|
|
|
EXPORT void iaxc_set_networking(iaxc_sendto_t st, iaxc_recvfrom_t rf)
|
|
{
|
|
iaxc_sendto = st;
|
|
iaxc_recvfrom = rf;
|
|
}
|
|
|
|
EXPORT void iaxc_set_jb_target_extra( long value )
|
|
{
|
|
/* store in jb_target_extra, a static global */
|
|
jb_target_extra = value;
|
|
}
|
|
|
|
static void jb_errf(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
char buf[1024];
|
|
|
|
va_start(args, fmt);
|
|
vsnprintf(buf, 1024, fmt, args);
|
|
va_end(args);
|
|
|
|
iaxci_usermsg(IAXC_ERROR, buf);
|
|
}
|
|
|
|
static void jb_warnf(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
char buf[1024];
|
|
|
|
va_start(args, fmt);
|
|
vsnprintf(buf, 1024, fmt, args);
|
|
va_end(args);
|
|
|
|
iaxci_usermsg(IAXC_NOTICE, buf);
|
|
}
|
|
|
|
#ifdef JB_DEBUGGING
|
|
static void jb_dbgf(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
vfprintf(stderr, fmt, args);
|
|
va_end(args);
|
|
}
|
|
#endif
|
|
|
|
static void setup_jb_output()
|
|
{
|
|
#ifdef JB_DEBUGGING
|
|
jb_setoutput(jb_errf, jb_warnf, jb_dbgf);
|
|
#else
|
|
jb_setoutput(jb_errf, jb_warnf, NULL);
|
|
#endif
|
|
}
|
|
|
|
// Note: Must be called before iaxc_initialize()
|
|
EXPORT void iaxc_set_preferred_source_udp_port(int port)
|
|
{
|
|
source_udp_port = port;
|
|
}
|
|
|
|
/* For "slow" systems. See iax.c code */
|
|
EXPORT int iaxc_video_bypass_jitter(int mode)
|
|
{
|
|
/* TODO:
|
|
* 1. When switching from jitter to no-jitter the buffer must be
|
|
* flushed of queued video packet and must be sent a key-frame
|
|
* to redraw the screen (partially done).
|
|
* 2. When switching from no-jitter to jitter we must drop all
|
|
* enqueued events prior the mode change (must be touched
|
|
* iax_sched_del and iax_get_event).
|
|
*/
|
|
return iax_video_bypass_jitter(calls[selected_call].session,mode);
|
|
}
|
|
|
|
EXPORT int iaxc_get_bind_port()
|
|
{
|
|
return iaxci_bound_port;
|
|
}
|
|
|
|
EXPORT int iaxc_initialize(int num_calls)
|
|
{
|
|
int i;
|
|
int port;
|
|
|
|
os_init();
|
|
|
|
setup_jb_output();
|
|
|
|
MUTEXINIT(&iaxc_lock);
|
|
MUTEXINIT(&event_queue_lock);
|
|
|
|
iaxc_set_audio_prefs(0);
|
|
|
|
if ( iaxc_recvfrom != (iaxc_recvfrom_t)recvfrom )
|
|
iax_set_networking(iaxc_sendto, iaxc_recvfrom);
|
|
|
|
/* Note that iax_init() only sets up the receive port when the
|
|
* sendto/recvfrom functions have not been replaced. We need
|
|
* to call iaxc_init in either case because there is other
|
|
* initialization beyond the socket setup that needs to be done.
|
|
*/
|
|
if ( (port = iax_init(source_udp_port)) < 0 )
|
|
{
|
|
iaxci_usermsg(IAXC_ERROR,
|
|
"Fatal error: failed to initialize iax with port %d",
|
|
port);
|
|
return -1;
|
|
}
|
|
|
|
if ( iaxc_recvfrom == (iaxc_recvfrom_t)recvfrom )
|
|
iaxci_bound_port = port;
|
|
else
|
|
iaxci_bound_port = -1;
|
|
|
|
/* tweak the jitterbuffer settings */
|
|
iax_set_jb_target_extra( jb_target_extra );
|
|
|
|
max_calls = num_calls;
|
|
/* initialize calls */
|
|
if ( max_calls <= 0 )
|
|
max_calls = 1; /* 0 == Default? */
|
|
|
|
/* calloc zeroes for us */
|
|
calls = (struct iaxc_call *)calloc(sizeof(struct iaxc_call), max_calls);
|
|
if ( !calls )
|
|
{
|
|
iaxci_usermsg(IAXC_ERROR, "Fatal error: can't allocate memory");
|
|
return -1;
|
|
}
|
|
|
|
selected_call = -1;
|
|
|
|
for ( i = 0; i < max_calls; i++ )
|
|
{
|
|
strncpy(calls[i].callerid_name, DEFAULT_CALLERID_NAME, IAXC_EVENT_BUFSIZ);
|
|
strncpy(calls[i].callerid_number, DEFAULT_CALLERID_NUMBER, IAXC_EVENT_BUFSIZ);
|
|
}
|
|
|
|
if ( !test_mode )
|
|
{
|
|
#ifdef AUDIO_PA
|
|
if ( pa_initialize(&audio_driver, 8000) )
|
|
{
|
|
iaxci_usermsg(IAXC_ERROR, "failed pa_initialize");
|
|
return -1;
|
|
}
|
|
#endif
|
|
#ifdef AUDIO_ALSA
|
|
/* TODO: It is unknown whether this stuff for direct access to
|
|
* alsa should be left in iaxclient. We're leaving it in here for
|
|
* the time being, but unless it becomes clear that someone cares
|
|
* about having it, it will be removed. Also note that portaudio
|
|
* is capable of using alsa. This is another reason why this
|
|
* direct alsa access may be unneeded.
|
|
*/
|
|
if ( alsa_initialize(&audio_driver, 8000) )
|
|
return -1;
|
|
#endif
|
|
#ifdef AUDIO_OPENAL
|
|
if ( openal_initialize(&audio_driver, 8000) )
|
|
{
|
|
iaxci_usermsg(IAXC_ERROR, "failed openal_initialize");
|
|
return -1;
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef USE_VIDEO
|
|
if ( video_initialize() )
|
|
iaxci_usermsg(IAXC_ERROR,
|
|
"iaxc_initialize: cannot initialize video!\n");
|
|
#endif
|
|
|
|
/* Default audio format capabilities */
|
|
audio_format_capability =
|
|
IAXC_FORMAT_ULAW |
|
|
IAXC_FORMAT_ALAW |
|
|
#ifdef CODEC_GSM
|
|
IAXC_FORMAT_GSM |
|
|
#endif
|
|
IAXC_FORMAT_SPEEX;
|
|
audio_format_preferred = IAXC_FORMAT_SPEEX;
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT void iaxc_shutdown()
|
|
{
|
|
iaxc_dump_all_calls();
|
|
|
|
get_iaxc_lock();
|
|
|
|
if ( !test_mode )
|
|
{
|
|
audio_driver.destroy(&audio_driver);
|
|
#ifdef USE_VIDEO
|
|
video_destroy();
|
|
#endif
|
|
}
|
|
|
|
/* destroy enocders and decoders for all existing calls */
|
|
if ( calls )
|
|
{
|
|
int i;
|
|
for ( i=0 ; i<max_calls ; i++ )
|
|
{
|
|
if ( calls[i].encoder )
|
|
calls[i].encoder->destroy(calls[i].encoder);
|
|
if ( calls[i].decoder )
|
|
calls[i].decoder->destroy(calls[i].decoder);
|
|
if ( calls[i].vencoder )
|
|
calls[i].vencoder->destroy(calls[i].vencoder);
|
|
if ( calls[i].vdecoder )
|
|
calls[i].vdecoder->destroy(calls[i].vdecoder);
|
|
}
|
|
free(calls);
|
|
calls = NULL;
|
|
}
|
|
put_iaxc_lock();
|
|
#ifdef WIN32
|
|
closesocket(iax_get_fd()); //fd:
|
|
#endif
|
|
|
|
free(calls);
|
|
|
|
MUTEXDESTROY(&event_queue_lock);
|
|
MUTEXDESTROY(&iaxc_lock);
|
|
}
|
|
|
|
|
|
EXPORT void iaxc_set_formats(int preferred, int allowed)
|
|
{
|
|
audio_format_capability = allowed;
|
|
audio_format_preferred = preferred;
|
|
}
|
|
|
|
EXPORT void iaxc_set_min_outgoing_framesize(int samples)
|
|
{
|
|
minimum_outgoing_framesize = samples;
|
|
}
|
|
|
|
EXPORT void iaxc_set_callerid(const char * name, const char * number)
|
|
{
|
|
int i;
|
|
|
|
for ( i = 0; i < max_calls; i++ )
|
|
{
|
|
strncpy(calls[i].callerid_name, name, IAXC_EVENT_BUFSIZ);
|
|
strncpy(calls[i].callerid_number, number, IAXC_EVENT_BUFSIZ);
|
|
}
|
|
}
|
|
|
|
static void iaxc_note_activity(int callNo)
|
|
{
|
|
if ( callNo < 0 )
|
|
return;
|
|
calls[callNo].last_activity = iax_tvnow();
|
|
}
|
|
|
|
static void iaxc_refresh_registrations()
|
|
{
|
|
struct iaxc_registration *cur;
|
|
struct timeval now;
|
|
|
|
now = iax_tvnow();
|
|
|
|
for ( cur = registrations; cur != NULL; cur = cur->next )
|
|
{
|
|
// If there is less than three seconds before the registration is about
|
|
// to expire, renew it.
|
|
if ( iaxci_usecdiff(&now, &cur->last) > (cur->refresh - 3) * 1000 *1000 )
|
|
{
|
|
if ( cur->session != NULL )
|
|
{
|
|
iax_destroy( cur->session );
|
|
}
|
|
cur->session = iax_session_new();
|
|
if ( !cur->session )
|
|
{
|
|
iaxci_usermsg(IAXC_ERROR, "Can't make new registration session");
|
|
return;
|
|
}
|
|
iax_register(cur->session, cur->host, cur->user, cur->pass, cur->refresh);
|
|
cur->last = now;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define LOOP_SLEEP 5 // In ms
|
|
static THREADFUNCDECL(main_proc_thread_func)
|
|
{
|
|
static int refresh_registration_count = 0;
|
|
|
|
THREADFUNCRET(ret);
|
|
|
|
/* Increase Priority */
|
|
iaxci_prioboostbegin();
|
|
|
|
while ( !main_proc_thread_flag )
|
|
{
|
|
get_iaxc_lock();
|
|
|
|
service_network();
|
|
if ( !test_mode )
|
|
service_audio();
|
|
|
|
// Check registration refresh once a second
|
|
if ( refresh_registration_count++ > 1000/LOOP_SLEEP )
|
|
{
|
|
iaxc_refresh_registrations();
|
|
refresh_registration_count = 0;
|
|
}
|
|
|
|
put_iaxc_lock();
|
|
|
|
iaxc_millisleep(LOOP_SLEEP);
|
|
}
|
|
|
|
/* Decrease priority */
|
|
iaxci_prioboostend();
|
|
|
|
main_proc_thread_flag = -1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
EXPORT int iaxc_start_processing_thread()
|
|
{
|
|
main_proc_thread_flag = 0;
|
|
|
|
if ( THREADCREATE(main_proc_thread_func, NULL, main_proc_thread,
|
|
main_proc_thread_id) == THREADCREATE_ERROR)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT int iaxc_stop_processing_thread()
|
|
{
|
|
if ( main_proc_thread_flag >= 0 )
|
|
{
|
|
main_proc_thread_flag = 1;
|
|
THREADJOIN(main_proc_thread);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int service_audio()
|
|
{
|
|
/* TODO: maybe we shouldn't allocate 8kB on the stack here. */
|
|
short buf [4096];
|
|
|
|
int want_send_audio =
|
|
selected_call >= 0 &&
|
|
((calls[selected_call].state & IAXC_CALL_STATE_OUTGOING) ||
|
|
(calls[selected_call].state & IAXC_CALL_STATE_COMPLETE))
|
|
&& !(audio_prefs & IAXC_AUDIO_PREF_SEND_DISABLE);
|
|
|
|
int want_local_audio =
|
|
(audio_prefs & IAXC_AUDIO_PREF_RECV_LOCAL_RAW) ||
|
|
(audio_prefs & IAXC_AUDIO_PREF_RECV_LOCAL_ENCODED);
|
|
|
|
if ( want_local_audio || want_send_audio )
|
|
{
|
|
for ( ;; )
|
|
{
|
|
int to_read;
|
|
int cmin;
|
|
|
|
audio_driver.start(&audio_driver);
|
|
|
|
/* use codec minimum if higher */
|
|
cmin = want_send_audio && calls[selected_call].encoder ?
|
|
calls[selected_call].encoder->minimum_frame_size :
|
|
1;
|
|
|
|
to_read = cmin > minimum_outgoing_framesize ?
|
|
cmin : minimum_outgoing_framesize;
|
|
|
|
/* Round up to the next multiple */
|
|
if ( to_read % cmin )
|
|
to_read += cmin - (to_read % cmin);
|
|
|
|
if ( to_read > (int)(sizeof(buf) / sizeof(short)) )
|
|
{
|
|
fprintf(stderr,
|
|
"internal error: to_read > sizeof(buf)\n");
|
|
exit(1);
|
|
}
|
|
|
|
/* Currently pa gives us either all the bits we ask for or none */
|
|
if ( audio_driver.input(&audio_driver, buf, &to_read) )
|
|
{
|
|
iaxci_usermsg(IAXC_ERROR, "ERROR reading audio\n");
|
|
break;
|
|
}
|
|
|
|
/* Frame was not available */
|
|
if ( !to_read )
|
|
break;
|
|
|
|
if ( audio_prefs & IAXC_AUDIO_PREF_RECV_LOCAL_RAW )
|
|
iaxci_do_audio_callback(selected_call, 0,
|
|
IAXC_SOURCE_LOCAL, 0, 0,
|
|
to_read * 2, (unsigned char *)buf);
|
|
|
|
if ( want_send_audio )
|
|
audio_send_encoded_audio(&calls[selected_call],
|
|
selected_call, buf,
|
|
calls[selected_call].format &
|
|
IAXC_AUDIO_FORMAT_MASK,
|
|
to_read);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
static int i = 0;
|
|
|
|
audio_driver.stop(&audio_driver);
|
|
|
|
/*!
|
|
\deprecated
|
|
Q: Why do we continuously send IAXC_EVENT_LEVELS events
|
|
when there is no selected call?
|
|
|
|
A: So that certain users of iaxclient do not have to
|
|
reset their vu meters when a call ends -- they can just
|
|
count on getting level callbacks. This is a bit of a hack
|
|
so any applications relying on this behavior should maybe
|
|
be changed.
|
|
*/
|
|
if ( i++ % 50 == 0 )
|
|
iaxci_do_levels_callback(AUDIO_ENCODE_SILENCE_DB,
|
|
AUDIO_ENCODE_SILENCE_DB);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* handle IAX text events */
|
|
static void handle_text_event(struct iax_event *e, int callNo)
|
|
{
|
|
iaxc_event ev;
|
|
int len;
|
|
|
|
if ( callNo < 0 )
|
|
return;
|
|
|
|
memset(&ev, 0, sizeof(iaxc_event));
|
|
ev.type = IAXC_EVENT_TEXT;
|
|
ev.ev.text.type = IAXC_TEXT_TYPE_IAX;
|
|
ev.ev.text.callNo = callNo;
|
|
|
|
len = e->datalen <= IAXC_EVENT_BUFSIZ - 1 ? e->datalen : IAXC_EVENT_BUFSIZ - 1;
|
|
strncpy(ev.ev.text.message, (char *) e->data, len);
|
|
iaxci_post_event(ev);
|
|
}
|
|
|
|
/* handle IAX URL events */
|
|
void handle_url_event( struct iax_event *e, int callNo )
|
|
{
|
|
iaxc_event ev;
|
|
|
|
if ( callNo < 0 )
|
|
return;
|
|
|
|
ev.ev.url.callNo = callNo;
|
|
ev.type = IAXC_EVENT_URL;
|
|
strcpy( ev.ev.url.url, "" );
|
|
|
|
switch ( e->subclass )
|
|
{
|
|
case AST_HTML_URL:
|
|
ev.ev.url.type = IAXC_URL_URL;
|
|
if ( e->datalen )
|
|
{
|
|
if ( e->datalen > IAXC_EVENT_BUFSIZ )
|
|
{
|
|
fprintf( stderr, "ERROR: URL too long %d > %d\n",
|
|
e->datalen, IAXC_EVENT_BUFSIZ );
|
|
} else
|
|
{
|
|
strncpy( ev.ev.url.url, (char *) e->data, e->datalen );
|
|
}
|
|
}
|
|
/* fprintf( stderr, "URL:%s\n", ev.ev.url.url ); */
|
|
break;
|
|
case AST_HTML_LINKURL:
|
|
ev.ev.url.type = IAXC_URL_LINKURL;
|
|
/* fprintf( stderr, "LINKURL event\n" ); */
|
|
break;
|
|
case AST_HTML_LDCOMPLETE:
|
|
ev.ev.url.type = IAXC_URL_LDCOMPLETE;
|
|
/* fprintf( stderr, "LDCOMPLETE event\n" ); */
|
|
break;
|
|
case AST_HTML_UNLINK:
|
|
ev.ev.url.type = IAXC_URL_UNLINK;
|
|
/* fprintf( stderr, "UNLINK event\n" ); */
|
|
break;
|
|
case AST_HTML_LINKREJECT:
|
|
ev.ev.url.type = IAXC_URL_LINKREJECT;
|
|
/* fprintf( stderr, "LINKREJECT event\n" ); */
|
|
break;
|
|
default:
|
|
fprintf( stderr, "Unknown URL event %d\n", e->subclass );
|
|
break;
|
|
}
|
|
iaxci_post_event( ev );
|
|
}
|
|
|
|
/* DANGER: bad things can happen if iaxc_netstat != iax_netstat.. */
|
|
EXPORT int iaxc_get_netstats(int call, int *rtt, struct iaxc_netstat *local,
|
|
struct iaxc_netstat *remote)
|
|
{
|
|
return iax_get_netstats(calls[call].session, rtt,
|
|
(struct iax_netstat *)local,
|
|
(struct iax_netstat *)remote);
|
|
}
|
|
|
|
/* handle IAX text events */
|
|
static void generate_netstat_event(int callNo)
|
|
{
|
|
iaxc_event ev;
|
|
|
|
if ( callNo < 0 )
|
|
return;
|
|
|
|
ev.type = IAXC_EVENT_NETSTAT;
|
|
ev.ev.netstats.callNo = callNo;
|
|
|
|
/* only post the event if the session is valid, etc */
|
|
if ( !iaxc_get_netstats(callNo, &ev.ev.netstats.rtt,
|
|
&ev.ev.netstats.local, &ev.ev.netstats.remote))
|
|
iaxci_post_event(ev);
|
|
}
|
|
|
|
static void handle_audio_event(struct iax_event *e, int callNo)
|
|
{
|
|
int total_consumed = 0;
|
|
short fr[4096];
|
|
const int fr_samples = sizeof(fr) / sizeof(short);
|
|
int samples, format;
|
|
#ifdef WIN32
|
|
int cycles_max = 100; //fd:
|
|
#endif
|
|
struct iaxc_call *call;
|
|
|
|
if ( callNo < 0 )
|
|
return;
|
|
|
|
call = &calls[callNo];
|
|
|
|
if ( callNo != selected_call )
|
|
{
|
|
/* drop audio for unselected call? */
|
|
return;
|
|
}
|
|
|
|
samples = fr_samples;
|
|
format = call->format & IAXC_AUDIO_FORMAT_MASK;
|
|
|
|
do
|
|
{
|
|
int bytes_decoded;
|
|
|
|
int mainbuf_delta = fr_samples - samples;
|
|
|
|
bytes_decoded = audio_decode_audio(call,
|
|
fr,
|
|
e->data + total_consumed,
|
|
e->datalen - total_consumed,
|
|
format,
|
|
&samples);
|
|
|
|
if ( bytes_decoded < 0 )
|
|
{
|
|
iaxci_usermsg(IAXC_STATUS,
|
|
"Bad or incomplete voice packet. Unable to decode. dropping");
|
|
return;
|
|
}
|
|
|
|
/* Pass encoded audio back to the app if required */
|
|
if ( audio_prefs & IAXC_AUDIO_PREF_RECV_REMOTE_ENCODED )
|
|
iaxci_do_audio_callback(callNo, e->ts, IAXC_SOURCE_REMOTE,
|
|
1, format & IAXC_AUDIO_FORMAT_MASK,
|
|
e->datalen - total_consumed,
|
|
e->data + total_consumed);
|
|
|
|
#ifdef WIN32
|
|
//fd: start: for some reason it loops here. Try to avoid it
|
|
cycles_max--;
|
|
if ( cycles_max < 0 )
|
|
{
|
|
iaxc_millisleep(0);
|
|
}
|
|
//fd: end
|
|
#endif
|
|
total_consumed += bytes_decoded;
|
|
if ( audio_prefs & IAXC_AUDIO_PREF_RECV_REMOTE_RAW )
|
|
{
|
|
// audio_decode_audio returns the number of samples.
|
|
// We are using 16 bit samples, so we need to double
|
|
// the number to obtain the size in bytes.
|
|
// format will also be 0 since this is raw audio
|
|
int size = (fr_samples - samples - mainbuf_delta) * 2;
|
|
iaxci_do_audio_callback(callNo, e->ts, IAXC_SOURCE_REMOTE,
|
|
0, 0, size, (unsigned char *)fr);
|
|
}
|
|
|
|
if ( iaxci_audio_output_mode )
|
|
continue;
|
|
|
|
if ( !test_mode )
|
|
audio_driver.output(&audio_driver, fr,
|
|
fr_samples - samples - mainbuf_delta);
|
|
|
|
} while ( total_consumed < e->datalen );
|
|
}
|
|
|
|
#ifdef USE_VIDEO
|
|
static void handle_video_event(struct iax_event *e, int callNo)
|
|
{
|
|
struct iaxc_call *call;
|
|
|
|
if ( callNo < 0 )
|
|
return;
|
|
|
|
if ( e->datalen == 0 )
|
|
{
|
|
iaxci_usermsg(IAXC_STATUS, "Received 0-size packet. Unable to decode.");
|
|
return;
|
|
}
|
|
|
|
call = &calls[callNo];
|
|
|
|
if ( callNo != selected_call )
|
|
{
|
|
/* drop video for unselected call? */
|
|
return;
|
|
}
|
|
|
|
if ( call->vformat )
|
|
{
|
|
if ( video_recv_video(call, selected_call, e->data,
|
|
e->datalen, e->ts, call->vformat) < 0 )
|
|
{
|
|
iaxci_usermsg(IAXC_STATUS,
|
|
"Bad or incomplete video packet. Unable to decode.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#endif /* USE_VIDEO */
|
|
|
|
static void iaxc_handle_network_event(struct iax_event *e, int callNo)
|
|
{
|
|
if ( callNo < 0 )
|
|
return;
|
|
|
|
iaxc_note_activity(callNo);
|
|
|
|
switch ( e->etype )
|
|
{
|
|
case IAX_EVENT_NULL:
|
|
break;
|
|
case IAX_EVENT_HANGUP:
|
|
iaxci_usermsg(IAXC_STATUS, "Call disconnected by remote");
|
|
// XXX does the session go away now?
|
|
iaxc_clear_call(callNo);
|
|
break;
|
|
case IAX_EVENT_REJECT:
|
|
iaxci_usermsg(IAXC_STATUS, "Call rejected by remote");
|
|
iaxc_clear_call(callNo);
|
|
break;
|
|
case IAX_EVENT_ACCEPT:
|
|
calls[callNo].format = e->ies.format & IAXC_AUDIO_FORMAT_MASK;
|
|
calls[callNo].vformat = e->ies.format & IAXC_VIDEO_FORMAT_MASK;
|
|
if ( !(e->ies.format & IAXC_VIDEO_FORMAT_MASK) )
|
|
{
|
|
iaxci_usermsg(IAXC_NOTICE,
|
|
"Failed video codec negotiation.");
|
|
}
|
|
iaxci_usermsg(IAXC_STATUS,"Call %d accepted", callNo);
|
|
break;
|
|
case IAX_EVENT_ANSWER:
|
|
calls[callNo].state &= ~IAXC_CALL_STATE_RINGING;
|
|
calls[callNo].state |= IAXC_CALL_STATE_COMPLETE;
|
|
iaxci_do_state_callback(callNo);
|
|
iaxci_usermsg(IAXC_STATUS,"Call %d answered", callNo);
|
|
//iaxc_answer_call(callNo);
|
|
// notify the user?
|
|
break;
|
|
case IAX_EVENT_BUSY:
|
|
calls[callNo].state &= ~IAXC_CALL_STATE_RINGING;
|
|
calls[callNo].state |= IAXC_CALL_STATE_BUSY;
|
|
iaxci_do_state_callback(callNo);
|
|
iaxci_usermsg(IAXC_STATUS, "Call %d busy", callNo);
|
|
break;
|
|
case IAX_EVENT_VOICE:
|
|
handle_audio_event(e, callNo);
|
|
if ( (calls[callNo].state & IAXC_CALL_STATE_OUTGOING) &&
|
|
(calls[callNo].state & IAXC_CALL_STATE_RINGING) )
|
|
{
|
|
calls[callNo].state &= ~IAXC_CALL_STATE_RINGING;
|
|
calls[callNo].state |= IAXC_CALL_STATE_COMPLETE;
|
|
iaxci_do_state_callback(callNo);
|
|
iaxci_usermsg(IAXC_STATUS,"Call %d progress",
|
|
callNo);
|
|
}
|
|
break;
|
|
#ifdef USE_VIDEO
|
|
case IAX_EVENT_VIDEO:
|
|
handle_video_event(e, callNo);
|
|
break;
|
|
#endif
|
|
case IAX_EVENT_TEXT:
|
|
handle_text_event(e, callNo);
|
|
break;
|
|
case IAX_EVENT_RINGA:
|
|
calls[callNo].state |= IAXC_CALL_STATE_RINGING;
|
|
iaxci_do_state_callback(callNo);
|
|
iaxci_usermsg(IAXC_STATUS,"Call %d ringing", callNo);
|
|
break;
|
|
case IAX_EVENT_PONG:
|
|
generate_netstat_event(callNo);
|
|
break;
|
|
case IAX_EVENT_URL:
|
|
handle_url_event(e, callNo);
|
|
break;
|
|
case IAX_EVENT_CNG:
|
|
/* ignore? */
|
|
break;
|
|
case IAX_EVENT_TIMEOUT:
|
|
iax_hangup(e->session, "Call timed out");
|
|
iaxci_usermsg(IAXC_STATUS, "Call %d timed out.", callNo);
|
|
iaxc_clear_call(callNo);
|
|
break;
|
|
case IAX_EVENT_TRANSFER:
|
|
calls[callNo].state |= IAXC_CALL_STATE_TRANSFER;
|
|
iaxci_do_state_callback(callNo);
|
|
iaxci_usermsg(IAXC_STATUS,"Call %d transfer released", callNo);
|
|
break;
|
|
case IAX_EVENT_DTMF:
|
|
iaxci_do_dtmf_callback(callNo,e->subclass);
|
|
iaxci_usermsg(IAXC_STATUS, "DTMF digit %c received", e->subclass);
|
|
break;
|
|
default:
|
|
iaxci_usermsg(IAXC_STATUS, "Unknown event: %d for call %d", e->etype, callNo);
|
|
break;
|
|
}
|
|
}
|
|
|
|
EXPORT int iaxc_unregister( int id )
|
|
{
|
|
int count = 0;
|
|
get_iaxc_lock();
|
|
count = iaxc_remove_registration_by_id(id);
|
|
put_iaxc_lock();
|
|
return count;
|
|
}
|
|
|
|
EXPORT int iaxc_register(const char * user, const char * pass, const char * host)
|
|
{
|
|
return iaxc_register_ex(user, pass, host, 60);
|
|
}
|
|
|
|
EXPORT int iaxc_register_ex(const char * user, const char * pass, const char * host, int refresh)
|
|
{
|
|
struct iaxc_registration *newreg;
|
|
|
|
newreg = (struct iaxc_registration *)malloc(sizeof (struct iaxc_registration));
|
|
if ( !newreg )
|
|
{
|
|
iaxci_usermsg(IAXC_ERROR, "Can't make new registration");
|
|
return -1;
|
|
}
|
|
|
|
get_iaxc_lock();
|
|
newreg->session = iax_session_new();
|
|
if ( !newreg->session )
|
|
{
|
|
iaxci_usermsg(IAXC_ERROR, "Can't make new registration session");
|
|
put_iaxc_lock();
|
|
return -1;
|
|
}
|
|
|
|
newreg->last = iax_tvnow();
|
|
newreg->refresh = refresh;
|
|
|
|
strncpy(newreg->host, host, 256);
|
|
strncpy(newreg->user, user, 256);
|
|
strncpy(newreg->pass, pass, 256);
|
|
|
|
/* send out the initial registration with refresh seconds */
|
|
iax_register(newreg->session, host, user, pass, refresh);
|
|
|
|
/* add it to the list; */
|
|
newreg->id = ++next_registration_id;
|
|
newreg->next = registrations;
|
|
registrations = newreg;
|
|
|
|
put_iaxc_lock();
|
|
return newreg->id;
|
|
}
|
|
|
|
static void codec_destroy( int callNo )
|
|
{
|
|
if ( calls[callNo].encoder )
|
|
{
|
|
calls[callNo].encoder->destroy( calls[callNo].encoder );
|
|
calls[callNo].encoder = NULL;
|
|
}
|
|
if ( calls[callNo].decoder )
|
|
{
|
|
calls[callNo].decoder->destroy( calls[callNo].decoder );
|
|
calls[callNo].decoder = NULL;
|
|
}
|
|
if ( calls[callNo].vdecoder )
|
|
{
|
|
calls[callNo].vdecoder->destroy(calls[callNo].vdecoder);
|
|
calls[callNo].vdecoder = NULL;
|
|
}
|
|
if ( calls[callNo].vencoder )
|
|
{
|
|
calls[callNo].vencoder->destroy(calls[callNo].vencoder);
|
|
calls[callNo].vencoder = NULL;
|
|
}
|
|
}
|
|
|
|
EXPORT int iaxc_call(const char * num)
|
|
{
|
|
return iaxc_call_ex(num, NULL, NULL, 1);
|
|
}
|
|
|
|
EXPORT int iaxc_call_ex(const char *num, const char* callerid_name, const char* callerid_number, int video)
|
|
{
|
|
int video_format_capability = 0;
|
|
int video_format_preferred = 0;
|
|
int callNo = -1;
|
|
struct iax_session *newsession;
|
|
char *ext = strstr(num, "/");
|
|
|
|
get_iaxc_lock();
|
|
|
|
// if no call is selected, get a new appearance
|
|
if ( selected_call < 0 )
|
|
{
|
|
callNo = iaxc_first_free_call();
|
|
} else
|
|
{
|
|
// use selected call if not active, otherwise, get a new appearance
|
|
if ( calls[selected_call].state & IAXC_CALL_STATE_ACTIVE )
|
|
{
|
|
callNo = iaxc_first_free_call();
|
|
} else
|
|
{
|
|
callNo = selected_call;
|
|
}
|
|
}
|
|
|
|
if ( callNo < 0 )
|
|
{
|
|
iaxci_usermsg(IAXC_STATUS, "No free call appearances");
|
|
goto iaxc_call_bail;
|
|
}
|
|
|
|
newsession = iax_session_new();
|
|
if ( !newsession )
|
|
{
|
|
iaxci_usermsg(IAXC_ERROR, "Can't make new session");
|
|
goto iaxc_call_bail;
|
|
}
|
|
|
|
calls[callNo].session = newsession;
|
|
|
|
codec_destroy( callNo );
|
|
|
|
if ( ext )
|
|
{
|
|
strncpy(calls[callNo].remote_name, num, IAXC_EVENT_BUFSIZ);
|
|
strncpy(calls[callNo].remote, ++ext, IAXC_EVENT_BUFSIZ);
|
|
} else
|
|
{
|
|
strncpy(calls[callNo].remote_name, num, IAXC_EVENT_BUFSIZ);
|
|
strncpy(calls[callNo].remote, "" , IAXC_EVENT_BUFSIZ);
|
|
}
|
|
|
|
if ( callerid_number != NULL )
|
|
strncpy(calls[callNo].callerid_number, callerid_number, IAXC_EVENT_BUFSIZ);
|
|
|
|
if ( callerid_name != NULL )
|
|
strncpy(calls[callNo].callerid_name, callerid_name, IAXC_EVENT_BUFSIZ);
|
|
|
|
strncpy(calls[callNo].local , calls[callNo].callerid_name, IAXC_EVENT_BUFSIZ);
|
|
strncpy(calls[callNo].local_context, "default", IAXC_EVENT_BUFSIZ);
|
|
|
|
calls[callNo].state = IAXC_CALL_STATE_ACTIVE | IAXC_CALL_STATE_OUTGOING;
|
|
|
|
/* reset activity and ping "timers" */
|
|
iaxc_note_activity(callNo);
|
|
calls[callNo].last_ping = calls[callNo].last_activity;
|
|
|
|
#ifdef USE_VIDEO
|
|
if ( video )
|
|
iaxc_video_format_get_cap(&video_format_preferred, &video_format_capability);
|
|
#endif
|
|
|
|
iaxci_usermsg(IAXC_NOTICE, "Originating an %s call",
|
|
video_format_preferred ? "audio+video" : "audio only");
|
|
iax_call(calls[callNo].session, calls[callNo].callerid_number,
|
|
calls[callNo].callerid_name, num, NULL, 0,
|
|
audio_format_preferred | video_format_preferred,
|
|
audio_format_capability | video_format_capability);
|
|
|
|
// does state stuff also
|
|
iaxc_select_call(callNo);
|
|
|
|
iaxc_call_bail:
|
|
put_iaxc_lock();
|
|
|
|
return callNo;
|
|
}
|
|
|
|
EXPORT void iaxc_send_busy_on_incoming_call(int callNo)
|
|
{
|
|
if ( callNo < 0 )
|
|
return;
|
|
|
|
iax_busy(calls[callNo].session);
|
|
}
|
|
|
|
EXPORT void iaxc_answer_call(int callNo)
|
|
{
|
|
if ( callNo < 0 )
|
|
return;
|
|
|
|
calls[callNo].state |= IAXC_CALL_STATE_COMPLETE;
|
|
calls[callNo].state &= ~IAXC_CALL_STATE_RINGING;
|
|
iax_answer(calls[callNo].session);
|
|
iaxci_do_state_callback(callNo);
|
|
}
|
|
|
|
EXPORT void iaxc_blind_transfer_call(int callNo, const char * dest_extension)
|
|
{
|
|
if ( callNo < 0 || !(calls[callNo].state & IAXC_CALL_STATE_ACTIVE) )
|
|
return;
|
|
|
|
iax_transfer(calls[callNo].session, dest_extension);
|
|
}
|
|
|
|
EXPORT void iaxc_setup_call_transfer(int sourceCallNo, int targetCallNo)
|
|
{
|
|
if ( sourceCallNo < 0 || targetCallNo < 0 ||
|
|
!(calls[sourceCallNo].state & IAXC_CALL_STATE_ACTIVE) ||
|
|
!(calls[targetCallNo].state & IAXC_CALL_STATE_ACTIVE) )
|
|
return;
|
|
|
|
iax_setup_transfer(calls[sourceCallNo].session, calls[targetCallNo].session);
|
|
}
|
|
|
|
static void iaxc_dump_one_call(int callNo)
|
|
{
|
|
if ( callNo < 0 )
|
|
return;
|
|
if ( calls[callNo].state == IAXC_CALL_STATE_FREE )
|
|
return;
|
|
|
|
iax_hangup(calls[callNo].session,"Dumped Call");
|
|
iaxci_usermsg(IAXC_STATUS, "Hanging up call %d", callNo);
|
|
iaxc_clear_call(callNo);
|
|
}
|
|
|
|
EXPORT void iaxc_dump_all_calls(void)
|
|
{
|
|
int callNo;
|
|
get_iaxc_lock();
|
|
for ( callNo = 0; callNo < max_calls; callNo++ )
|
|
iaxc_dump_one_call(callNo);
|
|
put_iaxc_lock();
|
|
}
|
|
|
|
|
|
EXPORT void iaxc_dump_call_number( int callNo )
|
|
{
|
|
if ( ( callNo >= 0 ) && ( callNo < max_calls ) )
|
|
{
|
|
get_iaxc_lock();
|
|
iaxc_dump_one_call(callNo);
|
|
put_iaxc_lock();
|
|
}
|
|
}
|
|
|
|
EXPORT void iaxc_dump_call(void)
|
|
{
|
|
if ( selected_call >= 0 )
|
|
{
|
|
get_iaxc_lock();
|
|
iaxc_dump_one_call(selected_call);
|
|
put_iaxc_lock();
|
|
}
|
|
}
|
|
|
|
EXPORT void iaxc_reject_call(void)
|
|
{
|
|
if ( selected_call >= 0 )
|
|
{
|
|
iaxc_reject_call_number(selected_call);
|
|
}
|
|
}
|
|
|
|
EXPORT void iaxc_reject_call_number( int callNo )
|
|
{
|
|
if ( ( callNo >= 0 ) && ( callNo < max_calls ) )
|
|
{
|
|
get_iaxc_lock();
|
|
iax_reject(calls[callNo].session, "Call rejected manually.");
|
|
iaxc_clear_call(callNo);
|
|
put_iaxc_lock();
|
|
}
|
|
}
|
|
|
|
EXPORT void iaxc_send_dtmf(char digit)
|
|
{
|
|
if ( selected_call >= 0 )
|
|
{
|
|
get_iaxc_lock();
|
|
if ( calls[selected_call].state & IAXC_CALL_STATE_ACTIVE )
|
|
iax_send_dtmf(calls[selected_call].session,digit);
|
|
put_iaxc_lock();
|
|
}
|
|
}
|
|
|
|
EXPORT void iaxc_send_text(const char * text)
|
|
{
|
|
if ( selected_call >= 0 )
|
|
{
|
|
get_iaxc_lock();
|
|
if ( calls[selected_call].state & IAXC_CALL_STATE_ACTIVE )
|
|
iax_send_text(calls[selected_call].session, text);
|
|
put_iaxc_lock();
|
|
}
|
|
}
|
|
|
|
EXPORT void iaxc_send_text_call(int callNo, const char * text)
|
|
{
|
|
if ( callNo < 0 || !(calls[callNo].state & IAXC_CALL_STATE_ACTIVE) )
|
|
return;
|
|
|
|
get_iaxc_lock();
|
|
if ( calls[callNo].state & IAXC_CALL_STATE_ACTIVE )
|
|
iax_send_text(calls[callNo].session, text);
|
|
put_iaxc_lock();
|
|
}
|
|
|
|
EXPORT void iaxc_send_url(const char * url, int link)
|
|
{
|
|
if ( selected_call >= 0 )
|
|
{
|
|
get_iaxc_lock();
|
|
if ( calls[selected_call].state & IAXC_CALL_STATE_ACTIVE )
|
|
iax_send_url(calls[selected_call].session, url, link);
|
|
put_iaxc_lock();
|
|
}
|
|
}
|
|
|
|
static int iaxc_find_call_by_session(struct iax_session *session)
|
|
{
|
|
int i;
|
|
for ( i = 0; i < max_calls; i++ )
|
|
if ( calls[i].session == session )
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
static struct iaxc_registration *iaxc_find_registration_by_session(
|
|
struct iax_session *session)
|
|
{
|
|
struct iaxc_registration *reg;
|
|
for (reg = registrations; reg != NULL; reg = reg->next)
|
|
if ( reg->session == session )
|
|
break;
|
|
return reg;
|
|
}
|
|
|
|
static void iaxc_handle_regreply(struct iax_event *e, struct iaxc_registration *reg)
|
|
{
|
|
iaxci_do_registration_callback(reg->id, e->etype, e->ies.msgcount);
|
|
|
|
// XXX I think the session is no longer valid.. at least, that's
|
|
// what miniphone does, and re-using the session doesn't seem to
|
|
// work!
|
|
iax_destroy(reg->session);
|
|
reg->session = NULL;
|
|
|
|
if ( e->etype == IAX_EVENT_REGREJ )
|
|
{
|
|
// we were rejected, so end the registration
|
|
iaxc_remove_registration_by_id(reg->id);
|
|
}
|
|
}
|
|
|
|
/* this is what asterisk does */
|
|
static int iaxc_choose_codec(int formats)
|
|
{
|
|
int i;
|
|
static int codecs[] =
|
|
{
|
|
IAXC_FORMAT_ULAW,
|
|
IAXC_FORMAT_ALAW,
|
|
IAXC_FORMAT_SLINEAR,
|
|
IAXC_FORMAT_G726,
|
|
IAXC_FORMAT_ADPCM,
|
|
IAXC_FORMAT_GSM,
|
|
IAXC_FORMAT_ILBC,
|
|
IAXC_FORMAT_SPEEX,
|
|
IAXC_FORMAT_LPC10,
|
|
IAXC_FORMAT_G729A,
|
|
IAXC_FORMAT_G723_1,
|
|
|
|
/* To negotiate video codec */
|
|
IAXC_FORMAT_JPEG,
|
|
IAXC_FORMAT_PNG,
|
|
IAXC_FORMAT_H261,
|
|
IAXC_FORMAT_H263,
|
|
IAXC_FORMAT_H263_PLUS,
|
|
IAXC_FORMAT_MPEG4,
|
|
IAXC_FORMAT_H264,
|
|
IAXC_FORMAT_THEORA,
|
|
};
|
|
for ( i = 0; i < (int)(sizeof(codecs) / sizeof(int)); i++ )
|
|
if ( codecs[i] & formats )
|
|
return codecs[i];
|
|
return 0;
|
|
}
|
|
|
|
static void iaxc_handle_connect(struct iax_event * e)
|
|
{
|
|
#ifdef USE_VIDEO
|
|
int video_format_capability;
|
|
int video_format_preferred;
|
|
#endif
|
|
int video_format = 0;
|
|
int format = 0;
|
|
int callno;
|
|
|
|
callno = iaxc_first_free_call();
|
|
|
|
if ( callno < 0 )
|
|
{
|
|
iaxci_usermsg(IAXC_STATUS,
|
|
"%i \n Incoming Call, but no appearances",
|
|
callno);
|
|
// XXX Reject this call!, or just ignore?
|
|
//iax_reject(e->session, "Too many calls, we're busy!");
|
|
iax_accept(e->session, audio_format_preferred & e->ies.capability);
|
|
iax_busy(e->session);
|
|
return;
|
|
}
|
|
|
|
/* negotiate codec */
|
|
/* first, try _their_ preferred format */
|
|
format = audio_format_capability & e->ies.format;
|
|
if ( !format )
|
|
{
|
|
/* then, try our preferred format */
|
|
format = audio_format_preferred & e->ies.capability;
|
|
}
|
|
|
|
if ( !format )
|
|
{
|
|
/* finally, see if we have one in common */
|
|
format = audio_format_capability & e->ies.capability;
|
|
|
|
/* now choose amongst these, if we got one */
|
|
if ( format )
|
|
{
|
|
format = iaxc_choose_codec(format);
|
|
}
|
|
}
|
|
|
|
if ( !format )
|
|
{
|
|
iax_reject(e->session, "Could not negotiate common codec");
|
|
return;
|
|
}
|
|
|
|
#ifdef USE_VIDEO
|
|
iaxc_video_format_get_cap(&video_format_preferred,
|
|
&video_format_capability);
|
|
|
|
/* first, see if they even want video */
|
|
video_format = (e->ies.format & IAXC_VIDEO_FORMAT_MASK);
|
|
|
|
if ( video_format )
|
|
{
|
|
/* next, try _their_ preferred format */
|
|
video_format &= video_format_capability;
|
|
|
|
if ( !video_format )
|
|
{
|
|
/* then, try our preferred format */
|
|
video_format = video_format_preferred &
|
|
(e->ies.capability & IAXC_VIDEO_FORMAT_MASK);
|
|
}
|
|
|
|
if ( !video_format )
|
|
{
|
|
/* finally, see if we have one in common */
|
|
video_format = video_format_capability &
|
|
(e->ies.capability & IAXC_VIDEO_FORMAT_MASK);
|
|
|
|
/* now choose amongst these, if we got one */
|
|
if ( video_format )
|
|
{
|
|
video_format = iaxc_choose_codec(video_format);
|
|
}
|
|
}
|
|
|
|
/* All video negotiations failed, then warn */
|
|
if ( !video_format )
|
|
{
|
|
iaxci_usermsg(IAXC_NOTICE,
|
|
"Notice: could not negotiate common video codec");
|
|
iaxci_usermsg(IAXC_NOTICE,
|
|
"Notice: switching to audio-only call");
|
|
}
|
|
}
|
|
#endif /* USE_VIDEO */
|
|
|
|
calls[callno].vformat = video_format;
|
|
calls[callno].format = format;
|
|
|
|
if ( e->ies.called_number )
|
|
strncpy(calls[callno].local, e->ies.called_number,
|
|
IAXC_EVENT_BUFSIZ);
|
|
else
|
|
strncpy(calls[callno].local, "unknown",
|
|
IAXC_EVENT_BUFSIZ);
|
|
|
|
if ( e->ies.called_context )
|
|
strncpy(calls[callno].local_context, e->ies.called_context,
|
|
IAXC_EVENT_BUFSIZ);
|
|
else
|
|
strncpy(calls[callno].local_context, "",
|
|
IAXC_EVENT_BUFSIZ);
|
|
|
|
if ( e->ies.calling_number )
|
|
strncpy(calls[callno].remote, e->ies.calling_number,
|
|
IAXC_EVENT_BUFSIZ);
|
|
else
|
|
strncpy(calls[callno].remote, "unknown",
|
|
IAXC_EVENT_BUFSIZ);
|
|
|
|
if ( e->ies.calling_name )
|
|
strncpy(calls[callno].remote_name, e->ies.calling_name,
|
|
IAXC_EVENT_BUFSIZ);
|
|
else
|
|
strncpy(calls[callno].remote_name, "unknown",
|
|
IAXC_EVENT_BUFSIZ);
|
|
|
|
iaxc_note_activity(callno);
|
|
iaxci_usermsg(IAXC_STATUS, "Call from (%s)", calls[callno].remote);
|
|
|
|
codec_destroy( callno );
|
|
|
|
calls[callno].session = e->session;
|
|
calls[callno].state = IAXC_CALL_STATE_ACTIVE|IAXC_CALL_STATE_RINGING;
|
|
|
|
iax_accept(calls[callno].session, format | video_format);
|
|
iax_ring_announce(calls[callno].session);
|
|
|
|
iaxci_do_state_callback(callno);
|
|
|
|
iaxci_usermsg(IAXC_STATUS, "Incoming call on line %d", callno);
|
|
}
|
|
|
|
static void service_network()
|
|
{
|
|
struct iax_event *e = 0;
|
|
int callNo;
|
|
struct iaxc_registration *reg;
|
|
|
|
while ( (e = iax_get_event(0)) )
|
|
{
|
|
#ifdef WIN32
|
|
iaxc_millisleep(0); //fd:
|
|
#endif
|
|
// first, see if this is an event for one of our calls.
|
|
callNo = iaxc_find_call_by_session(e->session);
|
|
if ( e->etype == IAX_EVENT_NULL )
|
|
{
|
|
// Should we do something here?
|
|
// Right now we do nothing, just go with the flow
|
|
// and let the event be deallocated.
|
|
} else if ( callNo >= 0 )
|
|
{
|
|
iaxc_handle_network_event(e, callNo);
|
|
} else if ( (reg = iaxc_find_registration_by_session(e->session)) != NULL )
|
|
{
|
|
iaxc_handle_regreply(e,reg);
|
|
} else if ( e->etype == IAX_EVENT_REGACK || e->etype == IAX_EVENT_REGREJ )
|
|
{
|
|
iaxci_usermsg(IAXC_ERROR, "Unexpected registration reply");
|
|
} else if ( e->etype == IAX_EVENT_REGREQ )
|
|
{
|
|
iaxci_usermsg(IAXC_ERROR,
|
|
"Registration requested by someone, but we don't understand!");
|
|
} else if ( e->etype == IAX_EVENT_CONNECT )
|
|
{
|
|
iaxc_handle_connect(e);
|
|
} else if ( e->etype == IAX_EVENT_TIMEOUT )
|
|
{
|
|
iaxci_usermsg(IAXC_STATUS,
|
|
"Timeout for a non-existant session. Dropping",
|
|
e->etype);
|
|
} else
|
|
{
|
|
iaxci_usermsg(IAXC_STATUS,
|
|
"Event (type %d) for a non-existant session. Dropping",
|
|
e->etype);
|
|
}
|
|
iax_event_free(e);
|
|
}
|
|
}
|
|
|
|
EXPORT int iaxc_audio_devices_get(struct iaxc_audio_device **devs, int *nDevs,
|
|
int *input, int *output, int *ring)
|
|
{
|
|
if ( test_mode )
|
|
return 0;
|
|
|
|
*devs = audio_driver.devices;
|
|
*nDevs = audio_driver.nDevices;
|
|
audio_driver.selected_devices(&audio_driver, input, output, ring);
|
|
return 0;
|
|
}
|
|
|
|
EXPORT int iaxc_audio_devices_set(int input, int output, int ring)
|
|
{
|
|
int ret;
|
|
|
|
if ( test_mode )
|
|
return 0;
|
|
|
|
get_iaxc_lock();
|
|
ret = audio_driver.select_devices(&audio_driver, input, output, ring);
|
|
put_iaxc_lock();
|
|
return ret;
|
|
}
|
|
|
|
EXPORT float iaxc_input_level_get()
|
|
{
|
|
if ( test_mode )
|
|
return 0;
|
|
|
|
return audio_driver.input_level_get(&audio_driver);
|
|
}
|
|
|
|
EXPORT float iaxc_output_level_get()
|
|
{
|
|
if ( test_mode )
|
|
return 0;
|
|
|
|
return audio_driver.output_level_get(&audio_driver);
|
|
}
|
|
|
|
EXPORT int iaxc_input_level_set(float level)
|
|
{
|
|
if ( test_mode )
|
|
return 0;
|
|
|
|
return audio_driver.input_level_set(&audio_driver, level);
|
|
}
|
|
|
|
EXPORT int iaxc_output_level_set(float level)
|
|
{
|
|
if ( test_mode )
|
|
return 0;
|
|
|
|
return audio_driver.output_level_set(&audio_driver, level);
|
|
}
|
|
|
|
EXPORT int iaxc_play_sound(struct iaxc_sound *s, int ring)
|
|
{
|
|
int ret;
|
|
|
|
if ( test_mode )
|
|
return 0;
|
|
|
|
get_iaxc_lock();
|
|
ret = audio_driver.play_sound(s,ring);
|
|
put_iaxc_lock();
|
|
return ret;
|
|
}
|
|
|
|
EXPORT int iaxc_stop_sound(int id)
|
|
{
|
|
int ret;
|
|
|
|
if ( test_mode )
|
|
return 0;
|
|
|
|
get_iaxc_lock();
|
|
ret = audio_driver.stop_sound(id);
|
|
put_iaxc_lock();
|
|
return ret;
|
|
}
|
|
|
|
EXPORT int iaxc_quelch(int callNo, int MOH)
|
|
{
|
|
struct iax_session *session = calls[callNo].session;
|
|
if ( !session )
|
|
return -1;
|
|
|
|
return iax_quelch_moh(session, MOH);
|
|
}
|
|
|
|
EXPORT int iaxc_unquelch(int call)
|
|
{
|
|
return iax_unquelch(calls[call].session);
|
|
}
|
|
|
|
EXPORT int iaxc_mic_boost_get( void )
|
|
{
|
|
return audio_driver.mic_boost_get( &audio_driver ) ;
|
|
}
|
|
|
|
EXPORT int iaxc_mic_boost_set( int enable )
|
|
{
|
|
return audio_driver.mic_boost_set( &audio_driver, enable ) ;
|
|
}
|
|
|
|
EXPORT char* iaxc_version(char * ver)
|
|
{
|
|
#ifndef LIBVER
|
|
#define LIBVER ""
|
|
#endif
|
|
strncpy(ver, LIBVER, IAXC_EVENT_BUFSIZ);
|
|
return ver;
|
|
}
|
|
|
|
EXPORT unsigned int iaxc_get_audio_prefs(void)
|
|
{
|
|
return audio_prefs;
|
|
}
|
|
|
|
EXPORT int iaxc_set_audio_prefs(unsigned int prefs)
|
|
{
|
|
unsigned int prefs_mask =
|
|
IAXC_AUDIO_PREF_RECV_LOCAL_RAW |
|
|
IAXC_AUDIO_PREF_RECV_LOCAL_ENCODED |
|
|
IAXC_AUDIO_PREF_RECV_REMOTE_RAW |
|
|
IAXC_AUDIO_PREF_RECV_REMOTE_ENCODED |
|
|
IAXC_AUDIO_PREF_SEND_DISABLE;
|
|
|
|
if ( prefs & ~prefs_mask )
|
|
return -1;
|
|
|
|
audio_prefs = prefs;
|
|
return 0;
|
|
}
|
|
|
|
EXPORT void iaxc_set_test_mode(int tm)
|
|
{
|
|
test_mode = tm;
|
|
}
|
|
|
|
EXPORT int iaxc_push_audio(void *data, unsigned int size, unsigned int samples)
|
|
{
|
|
struct iaxc_call *call;
|
|
|
|
if ( selected_call < 0 )
|
|
return -1;
|
|
|
|
call = &calls[selected_call];
|
|
|
|
if ( audio_prefs & IAXC_AUDIO_PREF_SEND_DISABLE )
|
|
return 0;
|
|
|
|
//fprintf(stderr, "iaxc_push_audio: sending audio size %d\n", size);
|
|
|
|
if ( iax_send_voice(call->session, call->format, data, size, samples) == -1 )
|
|
{
|
|
fprintf(stderr, "iaxc_push_audio: failed to send audio frame of size %d on call %d\n", size, selected_call);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void iaxc_debug_iax_set(int enable)
|
|
{
|
|
#ifdef DEBUG_SUPPORT
|
|
if (enable)
|
|
iax_enable_debug();
|
|
else
|
|
iax_disable_debug();
|
|
#endif
|
|
}
|
|
|