/* * iaxclient: a cross-platform IAX softphone library * * Copyrights: * Copyright (C) 2003 HorizonLive.com, (c) 2004, Horizon Wimba, Inc. * * Contributors: * Steve Kann * Michael Van Donselaar * Shawn Lawrence * * * This program is free software, distributed under the terms of * the GNU Lesser (Library) General Public License */ #include "iaxclient_lib.h" #include "jitterbuf.h" #if defined(__STDC__) || defined(_MSC_VER) #include #else #include #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" 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; struct iaxc_audio_driver audio; static int iAudioType; int audio_format_capability; int audio_format_preferred; void * post_event_handle = NULL; int post_event_id = 0; static int minimum_outgoing_framesize = 160; /* 20ms */ static MUTEX iaxc_lock; int netfd; int port; int c, i; int iaxc_audio_output_mode = 0; // Normal static int selected_call; // XXX to be protected by mutex? static struct iaxc_call* calls; static int nCalls; // number of calls for this library session struct timeval lastouttm; static void iaxc_service_network(); static int service_audio(); /* external global networking replacements */ static iaxc_sendto_t iaxc_sendto = sendto; static iaxc_recvfrom_t iaxc_recvfrom = recvfrom; static THREAD procThread; #ifdef WIN32 static THREADID procThreadID; #endif /* QuitFlag: 0: Running 1: Should Quit, -1: Not Running */ static int procThreadQuitFlag = -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; // Record whether lock is held, so we know whether to send events now // or queue them until the lock is released. static int iaxc_locked = 0; // Lock the library static void get_iaxc_lock() { MUTEXLOCK(&iaxc_lock); iaxc_locked = 1; } // Unlock the library and post any events that were queued in the meantime static void put_iaxc_lock() { iaxc_event *prev, *event = event_queue; event_queue = NULL; iaxc_locked = 0; MUTEXUNLOCK(&iaxc_lock); while (event) { iaxc_post_event(*event); prev = event; event = event->next; free(prev); } } EXPORT void iaxc_set_silence_threshold(double thr) { iaxc_silence_threshold = thr; iaxc_set_speex_filters(); } EXPORT void iaxc_set_audio_output(int mode) { iaxc_audio_output_mode = mode; } EXPORT int iaxc_get_filters(void) { return iaxc_filters; } EXPORT void iaxc_set_filters(int filters) { iaxc_filters = filters; iaxc_set_speex_filters(); } long iaxc_usecdiff( struct timeval *timeA, struct timeval *timeB ){ long secs = timeA->tv_sec - timeB->tv_sec; long usecs = secs * 1000000; usecs += (timeA->tv_usec - timeB->tv_usec); return usecs; } 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 = 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(char *message) { fprintf(stderr, "IAXCLIENT: "); fprintf(stderr, message); fprintf(stderr, "\n"); } // Post Events back to clients void iaxc_post_event(iaxc_event e) { if(e.type == 0) { iaxc_usermsg(IAXC_ERROR, "Error: something posted to us an invalid event"); return; } // If the library is locked then just queue the event to be posted // once the lock is released. if (iaxc_locked) { iaxc_event **tail = &event_queue; e.next = NULL; while (*tail) tail = &((*tail)->next); *tail = malloc(sizeof(iaxc_event)); memcpy(*tail, &e, sizeof(iaxc_event)); return; } // Library is not locked, so process event now. if(iaxc_event_callback) { int rv; rv = iaxc_event_callback(e); 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 iaxc_usermsg(int type, const char *fmt, ...) { va_list args; iaxc_event e; e.type=IAXC_EVENT_TEXT; e.ev.text.type=type; va_start(args, fmt); vsnprintf(e.ev.text.message, IAXC_EVENT_BUFSIZ, fmt, args); va_end(args); iaxc_post_event(e); } void iaxc_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; iaxc_post_event(e); } void iaxc_do_state_callback(int callNo) { iaxc_event e; if(callNo < 0 || callNo >= nCalls) 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; 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); iaxc_post_event(e); } void iaxc_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; iaxc_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= nCalls) { iaxc_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; iaxc_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) iaxc_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; } 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); iaxc_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); iaxc_usermsg(IAXC_NOTICE, buf); } static void jb_dbgf(const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); } static void setup_jb_output() { //jb_setoutput(jb_errf, jb_warnf, jb_dbgf); jb_setoutput(jb_errf, jb_warnf, NULL); } // Parameters: // audType - Define whether audio is handled by library or externally EXPORT int iaxc_initialize(int audType, int inCalls) { int i; /* os-specific initializations: init gettimeofday fake stuff in * Win32, etc) */ os_init(); setup_jb_output(); MUTEXINIT(&iaxc_lock); if(iaxc_sendto == sendto) { if ( (port = iax_init(0) < 0)) { iaxc_usermsg(IAXC_ERROR, "Fatal error: failed to initialize iax with port %d", port); return -1; } netfd = iax_get_fd(); } else { iax_set_networking(iaxc_sendto, iaxc_recvfrom); } nCalls = inCalls; /* initialize calls */ if(nCalls <= 0) nCalls = 1; /* 0 == Default? */ /* calloc zeroes for us */ calls = calloc(sizeof(struct iaxc_call), nCalls); if(!calls) { iaxc_usermsg(IAXC_ERROR, "Fatal error: can't allocate memory"); return -1; } iAudioType = audType; selected_call = 0; for(i=0; inext) { if(iaxc_usecdiff(&now, &cur->last) > cur->refresh ) { //fprintf(stderr, "refreshing registration %s:%s@%s\n", cur->user, cur->pass, cur->host); if( cur->session != NULL ) { iax_destroy( cur->session ); } cur->session = iax_session_new(); if(!cur->session) { iaxc_usermsg(IAXC_ERROR, "Can't make new registration session"); return; } iax_register(cur->session, cur->host, cur->user, cur->pass, 60); cur->last = now; } } } EXPORT void iaxc_process_calls(void) { #ifdef USE_WIN_AUDIO win_flush_audio_output_buffers(); if (iAudioType == AUDIO_INTERNAL) { win_prepare_audio_buffers(); } #endif get_iaxc_lock(); iaxc_service_network(); service_audio(); iaxc_refresh_registrations(); // XXX move to service_audio or something -- set call properly! #ifdef IAXC_VIDEO iaxc_send_video(NULL); #endif put_iaxc_lock(); } THREADFUNCDECL(iaxc_processor) { THREADFUNCRET(ret); /* Increase Priority */ iaxc_prioboostbegin(); while(1) { iaxc_process_calls(); iaxc_millisleep(5); if(procThreadQuitFlag) break; } iaxc_prioboostend(); return ret; } EXPORT int iaxc_start_processing_thread() { procThreadQuitFlag = 0; if( THREADCREATE(iaxc_processor, NULL, procThread, procThreadID) == THREADCREATE_ERROR) return -1; return 0; } EXPORT int iaxc_stop_processing_thread() { if(procThreadQuitFlag >= 0) { procThreadQuitFlag = 1; THREADJOIN(procThread); } procThreadQuitFlag = -1; return 0; } static int service_audio() { // we do this here to avoid looking at calls[-1] if(selected_call < 0) { static int i=0; if(i++ % 50 == 0) iaxc_do_levels_callback(-99,-99); // make sure audio is stopped audio.stop(&audio); return 0; } /* send audio only if incoming call answered, or outgoing call * selected. */ if( (calls[selected_call].state & IAXC_CALL_STATE_OUTGOING) || (calls[selected_call].state & IAXC_CALL_STATE_COMPLETE)) { short buf[1024]; // make sure audio is running if(audio.start(&audio)) { iaxc_usermsg(IAXC_ERROR, "Can't start audio"); } for(;;) { int toRead; int cmin; /* find mimumum frame size */ toRead = minimum_outgoing_framesize; /* use codec minimum if higher */ if(calls[selected_call].encoder) cmin = calls[selected_call].encoder->minimum_frame_size; else cmin = 1; if(cmin > toRead) toRead = cmin; /* round up to next multiple */ if(toRead % cmin) toRead += cmin - (toRead % cmin); if(toRead > sizeof(buf)/sizeof(short)) { fprintf(stderr, "internal error: toRead > sizeof(buf)\n"); exit(1); } if(audio.input(&audio,buf,&toRead)) { iaxc_usermsg(IAXC_ERROR, "ERROR reading audio\n"); break; } if(!toRead) break; /* frame not available */ /* currently, pa will always give us 0 or what we asked * for samples */ send_encoded_audio(&calls[selected_call], buf, calls[selected_call].format, toRead); } } else { static int i=0; if(i++ % 50 == 0) iaxc_do_levels_callback(-99,-99); // make sure audio is stopped audio.stop(&audio); } return 0; } /* handle IAX text events */ static void handle_text_event(struct iax_event *e, int callNo) { iaxc_event ev; if(callNo < 0) return; ev.type=IAXC_EVENT_TEXT; ev.ev.text.type=IAXC_TEXT_TYPE_IAX; ev.ev.text.callNo = callNo; strncpy(ev.ev.text.message, (char *) e->data, IAXC_EVENT_BUFSIZ); iaxc_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; } iaxc_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)) iaxc_post_event(ev); } static void handle_audio_event(struct iax_event *e, int callNo) { int total_consumed = 0; int cur; short fr[1024]; int samples; int bufsize = sizeof(fr)/sizeof(short); struct iaxc_call *call; int mainbuf_delta; if(callNo < 0) return; call = &calls[callNo]; if(callNo != selected_call) { /* drop audio for unselected call? */ return; } samples = bufsize; do { mainbuf_delta = bufsize - samples; cur = decode_audio(call, fr, e->data+total_consumed,e->datalen-total_consumed, call->format, &samples); if(cur < 0) { iaxc_usermsg(IAXC_STATUS, "Bad or incomplete voice packet. Unable to decode. dropping"); return; } total_consumed += cur; if(iaxc_audio_output_mode != 0) continue; audio.output(&audio,fr,bufsize - samples - mainbuf_delta); } while(total_consumed < e->datalen); } 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_HANGUP: iaxc_usermsg(IAXC_STATUS, "Call disconnected by remote"); // XXX does the session go away now? iaxc_clear_call(callNo); break; case IAX_EVENT_REJECT: iaxc_usermsg(IAXC_STATUS, "Call rejected by remote"); iaxc_clear_call(callNo); break; case IAX_EVENT_ACCEPT: calls[callNo].format = e->ies.format; //fprintf(stderr, "outgoing call remote accepted, format=%d\n", e->ies.format); iaxc_usermsg(IAXC_STATUS,"Call %d accepted", callNo); // issue_prompt(f); break; case IAX_EVENT_ANSWER: calls[callNo].state &= ~IAXC_CALL_STATE_RINGING; calls[callNo].state |= IAXC_CALL_STATE_COMPLETE; iaxc_do_state_callback(callNo); iaxc_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; iaxc_do_state_callback(callNo); break; case IAX_EVENT_VOICE: handle_audio_event(e, callNo); if (calls[callNo].state & IAXC_CALL_STATE_RINGING) { calls[callNo].state &= ~IAXC_CALL_STATE_RINGING; iaxc_do_state_callback(callNo); iaxc_usermsg(IAXC_STATUS,"Call %d progress", callNo); } break; case IAX_EVENT_TEXT: handle_text_event(e, callNo); break; case IAX_EVENT_RINGA: calls[callNo].state |= IAXC_CALL_STATE_RINGING; iaxc_do_state_callback(callNo); iaxc_usermsg(IAXC_STATUS,"Call %d ringing", callNo); break; case IAX_EVENT_PONG: /* we got a pong */ //fprintf(stderr, "**********GOT A PONG!\n"); 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"); iaxc_usermsg(IAXC_STATUS, "Call %d timed out.", callNo); iaxc_clear_call(callNo); break; case IAX_EVENT_TRANSFER: calls[callNo].state |= IAXC_CALL_STATE_TRANSFER; iaxc_do_state_callback(callNo); iaxc_usermsg(IAXC_STATUS,"Call %d transfer released", callNo); break; default: iaxc_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(char *user, char *pass, char *host) { struct iaxc_registration *newreg; newreg = malloc(sizeof (struct iaxc_registration)); if(!newreg) { iaxc_usermsg(IAXC_ERROR, "Can't make new registration"); return -1; } get_iaxc_lock(); newreg->session = iax_session_new(); if(!newreg->session) { iaxc_usermsg(IAXC_ERROR, "Can't make new registration session"); put_iaxc_lock(); return -1; } gettimeofday(&newreg->last,NULL); newreg->refresh = 60*1000*1000; /* 60 seconds, in usecs */ strncpy(newreg->host, host, 256); strncpy(newreg->user, user, 256); strncpy(newreg->pass, pass, 256); /* send out the initial registration timeout 300 seconds */ iax_register(newreg->session, host, user, pass, 300); /* 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; } } EXPORT void iaxc_call(char *num) { int callNo; 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) { iaxc_usermsg(IAXC_STATUS, "No free call appearances"); goto iaxc_call_bail; } newsession = iax_session_new(); if(!newsession) { iaxc_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); } 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; iax_call(calls[callNo].session, calls[callNo].callerid_number, calls[callNo].callerid_name, num, NULL, 0, audio_format_preferred, audio_format_capability); // does state stuff also iaxc_select_call(callNo); iaxc_call_bail: put_iaxc_lock(); } EXPORT void iaxc_answer_call(int callNo) { if(callNo < 0) return; //fprintf(stderr, "iaxc answering call %d\n", callNo); calls[callNo].state |= IAXC_CALL_STATE_COMPLETE; calls[callNo].state &= ~IAXC_CALL_STATE_RINGING; iax_answer(calls[callNo].session); iaxc_do_state_callback(callNo); } EXPORT void iaxc_blind_transfer_call(int callNo, char *DestExtn) { iax_transfer(calls[callNo].session, DestExtn); } 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"); iaxc_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= 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) { 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(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_url(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;inext) if (reg->session == session) break; return reg; } static void iaxc_handle_regreply(struct iax_event *e, struct iaxc_registration *reg) { iaxc_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, }; for(i=0;isession); 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 )) { iaxc_usermsg(IAXC_ERROR, "Unexpected registration reply"); } else if(e->etype == IAX_EVENT_REGREQ ) { iaxc_usermsg(IAXC_ERROR, "Registration requested by someone, but we don't understand!"); } else if(e->etype == IAX_EVENT_CONNECT) { int format = 0; callNo = iaxc_first_free_call(); if(callNo < 0) { iaxc_usermsg(IAXC_STATUS, "Incoming Call, but no appearances"); // XXX Reject this call!, or just ignore? iax_reject(e->session, "Too many calls, we're busy!"); goto bail; } /* 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"); goto bail; } 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); iaxc_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); iax_ring_announce(calls[callNo].session); iaxc_do_state_callback(callNo); iaxc_usermsg(IAXC_STATUS, "Incoming call on line %d", callNo); } else if (e->etype == IAX_EVENT_TIMEOUT) { iaxc_usermsg(IAXC_STATUS, "Timeout for a non-existant session. Dropping", e->etype); } else { iaxc_usermsg(IAXC_STATUS, "Event (type %d) for a non-existant session. Dropping", e->etype); } bail: iax_event_free(e); } } EXPORT int iaxc_audio_devices_get(struct iaxc_audio_device **devs, int *nDevs, int *input, int *output, int *ring) { *devs = audio.devices; *nDevs = audio.nDevices; audio.selected_devices(&audio,input,output,ring); return 0; } EXPORT int iaxc_audio_devices_set(int input, int output, int ring) { int ret = 0; get_iaxc_lock(); ret = audio.select_devices(&audio, input, output, ring); put_iaxc_lock(); return ret; } EXPORT double iaxc_input_level_get() { return audio.input_level_get(&audio); } EXPORT double iaxc_output_level_get() { return audio.output_level_get(&audio); } EXPORT int iaxc_input_level_set(double level) { return audio.input_level_set(&audio, level); } EXPORT int iaxc_output_level_set(double level) { return audio.output_level_set(&audio, level); } EXPORT int iaxc_play_sound(struct iaxc_sound *s, int ring) { int ret = 0; get_iaxc_lock(); ret = audio.play_sound(s,ring); put_iaxc_lock(); return ret; } EXPORT int iaxc_stop_sound(int id) { int ret = 0; get_iaxc_lock(); ret = audio.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.mic_boost_get( &audio ) ; } EXPORT int iaxc_mic_boost_set( int enable ) { return audio.mic_boost_set( &audio, enable ) ; } EXPORT char* iaxc_version(char* ver) { strncpy(ver, LIBVER, IAXC_EVENT_BUFSIZ); return ver; }