/* * Miniphone: A simple, command line telephone * * IAX Support for talking to Asterisk and other Gnophone clients * * Copyright (C) 1999, Linux Support Services, Inc. * * Mark Spencer * * This program is free software, distributed under the terms of * the GNU General Public License */ /* #define PRINTCHUCK /* enable this to indicate chucked incomming packets */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gsm.h" #include "iax-client.h" #include "frame.h" #include "miniphone.h" struct peer { int time; gsm gsmin; gsm gsmout; struct iax_session *session; struct peer *next; }; static struct peer *peers; static int answered_call = 0; /* stuff for wave audio device */ HWAVEOUT wout; HWAVEIN win; typedef struct whout { WAVEHDR w; short data[160]; struct whout *next; } WHOUT; WHOUT *outqueue = NULL; /* parameters for audio in */ #define NWHIN 8 /* number of input buffer entries */ /* NOTE the OUT_INTERVAL parameter *SHOULD* be more around 18 to 20 or so, since the packets should be spaced by 20 milliseconds. However, in practice, especially in Windoze-95, setting it that high caused underruns. 10 is just ever so slightly agressive, and the receiver has to chuck a packet every now and then. Thats about the way it should be to be happy. */ #define OUT_INTERVAL 10 /* number of ms to wait before sending more data to peer */ /* parameters for audio out */ #define OUT_DEPTH 12 /* number of outbut buffer entries */ #define OUT_PAUSE_THRESHOLD 2 /* number of active entries needed to start output (for smoothing) */ /* audio input buffer headers */ WAVEHDR whin[NWHIN]; /* audio input buffers */ char bufin[NWHIN][320]; /* initialize the sequence variables for the audio in stuff */ unsigned int whinserial = 1,nextwhin = 1; static struct peer *find_peer(struct iax_session *); static void parse_args(FILE *, unsigned char *); void do_iax_event(FILE *); void call(FILE *, char *); void answer_call(void); void reject_call(void); static void handle_event(FILE *, struct iax_event *e, struct peer *p); void parse_cmd(FILE *, int, char **); void issue_prompt(FILE *); void dump_array(FILE *, char **); static char *help[] = { "Welcome to the miniphone telephony client, the commands are as follows:\n", "Help\t\t-\tDisplays this screen.", "Call \t-\tDials the number supplied.", "Answer\t\t-\tAnswers an Inbound call.", "Reject\t\t-\tRejects an Inbound call.", "Dump\t\t-\tDumps (disconnects) the current call.", "Dtmf \t-\tSends specified DTMF digit.", "Status\t\t-\tLists the current sessions and their current status.", "Quit\t\t-\tShuts down the client.", "", 0 }; static struct peer *most_recent_answer; static struct iax_session *newcall = 0; /* holder of the time, relative to startup in system ticks. See our gettimeofday() implementation */ time_t startuptime; /* routine called at exit to shutdown audio I/O and close nicely. NOTE: If all this isnt done, the system doesnt not handle this cleanly and has to be rebooted. What a pile of doo doo!! */ void killem(void) { waveInStop(win); waveInReset(win); waveInClose(win); waveOutReset(wout); waveOutClose(wout); WSACleanup(); /* dont forget socket stuff too */ return; } /* Win-doze doenst have gettimeofday(). This sux. So, what we did is provide some gettimeofday-like functionality that works for our purposes. In the main(), we take a sample of the system tick counter (into startuptime). This function returns the relative time since program startup, more or less, which is certainly good enough for our purposes. */ void gettimeofday(struct timeval *tv, struct timezone *tz) { long l = startuptime + GetTickCount(); tv->tv_sec = l / 1000; tv->tv_usec = (l % 1000) * 1000; return; } static struct peer *find_peer(struct iax_session *session) { struct peer *cur = peers; while(cur) { if (cur->session == session) return cur; cur = cur->next; } return NULL; } void parse_args(FILE *f, unsigned char *cmd) { static char *argv[MAXARGS]; unsigned char *parse = cmd; int argc = 0, t = 0; // Don't mess with anything that doesn't exist... if(!*parse) return; memset(argv, 0, sizeof(argv)); while(*parse) { if(*parse < 33 || *parse > 128) { *parse = 0, t++; if(t > MAXARG) { fprintf(f, "Warning: Argument exceeds maximum argument size, command ignored!\n"); return; } } else if(t || !argc) { if(argc == MAXARGS) { fprintf(f, "Warning: Command ignored, too many arguments\n"); return; } argv[argc++] = parse; t = 0; } parse++; } if(argc) parse_cmd(f, argc, argv); } /* handle all network requests, and a pending scheduled event, if any */ void service_network(int netfd, FILE *f) { fd_set readfd; struct timeval dumbtimer; /* set up a timer that falls-through */ dumbtimer.tv_sec = 0; dumbtimer.tv_usec = 0; for(;;) /* suck everything outa network stuff */ { FD_ZERO(&readfd); FD_SET(netfd, &readfd); if (select(netfd + 1, &readfd, 0, 0, &dumbtimer) > 0) { if (FD_ISSET(netfd,&readfd)) { do_iax_event(f); (void) iax_time_to_next_event(); } else break; } else break; } do_iax_event(f); /* do pending event if any */ } int main(int argc, char *argv[]) { int port; int netfd; int c, i; FILE *f; char rcmd[RBUFSIZE]; gsm_frame fo; WSADATA foop; time_t t; WAVEFORMATEX wf; WHOUT *wh,*wh1,*wh2; unsigned long lastouttick = 0; /* get time of day in milliseconds, offset by tick count (see our gettimeofday() implementation) */ time(&t); startuptime = ((t % 86400) * 1000) - GetTickCount(); f = stdout; _dup2(fileno(stdout),fileno(stderr)); /* start up the windoze-socket layer stuff */ if (WSAStartup(0x0101,&foop)) { fprintf(stderr,"Fatal error: Falied to startup windows sockets\n"); return -1; } /* setup the format for opening audio channels */ wf.wFormatTag = WAVE_FORMAT_PCM; wf.nChannels = 1; wf.nSamplesPerSec = 8000; wf.nAvgBytesPerSec = 16000; wf.nBlockAlign = 2; wf.wBitsPerSample = 16; wf.cbSize = 0; /* open the audio out channel */ if (waveOutOpen(&wout,0,&wf,0,0,CALLBACK_NULL) != MMSYSERR_NOERROR) { fprintf(stderr,"Fatal Error: Failed to open wave output device\n"); return -1; } /* open the audio in channel */ if (waveInOpen(&win,0,&wf,0,0,CALLBACK_NULL) != MMSYSERR_NOERROR) { fprintf(stderr,"Fatal Error: Failed to open wave input device\n"); waveOutReset(wout); waveOutClose(wout); return -1; } /* activate the exit handler */ atexit(killem); /* initialize the audio in buffer structures */ memset(&whin,0,sizeof(whin)); if ( (port = iax_init(0) < 0)) { fprintf(stderr, "Fatal error: failed to initialize iax with port %d\n", port); return -1; } iax_set_formats(AST_FORMAT_GSM); netfd = iax_get_fd(); fprintf(f, "Text Based Telephony Client.\n\n"); issue_prompt(f); /* main tight loop */ while(1) { /* service the network stuff */ service_network(netfd,f); if (outqueue) /* if stuff in audio output queue, free it up if its available */ { /* go through audio output queue */ for(wh = outqueue,wh1 = wh2 = NULL,i = 0; wh != NULL; wh = wh->next) { service_network(netfd,f); /* service network here for better performance */ /* if last one was removed from queue, zot it here */ if (i && wh1) { free(wh1); wh1 = wh2; } i = 0; /* reset "last one removed" flag */ if (wh->w.dwFlags & WHDR_DONE) /* if this one is done */ { /* prepare audio header */ if ((c = waveOutUnprepareHeader(wout,&wh->w,sizeof(WAVEHDR))) != MMSYSERR_NOERROR) { fprintf(stderr,"Cannot unprepare audio out header, error %d\n",c); exit(255); } if (wh1 != NULL) /* if there was a last one */ { wh1->next = wh->next; } if (outqueue == wh) /* is first one, so set outqueue to next one */ { outqueue = wh->next; } i = 1; /* set 'to free' flag */ } wh2 = wh1; /* save old,old wh pointer */ wh1 = wh; /* save the old wh pointer */ } } /* go through all audio in buffers, and prepare and queue ones that are currently idle */ for(i = 0; i < NWHIN; i++) { service_network(netfd,f); /* service network stuff here for better performance */ if (!(whin[i].dwFlags & WHDR_PREPARED)) /* if not prepared, do so */ { /* setup this input buffer header */ memset(&whin[i],0,sizeof(WAVEHDR)); whin[i].lpData = bufin[i]; whin[i].dwBufferLength = 320; whin[i].dwUser = whinserial++; /* set 'user data' to current serial number */ /* prepare the buffer */ if (waveInPrepareHeader(win,&whin[i],sizeof(WAVEHDR))) { fprintf(stderr,"Unable to prepare header for input\n"); return -1; } /* add it to device (queue) */ if (waveInAddBuffer(win,&whin[i],sizeof(WAVEHDR))) { fprintf(stderr,"Unable to prepare header for input\n"); return -1; } } waveInStart(win); /* start it (if not already started) */ } /* if key pressed, do command stuff */ if(_kbhit()) { if ( ( fgets(&*rcmd, 256, stdin))) { rcmd[strlen(rcmd)-1] = 0; parse_args(f, &*rcmd); } else fprintf(f, "Fatal error: failed to read data!\n"); issue_prompt(f); } /* do audio input stuff for buffers that have received data from audio in device already. Must do them in serial number order (the order in which they were originally queued). */ if(answered_call) /* send audio only if call answered */ { for(;;) /* loop until all are found */ { for(i = 0; i < NWHIN; i++) /* find an available one that's the one we are looking for */ { service_network(netfd,f); /* service network here for better performance */ /* if not time to send any more, dont */ if (GetTickCount() < (lastouttick + OUT_INTERVAL)) { i = NWHIN; /* set to value that WILL exit loop */ break; } if ((whin[i].dwUser == nextwhin) && (whin[i].dwFlags & WHDR_DONE)) { /* if audio is ready */ /* must have read exactly 320 bytes */ if (whin[i].dwBytesRecorded != whin[i].dwBufferLength) { fprintf(stderr,"Short audio read, got %d bytes, expected %d bytes\n", whin[i].dwBytesRecorded, whin[i].dwBufferLength); return -1; } if(!most_recent_answer->gsmout) most_recent_answer->gsmout = gsm_create(); service_network(netfd,f); /* service network here for better performance */ /* encode the audio from the buffer into GSM format */ gsm_encode(most_recent_answer->gsmout, (short *) ((char *) whin[i].lpData), fo); if(iax_send_voice(most_recent_answer->session, AST_FORMAT_GSM, (char *)fo, sizeof(gsm_frame)) == -1) puts("Failed to send voice!"); lastouttick = GetTickCount(); /* save time of last output */ /* unprepare (free) the header */ waveInUnprepareHeader(win,&whin[i],sizeof(WAVEHDR)); /* initialize the buffer */ memset(&whin[i],0,sizeof(WAVEHDR)); /* bump the serial number to look for the next time */ nextwhin++; /* exit the loop so that we can start at lowest buffer again */ break; } } if (i >= NWHIN) break; /* if all found, get out of loop */ } } } return 0; } void do_iax_event(FILE *f) { int sessions = 0; struct iax_event *e = 0; struct peer *peer; while ( (e = iax_get_event(0))) { peer = find_peer(e->session); if(peer) { handle_event(f, e, peer); } else { if(e->etype != IAX_EVENT_CONNECT) { fprintf(stderr, "Huh? This is an event for a non-existant session?\n"); } sessions++; if(sessions >= MAX_SESSIONS) { fprintf(f, "Missed a call... too many sessions open.\n"); } if(e->event.connect.callerid && e->event.connect.dnid) fprintf(f, "Call from '%s' for '%s'", e->event.connect.callerid, e->event.connect.dnid); else if(e->event.connect.dnid) { fprintf(f, "Call from '%s'", e->event.connect.dnid); } else if(e->event.connect.callerid) { fprintf(f, "Call from '%s'", e->event.connect.callerid); } else printf("Call from"); fprintf(f, " (%s)\n", inet_ntoa(iax_get_peer_addr(e->session).sin_addr)); if(most_recent_answer) { fprintf(f, "Incoming call ignored, there's already a call waiting for answer... \ please accept or reject first\n"); iax_reject(e->session, "Too many calls, we're busy!"); } else { if ( !(peer = malloc(sizeof(struct peer)))) { fprintf(f, "Warning: Unable to allocate memory!\n"); return; } peer->time = time(0); peer->session = e->session; peer->gsmin = 0; peer->gsmout = 0; peer->next = peers; peers = peer; iax_accept(peer->session); iax_ring_announce(peer->session); most_recent_answer = peer; fprintf(f, "Incoming call!\n"); } iax_event_free(e); issue_prompt(f); } } } void call(FILE *f, char *num) { struct peer *peer; if(!newcall) newcall = iax_session_new(); else { fprintf(f, "Already attempting to call somewhere, please cancel first!\n"); return; } if ( !(peer = malloc(sizeof(struct peer)))) { fprintf(f, "Warning: Unable to allocate memory!\n"); return; } peer->time = time(0); peer->session = newcall; peer->gsmin = 0; peer->gsmout = 0; peer->next = peers; peers = peer; most_recent_answer = peer; iax_call(peer->session, num, 10); } void answer_call(void) { if(most_recent_answer) iax_answer(most_recent_answer->session); printf("Answering call!\n"); answered_call = 1; } void dump_call(void) { if(most_recent_answer) { iax_hangup(most_recent_answer->session,""); free(most_recent_answer); } printf("Dumping call!\n"); answered_call = 0; most_recent_answer = 0; answered_call = 0; peers = 0; newcall = 0; } void reject_call(void) { iax_reject(most_recent_answer->session, "Call rejected manually."); most_recent_answer = 0; } void handle_event(FILE *f, struct iax_event *e, struct peer *p) { int len,n; WHOUT *wh,*wh1; short fr[160]; static paused_xmit = 0; switch(e->etype) { case IAX_EVENT_HANGUP: iax_hangup(most_recent_answer->session, "Byeee!"); fprintf(f, "Call disconnected by peer\n"); free(most_recent_answer); most_recent_answer = 0; answered_call = 0; peers = 0; newcall = 0; break; case IAX_EVENT_REJECT: fprintf(f, "Authentication was rejected\n"); break; case IAX_EVENT_ACCEPT: fprintf(f, "Waiting for answer... RING RING\n"); issue_prompt(f); break; case IAX_EVENT_ANSWER: answer_call(); break; case IAX_EVENT_VOICE: switch(e->event.voice.format) { case AST_FORMAT_GSM: if(e->event.voice.datalen % 33) { fprintf(stderr, "Weird gsm frame, not a multiple of 33.\n"); break; } if (!p->gsmin) p->gsmin = gsm_create(); len = 0; while(len < e->event.voice.datalen) { if(gsm_decode(p->gsmin, (char *) e->event.voice.data + len, fr)) { fprintf(stderr, "Bad GSM data\n"); break; } else { /* its an audio packet to be output to user */ /* get count of pending items in audio output queue */ n = 0; if (outqueue) { /* determine number of pending out queue items */ for(wh = outqueue; wh != NULL; wh = wh->next) { if (!(wh->w.dwFlags & WHDR_DONE)) n++; } } /* if not too many, send to user, otherwise chuck packet */ if (n <= OUT_DEPTH) /* if not to chuck packet */ { /* malloc the memory for the queue item */ wh = (WHOUT *) malloc(sizeof(WHOUT)); if (wh == (WHOUT *) NULL) /* if error, bail */ { fprintf(stderr,"Outa memory!!!!\n"); exit(255); } /* initialize the queue entry */ memset(wh,0,sizeof(WHOUT)); /* copy the PCM data from the gsm conversion buffer */ memcpy((char *)wh->data,(char *)fr,sizeof(fr)); /* set parameters for data */ wh->w.lpData = (char *) wh->data; wh->w.dwBufferLength = 320; /* prepare buffer for output */ if (waveOutPrepareHeader(wout,&wh->w,sizeof(WAVEHDR))) { fprintf(stderr,"Cannot prepare header for audio out\n"); exit(255); } /* if not currently transmitting, hold off a couple of packets for smooth sounding output */ if ((!n) && (!paused_xmit)) { /* pause output (before starting) */ waveOutPause(wout); /* indicate as such */ paused_xmit = 1; } /* queue packet for output on audio device */ if (waveOutWrite(wout,&wh->w,sizeof(WAVEHDR))) { fprintf(stderr,"Cannot output to wave output device\n"); exit(255); } /* if we are paused, and we have enough packets, start audio */ if ((n > OUT_PAUSE_THRESHOLD) && paused_xmit) { /* start the output */ waveOutRestart(wout); /* indicate as such */ paused_xmit = 0; } /* insert it onto tail of outqueue */ if (outqueue == NULL) /* if empty queue */ outqueue = wh; /* point queue to new entry */ else /* otherwise is non-empty queue */ { wh1 = outqueue; while(wh1->next) wh1 = wh1->next; /* find last entry in queue */ wh1->next = wh; /* point it to new entry */ } } #ifdef PRINTCHUCK else printf("Chucking packet!!\n"); #endif } len += 33; } break; default : fprintf(f, "Don't know how to handle that format %d\n", e->event.voice.format); } break; case IAX_EVENT_RINGA: break; default: fprintf(f, "Unknown event: %d\n", e->etype); break; } } void parse_cmd(FILE *f, int argc, char **argv) { _strupr(argv[0]); if(!strcmp(argv[0], "HELP")) { if(argc == 1) dump_array(f, help); else if(argc == 2) { if(!strcmp(argv[1], "HELP")) fprintf(f, "Help \t-\tDisplays general help or specific help on command if supplied an arguement\n"); else if(!strcmp(argv[1], "QUIT")) fprintf(f, "Quit\t\t-\tShuts down the miniphone\n"); else fprintf(f, "No help available on %s\n", argv[1]); } else { fprintf(f, "Too many arguements for command help.\n"); } } else if(!strcmp(argv[0], "STATUS")) { if(argc == 1) { int c = 0; struct peer *peerptr = peers; if(!peerptr) fprintf(f, "No session matches found.\n"); else while(peerptr) { fprintf(f, "Listing sessions:\n\n"); fprintf(f, "Session %d\n", ++c); fprintf(f, "Session existed for %d seconds\n", (int)time(0)-peerptr->time); if(answered_call) fprintf(f, "Call answered.\n"); else fprintf(f, "Call ringing.\n"); peerptr = peerptr->next; } } else fprintf(f, "Too many arguments for command status.\n"); } else if(!strcmp(argv[0], "ANSWER")) { if(argc > 1) fprintf(f, "Too many arguements for command answer\n"); else answer_call(); } else if(!strcmp(argv[0], "REJECT")) { if(argc > 1) fprintf(f, "Too many arguements for command reject\n"); else { fprintf(f, "Rejecting current phone call.\n"); reject_call(); } } else if(!strcmp(argv[0], "CALL")) { if(argc > 2) fprintf(f, "Too many arguements for command call\n"); else { call(f, argv[1]); } } else if(!strcmp(argv[0], "DUMP")) { if(argc > 1) fprintf(f, "Too many arguements for command dump\n"); else { dump_call(); } } else if(!strcmp(argv[0], "DTMF")) { if(argc > 2) { fprintf(f, "Too many arguements for command dtmf\n"); return; } if (argc < 1) { fprintf(f, "Too many arguements for command dtmf\n"); return; } if(most_recent_answer) iax_send_dtmf(most_recent_answer->session,*argv[1]); } else if(!strcmp(argv[0], "QUIT")) { if(argc > 1) fprintf(f, "Too many arguements for command quit\n"); else { fprintf(f, "Good bye!\n"); exit(1); } } else fprintf(f, "Unknown command of %s\n", argv[0]); } void issue_prompt(FILE *f) { fprintf(f, "TeleClient> "); fflush(f); } void dump_array(FILE *f, char **array) { while(*array) fprintf(f, "%s\n", *array++); }