Dank des Asterisk Gateway Interface entwickeln Sie eigene Telefonie-Anwendungen mit ihrer Lieblings-Programmiersprache. Der Workshop erklärt die Grundlagen von AGI anhand einer putzigen Anwendung, die Anrufern zu per Telefontastatur eingegebenen Begriffen die zugehörigen Wikipedia-Artikel vorliest .
Mit Hilfe des Dialplan-Befehls »AGI« übergibt Asterisk die Kontrolle über einen Channel weitgehend an ein externes Programm, das Sie in vielen gängigen Sprachen schreiben dürfen. Der Artikel beschreibt Prinzip, Syntax und die Fallstricke von AGI bei Asterisk 1.4 anhand einer Text-to-Speech-Anwendung für Wikipedia, deren Ablauf Abbildung 1 darstellt. Die blauen Ziffern im Callflow geben die Nummer des Sprachbausteins an, die roten Textfragmente spielt das AGI-Programm dem Benutzer vor.
Nachdem der Anrufer seine Sprache ausgewählt hat, wird er gebeten, mit Hilfe der Tabelle auf [http://www.readwikipedia.net], siehe auch [1], einen Begriff per Touchtone einzutippen. Ein PHP-Skript schlägt den Begriff bei Wikipedia nach. Lässt sich ein Wiki-Eintrag finden, konvertiert das Skript den Text mit einem Text-to-Speech-System in Audiodaten.

Abbildung 1: Der Ablaufplan der Telefonie-Anwendung von Readwikipedia.net. Der Benutzer tippt sich auf seinem Telefon durch ein Menü und bekommt dann einen Wikipedia-Eintrag vorgelesen.
Wikipedia am Telefon: So funktioniert’s
Das AGI-Programm spielt dem Anrufer die Audiodatei für den Benutzer vor. Anschließend kann er einen neuen Begriff eingeben. Hat sich kein Wikipedia-Artikel gefunden oder tritt ein Problem bei der Wiedergabe auf, informiert das Programm den Benutzer und gibt ihm die Möglichkeit, es mit einem anderen Begriff erneut zu versuchen. Das Ganze können Sie mit einem Anruf unter der Nummer +49 41 06 61 87 18 des Autors ausprobieren.
Das Skript »wikipedia2speech.php« von [2] konvertiert Artikel sowohl von der deutschen als auch von der englischen Wikipedia. Dazu bedient es sich zweier Text-to-Speech-Systeme. Eine Installationsanleitung sowie Anwendungsbeispiele finden Sie auf [2]. Das gleich vorgestellte AGI-Programm ruft »wikipedia2speech.php« mit Parametern auf. Das PHP-Skript erzeugt die angeforderte Audiodatei, die das AGI-Programm dann dem Anrufer vorspielt. Das installierte PHP-Skript ist also Voraussetzung für das Funktionieren des AGI-Programms.
Thema Umlaute: Man spricht Deutsch
Damit der AGI-Code (Fragment in Listing 1, alle Listings vollständig unter [3]) funktioniert, muss Asterisk von dem AGI-Befehl »SAY ALPHA« Umlaute und das ß annehmen und ein passendes Soundfile abspielen. Ein Standard-Asterisk ist dazu nicht in der Lage. Um dies zu korrigieren, fügen Sie in der Datei »main/say.c« der »switch case«-Anweisung in der Funktion »say_character_str_full()« die Zeilen in Listing 2 hinzu. »auml«, »ouml«, »uuml« und »szlig« sind die Sprachbausteine für Ä, Ö, Ü sowie ß, die Sie ebenfalls aufnehmen und zum Beispiel als »auml.gsm« im Verzeichnis »/var/lib/asterisk/sounds/de« speichern.
|
Listing 1: |
|---|
[...]
21 int agiNow_setlanguage(const char *lang)
22 {
23 char res_line[MAXLINE];
24
25 printf("EXEC SET CHANNEL(language)=%sn", lang);
26 fgets(res_line, (int)sizeof(res_line), stdin);
27 return 0;
28 }
29
30 int agiNow_getoption(long *endpos, const char *filename, const char *escdigits, const char *format, ...)
[...]
59
60 int agiNow_sayalpha(char *word, const char *escdigits)
[...]
75
76 int agiNow_waitfordigit(int timeout)
[...]
86
87 int agiNow_streamfile(long *endpos, const char *filename, const char *escdigits, const char *format, ...)
[...]
116
117 int agiNow_verbose(char *msg, const char *format, ...)
[...]
130
131 int agiNow_get_word_from_user(char search_this[], int array_length)
[...]
208
209 int read_initial_agi_vars()
210 {
211 char c, line[256];
212 int line_pos, var_pos, struct_count = 0;
213
214 while (strcmp(fgets(line, (int)sizeof(line), stdin), "n")) {
215 // Save the variable name in agi_vars[struct_count].name.
216 for (line_pos = var_pos = 0; (c = line[line_pos]) != ':'; line_pos++, var_pos++) {
217 agi_vars[struct_count].name[var_pos] = c;
218 if ((AGI_STDIN_LENGTH-1) == var_pos) break;
219 }
220 var_pos++;
221 agi_vars[struct_count].name[var_pos] = '
|
|
Listing 2: Änderungen in |
|---|
01 case ('xE4'):
02 fn = "auml";
03 break;
04 case ('xC4'):
05 fn = "auml";
06 break;
07 case ('xF6'):
08 fn = "ouml";
09 break;
10 case ('xD6'):
11 fn = "ouml";
12 break;
13 case ('xFC'):
14 fn = "uuml";
15 break;
16 case ('xDC'):
17 fn = "uuml";
18 break;
19 case ('xDF'):
20 fn = "szlig";
21 break;
|
Mit »make && make install« aktivieren Sie die Änderung, sofern Ihnen schon ein konfigurierter Verzeichnisbaum vorliegt. Nun müsste es Ihnen möglich sein, mit dem AGI-Befehl »SAY ALPHA« Umlaute versuchsweise wiederzugeben, sofern diese ISO-8859-1-kodiert sind.
Eine Übersicht über die Sprachbausteine, die das AGI-Programm in Listing 1 zusätzlich benötigt, finden Sie in Tabelle 1.Die beiden Bausteine »01.gsm« und »02.gsm« sind zweisprachig, da an der entsprechenden Stelle im Callflow die Benutzersprache noch unbekannt ist. Per se nimmt Asterisk Englisch an und erwartet die Sprachbausteine in »/var/lib/asterisk/sounds«. Wenn dann ein AGI-Befehl innerhalb des AGI-Programms die Sprache auf Deutsch setzt, konzentriert sich Asterisk auf das Verzeichnis »/var/lib/asterisk/sounds/de«.
|
Tabelle 1: Eigene |
|---|
Außerdem benötigt das AGI-Programm einige deutsche Sprachbausteine, um das deutsche Alphabet abspielen zu können. Die passenden Dateien laden Sie von [4] herunter, an gleicher Stelle liegt auch eine Anleitung, anhand derer Sie die Dateien installieren. Beachten Sie, dass Asterisk die neuen Sprachbausteine nur finden kann, wenn Sie der Konfigurationsdatei »asterisk.conf« die Zeilen
[options] languageprefix=yes
hinzugefügt haben. (Existiert »[options]« bereits, natürlich nur die zweite Zeile.)
Programm übersetzen, aufrufen und parametrisieren
Der Code in Listing 1 realisiert den Callflow in Abbildung 1. Damit das Programm übersichtlich bleibt, verzichtet es auf umfängliche Fehlerbehandlungen. Nachdem Sie den Quelltext mit
gcc -o readwikipedia-agi readwikipedia-agi.c
kompiliert haben, kopieren Sie das Binary »readwikipedia-agi« nach »/var/lib/asterisk/agi-bin/«. Das ist das Verzeichnis, in dem Asterisk normalerweise ein Programm erwartet, wenn der AGI-Befehl es, wie im folgenden Context zu sehen, bei einem Anruf der Extension 69 aufruft:
[default] exten => 69,1,Answer() exten => 69,2,AGI(readwikipedia-agi) exten => 69,3,Hangup()
Asterisk liefert dem AGI-Programm beim Aufruf Informationen, wie Listing 3 sie beispielhaft aufführt und die das AGI-Programm als Erstes von der Standardeingabe holen sollte. Das Format ergibt sich aus »Variablenname: Wert«. Das AGI-Programm muss so lange Daten von Stdin lesen, bis es auf eine Zeile trifft, die nur das Newline-Zeichen enthält und so das Ende der Daten markiert. In Listing 1 ist die Funktion »read_initial_agi_vars()« ab Zeile 209 dafür zuständig, die AGI-Variablen einzulesen. Wollen Sie aus dem Dialplan heraus »Argument_1«, …, »Argument_n« an das AGI-Programm übergeben, starten Sie es mit:
AGI(readwikipedia-agi|Argument_1|...|Argument_n)
In Sachen Argumente wäre das Kommandozeilen-Äquivalent dazu:
readwikipedia-agi Argument_1 ... Argument_n
Informationen über seine Verzeichniskonfiguration stellt Asterisk dem AGI-Programm über Umgebungsvariablen wie in Tabelle 2 zur Verfügung.
|
Tabelle 2: |
|---|
Kommunikation zwischen AGI-Programm und Asterisk
AGI-Befehle an Asterisk schicken Sie wie in Listing 1, Zeile 25, über Stdout. Jeder Befehl löst eine Antwort aus, die das AGI-Programm von Stdin einlesen sollte, wie es der Aufruf von »fgets()« in Zeile 26 tut. Wenn Sie sich den Datenaustausch zwischen Asterisk und dem AGI-Programm anschauen wollen, tippen Sie »agi debug« auf Asterisks CLI. Jeden dort durch »AGI Tx >>« gekennzeichneten Text kann und sollte das AGI-Programm von Stdin lesen. Alles, was Asterisk als AGI-Befehl auszuwerten versucht, steht hinter »AGI Rx <<« (siehe Abbildung 4).
Die Eingabe von »agi debug off« verhindert die weitere Ausgabe dieser Meldungen. Von den AGI-Variablen abgesehen schickt Asterisk von sich aus keine Daten per Stdout an das AGI-Programm. Beim Schreiben vom AGI-Programm aus nach Stdout müssen Sie dringend beachten, dass Linux bei seinen Standard-E/A-Funktionen intern puffert, damit die Zahl der physikalischen Lese- und Schreiboperationen mit »read()«- und »write()«- Systemaufrufen möglichst klein bleibt.
Linux folgt dabei der Konvention, dass es Datenströme, die sich auf ein Terminal beziehen, zeilenweise puffert, alle anderen vollständig. Bei AGI-Programmen zeigt Stdout nicht auf ein Terminal und unterliegt somit der vollständigen Pufferung des Systems, was beim AGI-Entwickeln unerwünscht ist. Die Funktion »setvbuf()« in Zeile 261 sorgt wieder fürs zeilenweise Puffern.
Für die Programmierpraxis gut zu wissen ist, dass »agi show« eine Liste aller gültigen AGI-Befehlen liefert. Ausführlichere Hilfe im HTML-Format gibt’s per »agi dumphtml /tmp/agi.html«. Details zu einem einzelnen Kommando vermittelt »agi show Befehl«.
Gewusst wie: Fehlersuche
Debugmeldungen dürfen Sie nicht einfach mit »printf()« oder »echo()« ausgeben, da sie auf der Standardausgabe landen würden. Für diesen Zweck gibt es den AGI-Befehl »VERBOSE«, wie er in Zeile 126 von Listing 1 auftaucht. Das erste Argument ist die Meldung, die auf dem CLI erscheinen soll. Das zweite, optionale Argument spezifiziert die Loglevel, bei denen die Meldung erscheint.
Asterisk schreibt die Meldung auf das CLI, wenn Asterisks Loglevel, den der befehl »core set verbose Level« setzt, größer oder gleich dem Loglevel im AGI-Befehl ist. Fehlt im AGI-Befehl ein Loglevel, dann gibt Asterisk die Meldung in jedem Fall aus, unabhängig von seinem eigenen Loglevel, also auch dann, wenn Sie beim »core set verbose«-Befehl eine »0« gesetzt hätten.
Alternativ können Sie Debugmeldungen auch nach Stderr schicken. Der Text erscheint dann immer auf dem CLI, wenn Sie Asterisk mit »asterisk -c« gestartet haben. Wer sich dagegen mit »asterisk -r« mit einem laufenden Prozess verbindet, sieht die Ausgaben nach Stderr nicht.
Der Callflow
Der AGI-Befehl »GET OPTION« ist gut geeignet, um den Benutzer zu fragen, welche Sprache er verwenden will. Zeile 265 ruft die Funktion »agiNow_getoption()« auf, die den Befehl mit Argumenten an Asterisk schickt. Als Eingaben des Benutzer sind lediglich [1] oder [2] erlaubt. Die While-Schleife von Zeile 266 bis Zeile 278 läuft so lange, bis der Anrufer eine gültige Auswahl getroffen hat.
|
Listing 3: AGI-Variablen zum |
|---|
01 agi_request: readwikipedia-agi 02 agi_channel: SIP/111-08203fa8 03 agi_language: en 04 agi_type: SIP 05 agi_uniqueid: 1207080826.11 06 agi_callerid: 111 07 agi_calleridname: unknown 08 agi_callingpres: 0 09 agi_callingani2: 0 10 agi_callington: 0 11 agi_callingtns: 0 12 agi_dnid: 69 13 agi_rdnis: unknown 14 agi_context: default 15 agi_extension: 69 16 agi_priority: 2 17 agi_enhanced: 0.0 18 agi_accountcode: |
Der Anrufer entscheidet per Tastendruck
Die Zeilen 268 und 272 setzen die Sprache auf Deutsch beziehungsweise auf Englisch. Angenehm bei dem Befehl »GET OPTION« ist, dass der Benutzer die Wiedergabe des Sprachbausteins durch Tippen einer gültigen Ziffer, hier: [1] oder [2], vorzeitig abbrechen kann. Die Zeilen 279 und 280 rufen die Funktion »agiNow_streamfile()« auf, um je eine Audiodatei wiederzugeben. Die erste enthält die Aufforderung, einen Begriff einzugeben, die zweite ist der Signalton. »agiNow_streamfile()« schickt den entsprechenden AGI-Befehl an Asterisk.
Die nun folgende While-Schleife in den Zeilen von 281 bis 317 kann der Anrufer nur dadurch verlassen, indem er auflegt. Sie eröffnet ihm die Möglichkeit, endlos oft einen neuen Begriff einzutippen, wie es der Callflow in Abbildung 1 vorgibt. Der erste Schritt der While-Schleife liest so lange einen Begriff vom Benutzer ein, bis die Eingabe länger als null Zeichen ist. Dies leistet die While-Schleife ab Zeile 283. Ihre entscheidende Funktion ist offensichtlich »agiNow_get_word_from_user()«, die in Zeile 131 beginnt.
Abbildung 2 beschreibt den Ablauf der Funktion, wie er sich für den Anwender darstellt. Rote Texte bekommt der Benutzer an der jeweiligen Stelle vorgespielt. So gibt der Sprachbaustein »deleted-character« den Text »gelöschter Buchstabe« wieder. Sie müssen natürlich die Sprachbausteine zuvor aufnehmen und unter den angegebenen Namen speichern, zum Beispiel als »/var/lib/asterisk/sounds/deleted-character.gsm«. Für den Buchstaben A drückt der Anrufer [0][1], für den Buchstaben B [0][2] und so weiter. Die komplette Umwandlungstabelle finden Sie unter [1] und in Abbildung 5.

Abbildung 2: Ablaufplan zur Funktion »agiNow_get_word_from_user()«. Texte in Rot bekommt der Benutzer an der jeweiligen Stelle vorgespielt.
PHP-Skript aus C-Programm heraus aufrufen
Die Funktion »system()« in Zeile 300 ruft das PHP-Skript »wikipedia2speech.php« auf, das eine Audiodatei mit dem in Sprache konvertierten Wikipedia-Artikel erzeugt. Falls bereits vorhanden gibt der Aufruf von »agiNow_streamfile()« in Zeile 304 die Datei wieder. »”#”« als vorletzter Parameter beim Aufruf der Funktion in Zeile 307 bewirkt, dass der Benutzer die Wiedergabe mit der Rautetaste beenden kann. Abhängig davon, ob der Benutzer tatsächlich Audiodaten zu hören bekommt, spielt der danach folgende Code einen der Sprachbausteine »06« oder »07« ab.
Alle in Listing 1 verwendeten Funktionen stellt die GPL-Bibliothek »libagiNow« zur Verfügung, die Sie bei [5] bekommen.
Unerwarteter Befehlsablauf
Nach dem Überblick geht es weiter mit dem Befehl »STREAM FILE«, um darzustellen, welche Fallen auf AGI-Entwickler lauern. Der Hilfetext
Usage: STREAM FILE <filename> <escape digits> [sample offset] Send the given file, allowing playback to be interrupted by the given digits, if any. ...
den »agi show stream file« liefert, lässt die irrige Vermutung zu, dass das Pseudoprogramm in Listing 4 dem Anrufer die Datei »letters/a.gsm« vorspielt. Das Problem liegt jedoch darin, dass Asterisk nicht genug Zeit bleibt, um die Audiodatei wiederzugeben, während das AGI-Skript läuft. Das AGI-Programm arbeitet nämlich nur den Bruchteil einer Sekunde lang, sofort anschließend macht Asterisk mit der nächsten anstehenden Priority im Dialplan weiter.
Würden Sie hinter »flush(stdout)« mit »sleep(1)« das Skript eine Sekunde lang warten lassen, spielt Asterisk die Audiodatei ab. Sleep-Aufrufe sind natürlich keine Option für Produktivsysteme. Richtig ist es, Antworten auf AGI-Befehle auch dann von Stdin zu lesen, wenn niemand sie auswertet. In Listing 4 müssten Sie hinter »flush(stdout)« in Zeile 3 einen Befehl einfügen, der eine von »STREAM FILE« gelieferte Ausgabezeile einliest.
In diesem Fall funktioniert selbst das nicht, was daran liegt, dass der eingefügte Befehl lediglich die erste Zeile der AGI-Variablen liest, die der Puffer gerade parat hält, im Beispiel oben die Zeile:
agi_request: readwikipedia-agi
Nachdem Asterisk die Zeile gelesen hat, wirft das AGI-Programm sofort das Handtuch und gibt nichts mehr aus. Das Problem: Sie haben nicht als Erstes nach Programmstart alle AGI-Variablen eingelesen. Nur dann nämlich wartet ein »fgets()«-Befehl nach dem »flush(stdout)« tatsächlich auf die Antwort von »STREAM FILE«. Das sorgt auch dafür, dass der »STREAM FILE«-Befehl genau so viel Zeit bekommt, wie er für die Ausführung braucht.
|
Listing 4: Fehlerhafter |
|---|
01 program stream_file 02 schreibe 'STREAM FILE letters/a ""n' nach stdout 03 flush(stdout) 04 end program stream_file |
Antworten unter der Lupe
Asterisk kennt für AGI-Programme drei verschiedene Rückgabecodes. Mit »510« teilt es mit, dass es einen ungültigen AGI-Befehl erhalten hat:
AGI Rx << GET ATA demo-congrats 5000 10 AGI Tx >> 510 Invalid or unknown command
Wenn Sie für einen Befehl eine falsche Syntax verwendet haben, kommt »520« zurück, wie in Listing 5 zu sehen ist. Hier vermisst der »GET DATA«-Befehl die Angabe der Datei, die er wiedergeben soll. Das ist übrigens der einzige Fall, bei dem Asterisk eine mehrzeilige Antwort zurückliefert. Die Hilfe zu dem AGI-Befehl säumen zwei Zeilen, die mit »520« beginnen. Ein Rückgabecode von 200 schließlich signalisiert, dass Sie einen AGI-Befehl korrekt verwendet haben:
AGI Rx << GET DATA demo-congrats AGI Tx >> 200 result=-1
Antworten dieser Art umfassen immer genau eine Zeile. Diese Zeile enthält immer die Zeichenkette »result=Integer [Weitere_Informationen]«. Je nach Befehl differiert die Bedeutung der Integerzahl. Darüber und über weitere Informationen, die manche AGI-Befehle liefern, weiß das Wiki [6] zu berichten.

Abbildung 3: Dreh- und Angelpunkt jedes Voiceportals sind Sprachausgaben. Entsprechende Dateien müssen Sie vor Inbetriebnahme aufzeichen – hier geschehen für das Wort „Linux-Magazin“.
Im Zweifel die Quellen
Beschleicht Sie der Verdacht, dass die Dokumentation bei [6] veraltet ist, oder brauchen Sie den Datentyp eines Rückgabewerts, hilft das Studium des Asterisk-Quelltextes. Die Quelltextdatei für den Befehl »AGI« trägt den Namen »Asterisk-Quellverzeichnis/res/res_agi.c«. Unter [7] gibt es zudem eine mit Doxygen erstellte Dokumentation der Asterisk-Quellen. Daten, die Asterisk an das AGI-Programm schickt, beginnen im Quelltext mit »fdprintf(fd,« oder »fdprintf(agi->fd,«.
Für jeden AGI-Befehl gibt es in der Datei eine eigene Funktion, für »STREAM FILE« beispielsweise ist die Funktion »handle_streamfile()« zuständig. Die beiden Zeilen
fdprintf(agi->fd, "200 result=%d endpos=%ldn", 0, sample_offset); fdprintf(agi->fd, "200 result=%d endpos=%ldn", res, sample_offset);
machen klar, dass Asterisk eine Antwort in der Art »200 result=%d endpos=%ldn« zurückliefern wird, wobei »%d« ein Integer ist und »%ld« ein Long Integer.
Dead-AGI beim Hangup
Eine weitere Möglichkeit, AGI-Programme zu starten, bietet der Dialplan-Befehl »DeadAGI«. Er dient dazu, Programme in einer Hangup-Extension aufzurufen. Der folgende Context stellt das dar:
[default] exten => 69,1,Answer() exten => 69,2,AGI(agi.php) exten => 69,3,Hangup() exten => h,1,DeadAGI(deadagi.php)
Wenn ein Benutzer auflegt, während »agi.php« läuft, springt Asterisk sofort in die Extension »h«, ohne die Priority 3 in der Extension »69« auszuführen. In »h« startet »DeadAGI« dann das Programm »deadagi.php«.
Dead-AGI-Programme werden gern benutzt, um Aufräumarbeiten zu erledigen. So könnte zum Beispiel das Programm »agi.php« mehrere temporäre Dateien gespeichert haben, deren Namen alle mit dem Namen des Channel beginnen. Da der Channel-Name auch in »deadagi.php« zur Verfügung steht, können Sie darin alle temporären Dateien löschen, die mit dem Channel-Namen beginnen.
Die Abfrage des Channel-Status mit dem AGI-Befehl »CHANNEL STATUS« in »deadagi.php« liefert möglicherweise überraschend die Antwort »Line is up« – und das selbst bei folgendem Context:
[default] exten => 69,1,Answer() exten => 69,2,Hangup() exten => h,1,DeadAGI(deadagi.php)
Dies ist wichtig für Abrechnungsfragen. Wenn in dem eben gezeigten Context das Dead-AGI-Programm nichts anderes macht, als 10 Sekunden zu warten, dann zeigt Asterisk in den Call Detail Records eine Abrechnungszeit von mindestens 10 Sekunden an. Die wohl eher gewünschte Abrechnungszeit von 0 Sekunden liefert dagegen der Context:
[default] exten => 69,1,Answer() exten => 69,2,Hangup() exten => h,1,ResetCDR(w) exten => h,2,DeadAGI(deadagi.php)
Der Befehl »ResetCDR(w)« speichert den momentanen Zustand des Call Detail Record. Nach dem Aufruf des Dead-AGI-Programms in der Hangup-Extension können natürlich weitere Priorities folgen, die Asterisk nach Beendigung des Dead-AGI-Programms ausführen soll.

Abbildung 4: In der zweiten Zeile teilt Asterisk mit, dass es das Programm »tue-nichts« aufruft. Es besteht lediglich aus der Zeile »int main() {return 0;}«. Das AGI-Debugging ist bei diesem Aufruf aktiviert.
|
Listing 5: Asterisk hilft beim |
|---|
01 AGI Rx << GET DATA 02 AGI Tx >> 520-Invalid command syntax. Proper usage follows: 03 AGI Tx >> Usage: GET DATA <file to be streamed> [timeout] [max digits] 04 Stream the given file, and recieve DTMF data. Returns the digits received from the channel at the other end. 05 AGI Tx >> 520 End of proper usage. |
Audiodaten lesen: EAGI
Ein mit dem Befehl »EAGI« gestartetes Programm verhält sich identisch zu AGI-Programmen – mit der Ausnahme, dass es über den Datei-Deskriptor 3 sämtliche Audiodaten lesen darf, die der Anrufer in den Channel schickt. Das C-Programm in Listing 6 tut dies und speichert die Audiodaten in der Datei »/tmp/c-audio.raw«. Diese können Sie entweder mit
sox -t raw -r 8000 -s -w -c 1 U c-audio.raw c-audio.wav
in die WAV-Datei »c-audio.wav« konvertieren. Oder Sie geben die Daten mit
cat c-audio.raw | sox -t raw -r 8000 -s -w -c 1 - -t wav - | play -t wav -r 8000 -c 1 -
direkt auf dem Rechner wieder (siehe Kurve in Abbildung 3).
|
Listing 6: Audiodaten als |
|---|
01 #include <fcntl.h>
02 #include <stdio.h>
03 #include <unistd.h>
04 #include <errno.h>
05 #include <sys/select.h>
06 #include <string.h>
07
08 #define AUDIO_FILENO (STDERR_FILENO + 1)
09
10 int main(void)
11 {
12 fd_set fds;
13 int res, fd;
14 char audiobuf[4096];
15
16 if ((fd = open("/tmp/c-audio.raw", O_WRONLY | O_TRUNC | O_CREAT, 660)) < 0)
17 exit(0);
18 for (;;) {
19 FD_ZERO(&fds);
20 FD_SET(AUDIO_FILENO, &fds);
21 if ((res = select(AUDIO_FILENO + 1, &fds, NULL, NULL, NULL)) < 0) {
22 fprintf(stderr, "Error in select: %sn", strerror(errno));
23 break;
24 }
25 if (FD_ISSET(AUDIO_FILENO, &fds)) {
26 res = read(AUDIO_FILENO, audiobuf, sizeof(audiobuf));
27 if (res > 0) {
28 write(fd, audiobuf, res);
29 }
30 }
31 }
32 if (fd > 0)
33 close(fd);
34 exit(0);
35 }
|
Ein AGI-Programm beenden
Bei nicht gesetzter Channel-Variable »AGISIGHUP« schickt Asterisk sofort das Signal »SIGHUP« an Ihr laufendes AGI-Programm, wenn ein Anrufer auflegt. Daher müssen Sie sich auch keine Gedanken machen, dass das Programm in einer While-Schleife hängen bleibt, wenn der Benutzer auflegt.
Dieses Verhalten kann allerdings unerwünscht sein, beispielsweise wenn das AGI-Programm noch temporäre Dateien aufräumen oder eine gerade vom Anrufer aufgenommene Nachricht verschicken soll. Außerdem arbeitet Asterisk die in der Extension folgenden Priorities nicht ab, wenn der Anrufer einhängt. Wollen Sie dieses Asterisk-»SIGHUP« an Ihr AGI-Programm unterbinden, dann weisen Sie vor dem AGI-Programmstart mit
Set(AGISIGHUP="no")
der genannten Variablen »no« zu. Dennoch kann es vorkommen, dass ein AGI-Programm von außen beendet wird, weil es genau genommen über zwei Pipes mit Asterisk kommuniziert. (Die erzeugt übrigens die Funktion »launch_script()« in der Datei »res_agi.c«.) Legt der Anrufer auf, dann hört Asterisk auf, von der Pipe zu lesen. Wenn das AGI-Programm dann per Stdout in die Pipe schreibt, bekommt es sofort das Signal »SIGPIPE« geschickt und haucht selbst sein Leben aus.
Das passiert immer, wenn ein Prozess in eine Pipe schreibt, bei der der lesende Prozess schon beendet ist (Broken Pipe, siehe »man 7 signal«). Wollen Sie nach Stdout schreiben ohne die Angst, dass ihr AGI-Programm dabei stirbt, müssen Sie das Signal »SIGPIPE« ignorieren oder einen Signalhandler dafür schreiben.
Verteilt: Fast-AGI
Wenn die benötigte Rechenzeit und der Speicherbedarf aller auszuführenden AGI-Programme anschwellen, dann ist es sinnvoll, die Programme nicht auf demselben Server auszuführen wie Asterisk, sondern auf einem anderen im lokalen Netzwerk. Programme, mit denen Asterisk eine TCP/IP-Verbindung unterhält, heißen im Folgenden Fast-AGI-Programme. Die Syntax, um vom Dialplan aus die Kontrolle über einen Channel an ein solches Fast-AGI-Programm zu übertragen, lautet:
AGI(agi://IP-Nummer[:Port][/Zeichenkette]
Wenn Asterisk eine Priority mit einem Befehl dieser Art abarbeitet und Sie keine Portnummer angegeben haben, dann versucht der Asterisk-Server eine TCP/IP-Verbindung zu Port 4573 des Rechners mit der angegebenen IP-Nummer aufzubauen. Dort muss das Fast-AGI-Programm permanent laufen und Verbindungen zu dem Port entgegennehmen.
Jedem Fast-AGI-Programm einen eigenen Port zuzuweisen ist eine Möglichkeit, wie Sie mehrere Fast-AGI-Programme auf einem Rechner parallel laufen lassen können. Eine andere bietet der Einsatz eines Fast-AGI-Servers, wie ihn das Paket Asterisk-Java [8] für Java-Programmierer mitbringt. Sie können auch mit dem Befehl »DeadAGI« eine TCP/IP-Verbindung zu einem Fast-AGI-Programm aufbauen.
Unterschiede sind gering
Die Syntax ist identisch zum AGI-Befehl.Und: Alle AGI-Variablen, die Asterisk einem AGI-Programm über Stdin zur Verfügung stellt, schickt es auch an Fast-AGI-Programme. Zu den AGI-Variablen kommt bei Fast-AGI-Programmen in jedem Fall die Variable »agi_network« hinzu, die auf den Wert »yes« gesetzt ist. Genau dann, wenn in dem oben gezeigten Aufruf des AGI-Befehls die übergebene Zeichenkette existiert, schickt Asterisk noch die AGI-Variable »agi_network_script«, deren Wert dann »Zeichenkette« ist, an das Fast-AGI-Programm. Diese Variable können Sie auch dazu verwenden, um dem Fast-AGI-Programm Argumente zu übergeben. Der Befehl
AGI(agi://192.168.0.3/?Name1=Wert1&Name2=Wert2&Name3=Wert3)
liefert darum erwartungsgemäß auf Asterisks Kommandozeilen-Interface:
agi_network_script: ?Name1=Wert1&Name2=Wert2&Name3=Wert3
Das Format, in dem Sie die Argumente übergeben, ist frei wählbar. Es ist Aufgabe des Fast-AGI-Programms, den Wert der Variablen »agi_network_script« zu verarbeiten. Im Wesentlichen verhält sich Fast-AGI also wie AGI. Befehle, die Asterisk über die TCP/IP-Verbindung erhält, beantwortet es genauso wie AGI-Befehle über Stdin. Ein direkter Zugriff auf den Audiodatenstrom wie bei von EAGI aufgerufenen Programmen ist bei FastAGI-Programmen nicht möglich.
Da ein Fast-AGI-Programm aber niemals ein Signal bekommt, wenn der Anrufer auflegt, spielt die Channel-Variable »AGISIGHUP« bei diesen Programmen keine Rolle. Wenn das Fast-AGI-Programm Daten an Asterisk schickt, nachdem der Anrufer aufgelegt hat, dann hat das einen Socket-Write-Error zur Folge. Da bei Fast-AGI-Programmen in der Situation natürlich kein »SIGPIPE« entsteht, haben AGI-Programme, die sowohl »SIGPIPE« als auch »SIGHUP« ignorieren, recht große Ähnlichkeit mit Fast-AGI-Programmen.

Abbildung 5: Anhand dieser Zeichenabelle, deren Systematik offensichtlich ist, geben die Anrufer den gewünschten Wikipedia-Begriff per Nummerntastatur an das System weiter.
Information über den Programmzustand
Die Befehle »AGI«, »EAGI« und »DeadAGI« informieren mit der Channel-Variablen »AGISTATUS« über ihren Zustand. Wenn sich das Programm nicht ausführen ließ, dann ist ihr Wert »FAILURE«. Dieses Feature funktioniert allerdings nur in Asterisk-Versionen korrekt, die das Patch zu Bug 12462 [9] enthalten.
Auch einen »FAILURE« melden AGI- und Dead-AGI-Programme per »AGISTATUS«, wenn Sie einen von null verschiedenen Wert zurückgeben. Bei zurückgelieferter Null beinhaltet die Variable dagegen »SUCCESS« – allerdings nur mit neueren Asterisk-Versionen. Wenn der Anrufer während der Ausführung eines AGI-Programms auflegt, bekommt »AGISTATUS« den Wert »HANGUP« zugewiesen.
Ausblick
Das Asterisk Gateway Interface ermöglicht komplexe Telefonie-Anwendungen in vielerlei Programmiersprachen. Einige fertige Bibliotheken für Dead- und Fast-AGI-Programme erleichtern dabei die Praxis – eine Zusammenstellung für mehrere Sprachen finden Sie unter [6]. Anfang Oktober hat Digium die erste offizielle Version von Asterisk 1.6 veröffentlicht, in der es auch mit dem Asterisk Gateway Interface zusammenhängende Neuerungen [10] gibt. (jk)
|
Infos |
|---|
|
[1] Readwikipedia: [http://www.readwikipedia.net] [2] Wikipedia2speech.php und Anleitung: [http://www.open-tk.de/wikipedia2speech] [3] Vollständige Listings zum Artikel: [ftp://www.linux-magazin.de/pub/listings/magazin/2008/12/AGI/] [4] Deutsche Sprachbausteine: [http://www.amooma.de/asterisk/sprachbausteine/] [5] Libaginow: [http://www.open-tk.de/libaginow] [6] AGI-Referenzen und -Beispiele in vielen Programmiersprachen sowie Rückgabewerte von AGI-Befehlen: [http://www.voip-info.org/wiki/view/Asterisk+AGI] [7] Dokumentation der Asterisk-Quellen: [http://www.asterisk.org/doxygen/1.4/] [8] Asterisk-Java: [http://asterisk-java.org] [9] Asterisk-Bug 12462: [http://bugs.digium.com/view.php?id=12462] [10] AGI bei Asterisk 1.6: [http://www.open-tk.de/agi] |
|
Der Autor |
|---|
|
VoIP-Fan Fabian Müller studiert Mathematik an der Fern-Universität in Hagen und ist Inhaber eines Quickborner Unternehmens, das Software entwickelt und Hardware vertreibt [http://www.open-tk.de]. |







