2 * Miniphone: A simple, command line telephone
4 * IAX Support for talking to Asterisk and other Gnophone clients
6 * Copyright (C) 1999, Linux Support Services, Inc.
8 * Mark Spencer <markster@linux-support.net>
10 * This program is free software, distributed under the terms of
11 * the GNU General Public License
14 /* #define PRINTCHUCK /* enable this to indicate chucked incomming packets */
31 #include "iax-client.h"
33 #include "miniphone.h"
41 struct iax_session *session;
45 static struct peer *peers;
46 static int answered_call = 0;
48 /* stuff for wave audio device */
52 typedef struct whout {
58 WHOUT *outqueue = NULL;
60 /* parameters for audio in */
61 #define NWHIN 8 /* number of input buffer entries */
62 /* NOTE the OUT_INTERVAL parameter *SHOULD* be more around 18 to 20 or so, since the packets should
63 be spaced by 20 milliseconds. However, in practice, especially in Windoze-95, setting it that high
64 caused underruns. 10 is just ever so slightly agressive, and the receiver has to chuck a packet
65 every now and then. Thats about the way it should be to be happy. */
66 #define OUT_INTERVAL 10 /* number of ms to wait before sending more data to peer */
67 /* parameters for audio out */
68 #define OUT_DEPTH 12 /* number of outbut buffer entries */
69 #define OUT_PAUSE_THRESHOLD 2 /* number of active entries needed to start output (for smoothing) */
71 /* audio input buffer headers */
73 /* audio input buffers */
74 char bufin[NWHIN][320];
76 /* initialize the sequence variables for the audio in stuff */
77 unsigned int whinserial = 1,nextwhin = 1;
79 static struct peer *find_peer(struct iax_session *);
80 static void parse_args(FILE *, unsigned char *);
81 void do_iax_event(FILE *);
82 void call(FILE *, char *);
83 void answer_call(void);
84 void reject_call(void);
85 static void handle_event(FILE *, struct iax_event *e, struct peer *p);
86 void parse_cmd(FILE *, int, char **);
87 void issue_prompt(FILE *);
88 void dump_array(FILE *, char **);
90 static char *help[] = {
91 "Welcome to the miniphone telephony client, the commands are as follows:\n",
92 "Help\t\t-\tDisplays this screen.",
93 "Call <Number>\t-\tDials the number supplied.",
94 "Answer\t\t-\tAnswers an Inbound call.",
95 "Reject\t\t-\tRejects an Inbound call.",
96 "Dump\t\t-\tDumps (disconnects) the current call.",
97 "Dtmf <Digit>\t-\tSends specified DTMF digit.",
98 "Status\t\t-\tLists the current sessions and their current status.",
99 "Quit\t\t-\tShuts down the client.",
104 static struct peer *most_recent_answer;
105 static struct iax_session *newcall = 0;
107 /* holder of the time, relative to startup in system ticks. See our
108 gettimeofday() implementation */
111 /* routine called at exit to shutdown audio I/O and close nicely.
112 NOTE: If all this isnt done, the system doesnt not handle this
113 cleanly and has to be rebooted. What a pile of doo doo!! */
121 WSACleanup(); /* dont forget socket stuff too */
125 /* Win-doze doenst have gettimeofday(). This sux. So, what we did is
126 provide some gettimeofday-like functionality that works for our purposes.
127 In the main(), we take a sample of the system tick counter (into startuptime).
128 This function returns the relative time since program startup, more or less,
129 which is certainly good enough for our purposes. */
130 void gettimeofday(struct timeval *tv, struct timezone *tz)
132 long l = startuptime + GetTickCount();
134 tv->tv_sec = l / 1000;
135 tv->tv_usec = (l % 1000) * 1000;
140 static struct peer *find_peer(struct iax_session *session)
142 struct peer *cur = peers;
144 if (cur->session == session)
152 parse_args(FILE *f, unsigned char *cmd)
154 static char *argv[MAXARGS];
155 unsigned char *parse = cmd;
158 // Don't mess with anything that doesn't exist...
162 memset(argv, 0, sizeof(argv));
164 if(*parse < 33 || *parse > 128) {
167 fprintf(f, "Warning: Argument exceeds maximum argument size, command ignored!\n");
170 } else if(t || !argc) {
171 if(argc == MAXARGS) {
172 fprintf(f, "Warning: Command ignored, too many arguments\n");
175 argv[argc++] = parse;
183 parse_cmd(f, argc, argv);
186 /* handle all network requests, and a pending scheduled event, if any */
187 void service_network(int netfd, FILE *f)
190 struct timeval dumbtimer;
192 /* set up a timer that falls-through */
193 dumbtimer.tv_sec = 0;
194 dumbtimer.tv_usec = 0;
197 for(;;) /* suck everything outa network stuff */
200 FD_SET(netfd, &readfd);
201 if (select(netfd + 1, &readfd, 0, 0, &dumbtimer) > 0)
203 if (FD_ISSET(netfd,&readfd))
206 (void) iax_time_to_next_event();
210 do_iax_event(f); /* do pending event if any */
215 main(int argc, char *argv[])
227 unsigned long lastouttick = 0;
231 /* get time of day in milliseconds, offset by tick count (see our
232 gettimeofday() implementation) */
234 startuptime = ((t % 86400) * 1000) - GetTickCount();
237 _dup2(fileno(stdout),fileno(stderr));
239 /* start up the windoze-socket layer stuff */
240 if (WSAStartup(0x0101,&foop)) {
241 fprintf(stderr,"Fatal error: Falied to startup windows sockets\n");
246 /* setup the format for opening audio channels */
247 wf.wFormatTag = WAVE_FORMAT_PCM;
249 wf.nSamplesPerSec = 8000;
250 wf.nAvgBytesPerSec = 16000;
252 wf.wBitsPerSample = 16;
254 /* open the audio out channel */
255 if (waveOutOpen(&wout,0,&wf,0,0,CALLBACK_NULL) != MMSYSERR_NOERROR)
257 fprintf(stderr,"Fatal Error: Failed to open wave output device\n");
260 /* open the audio in channel */
261 if (waveInOpen(&win,0,&wf,0,0,CALLBACK_NULL) != MMSYSERR_NOERROR)
263 fprintf(stderr,"Fatal Error: Failed to open wave input device\n");
268 /* activate the exit handler */
270 /* initialize the audio in buffer structures */
271 memset(&whin,0,sizeof(whin));
273 if ( (port = iax_init(0) < 0)) {
274 fprintf(stderr, "Fatal error: failed to initialize iax with port %d\n", port);
279 iax_set_formats(AST_FORMAT_GSM);
280 netfd = iax_get_fd();
282 fprintf(f, "Text Based Telephony Client.\n\n");
285 /* main tight loop */
287 /* service the network stuff */
288 service_network(netfd,f);
289 if (outqueue) /* if stuff in audio output queue, free it up if its available */
291 /* go through audio output queue */
292 for(wh = outqueue,wh1 = wh2 = NULL,i = 0; wh != NULL; wh = wh->next)
294 service_network(netfd,f); /* service network here for better performance */
295 /* if last one was removed from queue, zot it here */
301 i = 0; /* reset "last one removed" flag */
302 if (wh->w.dwFlags & WHDR_DONE) /* if this one is done */
304 /* prepare audio header */
305 if ((c = waveOutUnprepareHeader(wout,&wh->w,sizeof(WAVEHDR))) != MMSYSERR_NOERROR)
307 fprintf(stderr,"Cannot unprepare audio out header, error %d\n",c);
310 if (wh1 != NULL) /* if there was a last one */
312 wh1->next = wh->next;
314 if (outqueue == wh) /* is first one, so set outqueue to next one */
318 i = 1; /* set 'to free' flag */
320 wh2 = wh1; /* save old,old wh pointer */
321 wh1 = wh; /* save the old wh pointer */
324 /* go through all audio in buffers, and prepare and queue ones that are currently idle */
325 for(i = 0; i < NWHIN; i++)
327 service_network(netfd,f); /* service network stuff here for better performance */
328 if (!(whin[i].dwFlags & WHDR_PREPARED)) /* if not prepared, do so */
330 /* setup this input buffer header */
331 memset(&whin[i],0,sizeof(WAVEHDR));
332 whin[i].lpData = bufin[i];
333 whin[i].dwBufferLength = 320;
334 whin[i].dwUser = whinserial++; /* set 'user data' to current serial number */
335 /* prepare the buffer */
336 if (waveInPrepareHeader(win,&whin[i],sizeof(WAVEHDR)))
338 fprintf(stderr,"Unable to prepare header for input\n");
341 /* add it to device (queue) */
342 if (waveInAddBuffer(win,&whin[i],sizeof(WAVEHDR)))
344 fprintf(stderr,"Unable to prepare header for input\n");
348 waveInStart(win); /* start it (if not already started) */
351 /* if key pressed, do command stuff */
354 if ( ( fgets(&*rcmd, 256, stdin))) {
355 rcmd[strlen(rcmd)-1] = 0;
356 parse_args(f, &*rcmd);
357 } else fprintf(f, "Fatal error: failed to read data!\n");
361 /* do audio input stuff for buffers that have received data from audio in device already. Must
362 do them in serial number order (the order in which they were originally queued). */
363 if(answered_call) /* send audio only if call answered */
365 for(;;) /* loop until all are found */
367 for(i = 0; i < NWHIN; i++) /* find an available one that's the one we are looking for */
369 service_network(netfd,f); /* service network here for better performance */
370 /* if not time to send any more, dont */
371 if (GetTickCount() < (lastouttick + OUT_INTERVAL))
373 i = NWHIN; /* set to value that WILL exit loop */
376 if ((whin[i].dwUser == nextwhin) && (whin[i].dwFlags & WHDR_DONE)) { /* if audio is ready */
378 /* must have read exactly 320 bytes */
379 if (whin[i].dwBytesRecorded != whin[i].dwBufferLength)
381 fprintf(stderr,"Short audio read, got %d bytes, expected %d bytes\n", whin[i].dwBytesRecorded,
382 whin[i].dwBufferLength);
385 if(!most_recent_answer->gsmout)
386 most_recent_answer->gsmout = gsm_create();
388 service_network(netfd,f); /* service network here for better performance */
389 /* encode the audio from the buffer into GSM format */
390 gsm_encode(most_recent_answer->gsmout, (short *) ((char *) whin[i].lpData), fo);
391 if(iax_send_voice(most_recent_answer->session,
392 AST_FORMAT_GSM, (char *)fo, sizeof(gsm_frame)) == -1)
393 puts("Failed to send voice!");
394 lastouttick = GetTickCount(); /* save time of last output */
396 /* unprepare (free) the header */
397 waveInUnprepareHeader(win,&whin[i],sizeof(WAVEHDR));
398 /* initialize the buffer */
399 memset(&whin[i],0,sizeof(WAVEHDR));
400 /* bump the serial number to look for the next time */
402 /* exit the loop so that we can start at lowest buffer again */
406 if (i >= NWHIN) break; /* if all found, get out of loop */
415 do_iax_event(FILE *f) {
417 struct iax_event *e = 0;
420 while ( (e = iax_get_event(0))) {
421 peer = find_peer(e->session);
423 handle_event(f, e, peer);
425 if(e->etype != IAX_EVENT_CONNECT) {
426 fprintf(stderr, "Huh? This is an event for a non-existant session?\n");
430 if(sessions >= MAX_SESSIONS) {
431 fprintf(f, "Missed a call... too many sessions open.\n");
435 if(e->event.connect.callerid && e->event.connect.dnid)
436 fprintf(f, "Call from '%s' for '%s'", e->event.connect.callerid,
437 e->event.connect.dnid);
438 else if(e->event.connect.dnid) {
439 fprintf(f, "Call from '%s'", e->event.connect.dnid);
440 } else if(e->event.connect.callerid) {
441 fprintf(f, "Call from '%s'", e->event.connect.callerid);
442 } else printf("Call from");
443 fprintf(f, " (%s)\n", inet_ntoa(iax_get_peer_addr(e->session).sin_addr));
445 if(most_recent_answer) {
446 fprintf(f, "Incoming call ignored, there's already a call waiting for answer... \
447 please accept or reject first\n");
448 iax_reject(e->session, "Too many calls, we're busy!");
450 if ( !(peer = malloc(sizeof(struct peer)))) {
451 fprintf(f, "Warning: Unable to allocate memory!\n");
455 peer->time = time(0);
456 peer->session = e->session;
463 iax_accept(peer->session);
464 iax_ring_announce(peer->session);
465 most_recent_answer = peer;
466 fprintf(f, "Incoming call!\n");
475 call(FILE *f, char *num)
480 newcall = iax_session_new();
482 fprintf(f, "Already attempting to call somewhere, please cancel first!\n");
486 if ( !(peer = malloc(sizeof(struct peer)))) {
487 fprintf(f, "Warning: Unable to allocate memory!\n");
491 peer->time = time(0);
492 peer->session = newcall;
499 most_recent_answer = peer;
501 iax_call(peer->session, num, 10);
507 if(most_recent_answer)
508 iax_answer(most_recent_answer->session);
509 printf("Answering call!\n");
516 if(most_recent_answer)
518 iax_hangup(most_recent_answer->session,"");
519 free(most_recent_answer);
521 printf("Dumping call!\n");
523 most_recent_answer = 0;
532 iax_reject(most_recent_answer->session, "Call rejected manually.");
533 most_recent_answer = 0;
537 handle_event(FILE *f, struct iax_event *e, struct peer *p)
542 static paused_xmit = 0;
546 case IAX_EVENT_HANGUP:
547 iax_hangup(most_recent_answer->session, "Byeee!");
548 fprintf(f, "Call disconnected by peer\n");
549 free(most_recent_answer);
550 most_recent_answer = 0;
557 case IAX_EVENT_REJECT:
558 fprintf(f, "Authentication was rejected\n");
560 case IAX_EVENT_ACCEPT:
561 fprintf(f, "Waiting for answer... RING RING\n");
564 case IAX_EVENT_ANSWER:
567 case IAX_EVENT_VOICE:
568 switch(e->event.voice.format) {
570 if(e->event.voice.datalen % 33) {
571 fprintf(stderr, "Weird gsm frame, not a multiple of 33.\n");
576 p->gsmin = gsm_create();
579 while(len < e->event.voice.datalen) {
580 if(gsm_decode(p->gsmin, (char *) e->event.voice.data + len, fr)) {
581 fprintf(stderr, "Bad GSM data\n");
583 } else { /* its an audio packet to be output to user */
585 /* get count of pending items in audio output queue */
588 { /* determine number of pending out queue items */
589 for(wh = outqueue; wh != NULL; wh = wh->next)
591 if (!(wh->w.dwFlags & WHDR_DONE)) n++;
594 /* if not too many, send to user, otherwise chuck packet */
595 if (n <= OUT_DEPTH) /* if not to chuck packet */
597 /* malloc the memory for the queue item */
598 wh = (WHOUT *) malloc(sizeof(WHOUT));
599 if (wh == (WHOUT *) NULL) /* if error, bail */
601 fprintf(stderr,"Outa memory!!!!\n");
604 /* initialize the queue entry */
605 memset(wh,0,sizeof(WHOUT));
606 /* copy the PCM data from the gsm conversion buffer */
607 memcpy((char *)wh->data,(char *)fr,sizeof(fr));
608 /* set parameters for data */
609 wh->w.lpData = (char *) wh->data;
610 wh->w.dwBufferLength = 320;
612 /* prepare buffer for output */
613 if (waveOutPrepareHeader(wout,&wh->w,sizeof(WAVEHDR)))
615 fprintf(stderr,"Cannot prepare header for audio out\n");
618 /* if not currently transmitting, hold off a couple of packets for
619 smooth sounding output */
620 if ((!n) && (!paused_xmit))
622 /* pause output (before starting) */
624 /* indicate as such */
627 /* queue packet for output on audio device */
628 if (waveOutWrite(wout,&wh->w,sizeof(WAVEHDR)))
630 fprintf(stderr,"Cannot output to wave output device\n");
633 /* if we are paused, and we have enough packets, start audio */
634 if ((n > OUT_PAUSE_THRESHOLD) && paused_xmit)
636 /* start the output */
637 waveOutRestart(wout);
638 /* indicate as such */
641 /* insert it onto tail of outqueue */
642 if (outqueue == NULL) /* if empty queue */
643 outqueue = wh; /* point queue to new entry */
644 else /* otherwise is non-empty queue */
647 while(wh1->next) wh1 = wh1->next; /* find last entry in queue */
648 wh1->next = wh; /* point it to new entry */
652 else printf("Chucking packet!!\n");
659 fprintf(f, "Don't know how to handle that format %d\n", e->event.voice.format);
662 case IAX_EVENT_RINGA:
665 fprintf(f, "Unknown event: %d\n", e->etype);
671 parse_cmd(FILE *f, int argc, char **argv)
674 if(!strcmp(argv[0], "HELP")) {
678 if(!strcmp(argv[1], "HELP"))
679 fprintf(f, "Help <Command>\t-\tDisplays general help or specific help on command if supplied an arguement\n");
680 else if(!strcmp(argv[1], "QUIT"))
681 fprintf(f, "Quit\t\t-\tShuts down the miniphone\n");
682 else fprintf(f, "No help available on %s\n", argv[1]);
684 fprintf(f, "Too many arguements for command help.\n");
686 } else if(!strcmp(argv[0], "STATUS")) {
689 struct peer *peerptr = peers;
692 fprintf(f, "No session matches found.\n");
693 else while(peerptr) {
694 fprintf(f, "Listing sessions:\n\n");
695 fprintf(f, "Session %d\n", ++c);
696 fprintf(f, "Session existed for %d seconds\n", (int)time(0)-peerptr->time);
698 fprintf(f, "Call answered.\n");
699 else fprintf(f, "Call ringing.\n");
701 peerptr = peerptr->next;
703 } else fprintf(f, "Too many arguments for command status.\n");
704 } else if(!strcmp(argv[0], "ANSWER")) {
706 fprintf(f, "Too many arguements for command answer\n");
708 } else if(!strcmp(argv[0], "REJECT")) {
710 fprintf(f, "Too many arguements for command reject\n");
712 fprintf(f, "Rejecting current phone call.\n");
715 } else if(!strcmp(argv[0], "CALL")) {
717 fprintf(f, "Too many arguements for command call\n");
721 } else if(!strcmp(argv[0], "DUMP")) {
723 fprintf(f, "Too many arguements for command dump\n");
727 } else if(!strcmp(argv[0], "DTMF")) {
730 fprintf(f, "Too many arguements for command dtmf\n");
735 fprintf(f, "Too many arguements for command dtmf\n");
738 if(most_recent_answer)
739 iax_send_dtmf(most_recent_answer->session,*argv[1]);
740 } else if(!strcmp(argv[0], "QUIT")) {
742 fprintf(f, "Too many arguements for command quit\n");
744 fprintf(f, "Good bye!\n");
747 } else fprintf(f, "Unknown command of %s\n", argv[0]);
751 issue_prompt(FILE *f)
753 fprintf(f, "TeleClient> ");
758 dump_array(FILE *f, char **array) {
760 fprintf(f, "%s\n", *array++);